From 90aa1293b3dbcd6a9712119b1a514742932ab533 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Fri, 16 Oct 2026 07:43:30 +0100 Subject: [PATCH 1/4] refactor: route native lookups through a typed registry and host table Rust no longer imports sqlbuild modules by path. Classes, enums, errors and functions that sqlbuild._native registers are reached through records::registry (RecordId, ErrorId, NativeAttr), checked against the registration tables when the module initialises. Staying-Python entry points (adapter, user code, executor and runtime, third-party, public facade) are reached through the native_host table and sqlbuild._native_host, which resolves each name lazily. shape_classes.rs and hosted_native_name are removed. The native module path scanner checks every host table row. --- crates/README.md | 10 +- .../src/hosted_shapes/constants.rs | 2 - .../src/hosted_shapes/errors.rs | 5 - .../hosted_shapes/main/captured_shape_name.rs | 11 + .../hosted_shapes/main/hosted_native_name.rs | 28 - .../src/hosted_shapes/main/mod.rs | 2 +- .../sqlbuild-cache/src/hosted_shapes/mod.rs | 3 +- .../tests/test_hosted_shape_types.rs | 31 +- .../src/hosted_shapes/tests/test_types.rs | 6 +- .../src/dispatch/_helpers/routes.rs | 8 +- crates/sqlbuild-cli/src/dispatch/constants.rs | 183 +-- crates/sqlbuild-cli/src/dispatch/models.rs | 7 +- .../_helpers/analysis/binding_catalog.rs | 12 +- .../_helpers/analysis/explicit_references.rs | 34 +- .../_helpers/analysis/reference_facts.rs | 10 +- .../_helpers/analysis/required_columns.rs | 8 +- .../_helpers/analysis/schema_request.rs | 8 +- .../analysis_session/fixture_columns.rs | 20 +- .../analysis_session/fixture_inferences.rs | 6 +- .../analysis_session/model_analyses.rs | 42 +- .../analysis_session/outcome_objects.rs | 33 +- .../analysis_session/profile_inputs.rs | 25 +- .../analysis_session/stage_functions.rs | 12 +- .../_helpers/attachments/cursor_intrinsics.rs | 6 +- .../_helpers/attachments/reference_markers.rs | 7 +- .../_helpers/audit_command/command.rs | 55 +- .../bindings/_helpers/auditing/identity.rs | 8 +- .../bindings/_helpers/auditing/measurement.rs | 9 +- .../_helpers/auditing/schema_audits.rs | 7 +- .../_helpers/authored_values/python_nodes.rs | 6 +- .../_helpers/build_command/command.rs | 20 +- .../_helpers/build_command/completion.rs | 30 +- .../_helpers/build_command/defer_clone.rs | 78 +- .../_helpers/build_command/execution.rs | 52 +- .../_helpers/build_command/invocation.rs | 28 +- .../_helpers/build_command/lifecycle.rs | 36 +- .../_helpers/build_command/planning.rs | 29 +- .../_helpers/build_command/policies.rs | 7 +- .../build_progress/build_callbacks.rs | 6 +- .../_helpers/build_progress/terminal.rs | 4 +- .../bindings/_helpers/cache/native_store.rs | 17 +- .../bindings/_helpers/check/check_command.rs | 85 +- .../src/bindings/_helpers/check/check_core.rs | 48 +- .../bindings/_helpers/check/check_results.rs | 9 +- .../src/bindings/_helpers/cli/argv.rs | 7 +- .../src/bindings/_helpers/cli/dispatch.rs | 22 +- .../src/bindings/_helpers/cli/entry.rs | 104 +- .../src/bindings/_helpers/cli/launch_state.rs | 14 +- .../src/bindings/_helpers/cli/namespace.rs | 11 +- .../bindings/_helpers/cli/observability.rs | 47 +- .../src/bindings/_helpers/cli/progress.rs | 11 +- .../bindings/_helpers/cli/progress_text.rs | 10 +- .../bindings/_helpers/clone/clone_command.rs | 48 +- .../_helpers/clone/clone_execution.rs | 50 +- .../bindings/_helpers/clone/clone_pipeline.rs | 16 +- .../_helpers/command_runtime/adapters.rs | 20 +- .../_helpers/command_runtime/connection.rs | 26 +- .../_helpers/command_runtime/invocation.rs | 16 +- .../command_runtime/runtime_errors.rs | 21 +- .../command_runtime/warehouse_scope.rs | 18 +- .../_helpers/command_runtime/warehouses.rs | 31 +- .../compile_attachment/cursor_roles.rs | 8 +- .../compile_attachment/declaration_scope.rs | 22 +- .../compile_attachment/effective_config.rs | 17 +- .../compile_attachment/model_config.rs | 29 +- .../compile_attachment/model_inputs.rs | 52 +- .../sql_expansion_context.rs | 37 +- .../compile_attachment/storage_policies.rs | 20 +- .../_helpers/compile_attachment/target.rs | 8 +- .../compile_command/prepared_artifacts.rs | 4 +- .../_helpers/compile_command/report.rs | 39 +- .../_helpers/compile_command/reuse_attempt.rs | 27 +- .../compile_command/reuse_recording.rs | 18 +- .../compile_command/runtime_identity.rs | 5 +- .../_helpers/compile_command/target_writer.rs | 43 +- .../compile_command/test_artifact_cache.rs | 15 +- .../_helpers/compile_driver/analysis.rs | 58 +- .../_helpers/compile_driver/analysis_cache.rs | 6 +- .../_helpers/compile_driver/assembly.rs | 29 +- .../compile_driver/assembly_analysis.rs | 28 +- .../compile_driver/assembly_checks.rs | 23 +- .../_helpers/compile_driver/assembly_facts.rs | 17 +- .../compile_driver/assembly_models.rs | 24 +- .../compile_driver/assembly_project.rs | 27 +- .../compile_driver/assembly_sql_tests.rs | 21 +- .../_helpers/compile_driver/command.rs | 15 +- .../compile_driver/config_validation.rs | 31 +- .../_helpers/compile_driver/deferral.rs | 21 +- .../compile_driver/diagnostic_collector.rs | 6 +- .../bindings/_helpers/compile_driver/diff.rs | 8 +- .../_helpers/compile_driver/discovery.rs | 68 +- .../_helpers/compile_driver/inputs.rs | 69 +- .../compile_driver/integration_loaders.rs | 38 +- .../_helpers/compile_driver/phases.rs | 31 +- .../_helpers/compile_driver/pipeline.rs | 62 +- .../compile_driver/pipeline_entries.rs | 24 +- .../_helpers/compile_driver/plan_result.rs | 44 +- .../_helpers/compile_driver/project.rs | 51 +- .../compile_driver/python_plan_entries.rs | 29 +- .../compile_driver/relation_targets.rs | 16 +- .../compile_driver/selection_phases.rs | 47 +- .../compile_driver/target_defaults.rs | 13 +- .../_helpers/compile_driver/targets.rs | 32 +- .../compile_outputs/runtime_target.rs | 5 +- .../_helpers/compile_records/constants.rs | 56 +- .../_helpers/compiled_project/attachment.rs | 94 +- .../compiled_project/expanded_offsets.rs | 8 +- .../_helpers/compiled_project/loop_classes.rs | 127 +- .../_helpers/compiled_project/records.rs | 46 +- .../_helpers/compiler_records/constants.rs | 26 +- .../bindings/_helpers/compiler_records/mod.rs | 1 - .../compiler_records/shape_classes.rs | 42 - .../_helpers/compute_logs/conversion.rs | 37 +- .../bindings/_helpers/compute_logs/storage.rs | 20 +- .../_helpers/contract_command/command.rs | 33 +- .../_helpers/contract_command/write.rs | 22 +- .../_helpers/contract_records/constants.rs | 123 +- .../contracts/contract_diagnostics.rs | 29 +- .../_helpers/contracts/project_config.rs | 41 +- .../_helpers/contracts/resource_identities.rs | 9 +- .../_helpers/contracts/spec_resolution.rs | 29 +- .../bindings/_helpers/contracts/sql_values.rs | 27 +- .../_helpers/contracts/state_literals.rs | 9 +- .../bindings/_helpers/cost_command/bounds.rs | 5 +- .../bindings/_helpers/cost_command/command.rs | 16 +- .../bindings/_helpers/cost_command/refresh.rs | 25 +- .../bindings/_helpers/cost_command/refusal.rs | 7 +- .../_helpers/cost_report/degradation.rs | 17 +- .../bindings/_helpers/cost_report/records.rs | 19 +- .../_helpers/cost_telemetry/allocation.rs | 17 +- .../_helpers/cost_telemetry/context.rs | 8 +- .../_helpers/cost_telemetry/ledger.rs | 10 +- .../_helpers/cost_telemetry/snowflake.rs | 5 +- .../_helpers/cost_telemetry/values.rs | 3 - .../bindings/_helpers/cursor/conversion.rs | 17 +- .../src/bindings/_helpers/cursor/intervals.rs | 16 +- .../src/bindings/_helpers/cursor/overrides.rs | 18 +- .../src/bindings/_helpers/cursor/scalars.rs | 21 +- .../_helpers/dag_artifact/artifact.rs | 8 +- .../_helpers/dag_artifact/projection.rs | 32 +- .../_helpers/dag_artifact/selection.rs | 11 +- .../_helpers/dagster_invocation/access.rs | 20 +- .../_helpers/dagster_invocation/arguments.rs | 15 +- .../_helpers/dagster_invocation/failures.rs | 3 +- .../_helpers/dagster_invocation/results.rs | 30 +- .../_helpers/dagster_invocation/translator.rs | 7 +- .../_helpers/dbt_command/auto_init.rs | 16 +- .../bindings/_helpers/dbt_command/command.rs | 22 +- .../bindings/_helpers/dbt_command/debug.rs | 11 +- .../src/bindings/_helpers/dbt_command/init.rs | 20 +- .../_helpers/dbt_command/init_project.rs | 23 +- .../bindings/_helpers/dbt_events/results.rs | 12 +- .../src/bindings/_helpers/dbt_interop/cli.rs | 24 +- .../_helpers/dbt_interop/event_stream.rs | 5 +- .../bindings/_helpers/dbt_interop/graph.rs | 26 +- .../bindings/_helpers/dbt_interop/manifest.rs | 16 +- .../_helpers/dbt_interop/orchestration.rs | 11 +- .../bindings/_helpers/dbt_interop/pipeline.rs | 67 +- .../_helpers/dbt_interop/pipeline_phases.rs | 31 +- .../_helpers/dbt_interop/pipeline_prologue.rs | 37 +- .../src/bindings/_helpers/dbt_interop/plan.rs | 19 +- .../_helpers/dbt_interop/plan_output.rs | 40 +- .../bindings/_helpers/dbt_interop/profile.rs | 16 +- .../dbt_interop/profile_connection.rs | 22 +- .../_helpers/dbt_interop/profile_load.rs | 10 +- .../dbt_interop/reference_resolver.rs | 35 +- .../bindings/_helpers/dbt_interop/runner.rs | 18 +- .../_helpers/dbt_interop/runtime_config.rs | 32 +- .../_helpers/dbt_interop/runtime_execution.rs | 20 +- .../_helpers/dbt_interop/runtime_results.rs | 12 +- .../_helpers/dbt_interop/sqlbuild_work.rs | 40 +- .../_helpers/debug_command/run_debug.rs | 13 +- .../_helpers/diagnostics/log_records.rs | 9 +- .../bindings/_helpers/diagnostics/trackers.rs | 13 +- .../bindings/_helpers/diff_report/command.rs | 74 +- .../bindings/_helpers/diff_report/query.rs | 46 +- .../bindings/_helpers/diff_report/support.rs | 19 +- .../_helpers/diff_report/tolerances.rs | 17 +- .../_helpers/discovery/configuration.rs | 18 +- .../_helpers/discovery/declaration_records.rs | 47 +- .../_helpers/discovery/description_inputs.rs | 26 +- .../src/bindings/_helpers/discovery/faults.rs | 8 +- .../_helpers/discovery/header_declarations.rs | 48 +- .../_helpers/discovery/header_values.rs | 32 +- .../_helpers/discovery/hook_entries.rs | 8 +- .../_helpers/discovery/imported_modules.rs | 40 +- .../_helpers/discovery/imported_providers.rs | 45 +- .../discovery/imported_python_nodes.rs | 90 +- .../_helpers/discovery/model_file_record.rs | 8 +- .../discovery/python_function_files.rs | 16 +- .../src/bindings/_helpers/discovery/raised.rs | 58 +- .../_helpers/discovery/scope_snapshot.rs | 18 +- .../_helpers/discovery/sql_test_blocks.rs | 27 +- .../_helpers/discovery/sql_test_records.rs | 7 +- .../_helpers/discovery/yaml_entries.rs | 17 +- .../_helpers/discovery/yaml_records.rs | 9 +- .../_helpers/event_exporting/constants.rs | 8 - .../_helpers/event_exporting/dispatcher.rs | 7 +- .../_helpers/event_exporting/scope.rs | 14 +- .../_helpers/event_exporting/values.rs | 16 +- .../_helpers/event_exporting/workers.rs | 6 +- .../_helpers/execution_history/conversion.rs | 62 +- .../_helpers/execution_history/postgres.rs | 12 +- .../_helpers/execution_history/sqlite.rs | 18 +- .../_helpers/exported_constants/constants.rs | 5 +- .../_helpers/fingerprints/latest_rows.rs | 25 +- .../_helpers/fingerprints/query_hashes.rs | 9 +- .../_helpers/fingerprints/storage_io.rs | 12 +- .../_helpers/format_command/command.rs | 5 +- .../_helpers/format_command/config.rs | 11 +- .../bindings/_helpers/format_command/run.rs | 34 +- .../_helpers/format_command/selection.rs | 48 +- .../_helpers/freshness/freshness_command.rs | 53 +- .../_helpers/freshness/freshness_observe.rs | 8 +- .../_helpers/freshness/observation.rs | 43 +- .../bindings/_helpers/freshness/planning.rs | 41 +- .../_helpers/freshness/publication.rs | 29 +- .../bindings/_helpers/freshness/state_io.rs | 12 +- .../bindings/_helpers/freshness/support.rs | 29 +- .../_helpers/frontier/compile_frontier.rs | 15 +- .../src/bindings/_helpers/frontier/encoder.rs | 4 +- .../_helpers/graph/execution_gates.rs | 21 +- .../src/bindings/_helpers/graph/keys.rs | 7 +- .../_helpers/graph/python_lifecycle.rs | 7 +- .../_helpers/graph/python_node_graph.rs | 43 +- .../_helpers/graph/python_selection.rs | 8 +- .../src/bindings/_helpers/graph/views.rs | 8 +- .../_helpers/hand_built/compiled_model.rs | 41 +- .../bindings/_helpers/init_command/command.rs | 8 +- .../janitor/janitor_classification.rs | 11 +- .../_helpers/janitor/janitor_command.rs | 18 +- .../_helpers/janitor/janitor_facts.rs | 37 +- .../bindings/_helpers/janitor/janitor_keys.rs | 9 +- .../_helpers/janitor/janitor_old_names.rs | 33 +- .../_helpers/janitor/janitor_phases.rs | 26 +- .../_helpers/janitor/janitor_planning.rs | 15 +- .../_helpers/lifecycle_events/payloads.rs | 6 +- .../_helpers/lineage/lineage_cache.rs | 5 +- .../_helpers/lineage/lineage_command.rs | 55 +- .../_helpers/lineage/lineage_index.rs | 11 +- .../_helpers/lineage/lineage_objects.rs | 39 +- .../_helpers/lineage/project_lineage.rs | 7 +- .../bindings/_helpers/load/dlt_destination.rs | 9 +- .../bindings/_helpers/load/ingestr_command.rs | 32 +- .../bindings/_helpers/load/load_command.rs | 79 +- .../bindings/_helpers/load/load_progress.rs | 15 +- .../bindings/_helpers/load/load_selection.rs | 26 +- .../src/bindings/_helpers/manifest/builder.rs | 14 +- .../src/bindings/_helpers/manifest/rows.rs | 8 +- .../_helpers/microbatches/conversion.rs | 10 +- .../_helpers/microbatches/projection.rs | 16 +- .../_helpers/microbatches/statements.rs | 36 +- .../src/bindings/_helpers/mod.rs | 1 + .../_helpers/model_config/config_errors.rs | 16 +- .../_helpers/model_config/config_templates.rs | 11 +- .../_helpers/model_config/config_values.rs | 5 +- .../_helpers/model_config/contract_classes.rs | 106 +- .../_helpers/model_config/header_columns.rs | 9 +- .../_helpers/model_config/template_data.rs | 40 +- .../model_config/unicode_environment.rs | 6 +- .../bindings/_helpers/native_host/adapter.rs | 262 ++++ .../_helpers/native_host/constants.rs | 20 + .../bindings/_helpers/native_host/executor.rs | 970 ++++++++++++ .../bindings/_helpers/native_host/facade.rs | 58 + .../src/bindings/_helpers/native_host/host.rs | 54 + .../src/bindings/_helpers/native_host/mod.rs | 11 + .../bindings/_helpers/native_host/models.rs | 60 + .../_helpers/native_host/registration.rs | 15 + .../_helpers/native_host/third_party.rs | 250 ++++ .../_helpers/native_host/user_code.rs | 370 +++++ .../_helpers/output_capture/context.rs | 5 +- .../_helpers/output_capture/dispatcher.rs | 13 +- .../_helpers/output_capture/records.rs | 34 +- .../_helpers/output_capture/values.rs | 9 +- .../bindings/_helpers/plan_command/plan.rs | 38 +- .../bindings/_helpers/plan_command/rules.rs | 32 +- .../_helpers/plan_output/cursor_details.rs | 17 +- .../bindings/_helpers/plan_output/display.rs | 13 +- .../_helpers/plan_output/plan_text.rs | 7 +- .../_helpers/planner_changes/actions.rs | 6 +- .../_helpers/planner_changes/detect.rs | 33 +- .../_helpers/planner_changes/renames.rs | 10 +- .../_helpers/planner_changes/results.rs | 36 +- .../_helpers/planner_entries/clone_plans.rs | 75 +- .../_helpers/planner_entries/command_plans.rs | 35 +- .../_helpers/planner_entries/display_plan.rs | 31 +- .../planner_entries/execution_plan.rs | 51 +- .../_helpers/planner_entries/model_entries.rs | 81 +- .../_helpers/planner_entries/plan_output.rs | 57 +- .../_helpers/planner_entries/relations.rs | 25 +- .../_helpers/planner_entries/values.rs | 26 +- .../_helpers/planner_identity/graph.rs | 13 +- .../_helpers/planner_identity/hashing.rs | 10 +- .../_helpers/planner_identity/identities.rs | 9 +- .../_helpers/planner_identity/staleness.rs | 12 +- .../planner_migrations/column_candidates.rs | 5 +- .../planner_migrations/column_entries.rs | 31 +- .../planner_migrations/column_objects.rs | 15 +- .../planner_migrations/column_overlay.rs | 12 +- .../planner_migrations/column_planning.rs | 20 +- .../planner_migrations/column_warnings.rs | 7 +- .../_helpers/planner_migrations/columns.rs | 8 +- .../planner_migrations/compatibility.rs | 23 +- .../_helpers/planner_migrations/constants.rs | 15 +- .../_helpers/planner_migrations/discovery.rs | 13 +- .../planner_migrations/discovery_events.rs | 19 +- .../planner_migrations/discovery_models.rs | 22 +- .../planner_migrations/discovery_requests.rs | 13 +- .../planner_migrations/model_entries.rs | 33 +- .../planner_migrations/model_handover.rs | 7 +- .../planner_migrations/model_overlay.rs | 14 +- .../planner_migrations/model_planning.rs | 38 +- .../planner_migrations/old_name_entries.rs | 18 +- .../planner_migrations/old_name_objects.rs | 19 +- .../planner_migrations/old_name_planning.rs | 34 +- .../planner_migrations/old_name_references.rs | 19 +- .../_helpers/planner_migrations/old_names.rs | 20 +- .../planner_migrations/planning_boundary.rs | 18 +- .../planner_migrations/planning_execution.rs | 14 +- .../planner_migrations/planning_origin.rs | 8 +- .../planner_migrations/projections.rs | 10 +- .../planner_migrations/state_inspection.rs | 15 +- .../_helpers/planner_phases/buildability.rs | 7 +- .../_helpers/planner_phases/constants.rs | 7 - .../planner_phases/execution_changes.rs | 23 +- .../_helpers/planner_phases/identities.rs | 9 +- .../_helpers/planner_phases/objects.rs | 42 +- .../planner_phases/output_assembly.rs | 17 +- .../_helpers/planner_phases/retention.rs | 40 +- .../_helpers/planner_phases/scopes.rs | 9 +- .../planner_phases/warehouse_state.rs | 17 +- .../_helpers/planner_records/constants.rs | 36 +- .../planner_resolution/cursor_bounds.rs | 23 +- .../planner_resolution/cursor_model.rs | 54 +- .../planner_resolution/cursor_overrides.rs | 12 +- .../planner_resolution/cursor_policies.rs | 16 +- .../planner_resolution/cursor_ranges.rs | 48 +- .../planner_resolution/cursor_safety.rs | 21 +- .../planner_resolution/cursor_warnings.rs | 8 +- .../planner_resolution/cursor_windows.rs | 14 +- .../planner_resolution/future_safety.rs | 14 +- .../planner_resolution/maximum_safety.rs | 20 +- .../planner_resolution/orchestration.rs | 46 +- .../_helpers/planner_resolution/refs.rs | 23 +- .../_helpers/planner_resolution/sources.rs | 10 +- .../_helpers/planner_resolution/values.rs | 10 +- .../_helpers/planner_scenario/artifacts.rs | 18 +- .../bindings/_helpers/planner_scenario/cli.rs | 24 +- .../_helpers/planner_scenario/entries.rs | 26 +- .../_helpers/planner_scenario/execution.rs | 28 +- .../_helpers/planner_scenario/fixtures.rs | 12 +- .../_helpers/planner_scenario/graph.rs | 10 +- .../_helpers/planner_scenario/relations.rs | 8 +- .../bindings/_helpers/planner_scenario/sql.rs | 8 +- .../_helpers/planner_scenario/values.rs | 9 +- .../_helpers/planner_scope/loaders.rs | 10 +- .../bindings/_helpers/planner_scope/scope.rs | 31 +- .../_helpers/planner_scope/sources.rs | 12 +- .../bindings/_helpers/planner_scope/values.rs | 4 - .../_helpers/planner_selection/carrier.rs | 6 +- .../planner_selection/model_selectors.rs | 4 +- .../_helpers/planner_selection/selectors.rs | 14 +- .../_helpers/planner_selection/unit_tests.rs | 10 +- .../planner_strategy/audit_entries.rs | 21 +- .../_helpers/planner_strategy/audits.rs | 11 +- .../_helpers/planner_strategy/constants.rs | 6 - .../planner_strategy/cursor_inputs.rs | 23 +- .../_helpers/planner_strategy/cursor_types.rs | 8 +- .../planner_strategy/fixture_completion.rs | 3 +- .../planner_strategy/fixture_context.rs | 3 +- .../planner_strategy/fixture_reads.rs | 24 +- .../planner_strategy/fixture_types.rs | 8 +- .../planner_strategy/fixture_values.rs | 14 +- .../_helpers/planner_strategy/lineage.rs | 9 +- .../planner_strategy/microbatch_limits.rs | 29 +- .../_helpers/planner_strategy/microbatches.rs | 38 +- .../planner_strategy/model_entries.rs | 32 +- .../_helpers/planner_strategy/strategy.rs | 22 +- .../_helpers/planner_strategy/values.rs | 19 +- .../_helpers/planner_warehouse/constants.rs | 65 +- .../_helpers/planner_warehouse/cursors.rs | 78 +- .../planner_warehouse/fingerprint_cache.rs | 13 +- .../planner_warehouse/semantic_sources.rs | 5 +- .../_helpers/planner_warehouse/snapshot.rs | 34 +- .../planner_warehouse/source_columns.rs | 7 +- .../planner_warehouse/source_deferral.rs | 14 +- .../planner_warehouse/source_freshness.rs | 9 +- .../planner_warehouse/source_tables.rs | 8 +- .../_helpers/planner_warehouse/watermarks.rs | 10 +- .../project_assembly/native_resources.rs | 5 +- .../project_assembly/syntax_validation.rs | 23 +- .../bindings/_helpers/query_command/dag.rs | 19 +- .../_helpers/query_command/project.rs | 9 +- .../bindings/_helpers/query_command/query.rs | 23 +- .../_helpers/record_behaviours/auditing.rs | 10 +- .../_helpers/record_behaviours/compiled.rs | 30 +- .../_helpers/record_behaviours/cursors.rs | 21 +- .../_helpers/record_behaviours/freshness.rs | 6 +- .../_helpers/record_behaviours/lexical.rs | 6 +- .../record_behaviours/lineage_facts.rs | 22 +- .../_helpers/record_behaviours/migrations.rs | 8 +- .../_helpers/record_behaviours/planner.rs | 15 +- .../_helpers/record_behaviours/rule_shapes.rs | 9 +- .../bindings/_helpers/records/constants.rs | 16 +- .../src/bindings/_helpers/records/fields.rs | 7 +- .../src/bindings/_helpers/records/ids.rs | 1313 +++++++++++++++++ .../src/bindings/_helpers/records/mod.rs | 2 + .../src/bindings/_helpers/records/models.rs | 12 +- .../src/bindings/_helpers/records/registry.rs | 165 +++ .../_helpers/refactor/contract_compare.rs | 29 +- .../_helpers/refactor/contract_edits.rs | 21 +- .../_helpers/refactor/offline_compile.rs | 79 +- .../_helpers/refactor/python_strings.rs | 7 +- .../_helpers/refactor/refactor_command.rs | 16 +- .../_helpers/refactor/refactor_facts.rs | 26 +- .../bindings/_helpers/render/authored_sql.rs | 24 +- .../_helpers/render/declaration_indexes.rs | 10 +- .../_helpers/render/declaration_values.rs | 72 +- .../bindings/_helpers/render/macro_calls.rs | 6 +- .../_helpers/render/macro_expansion_result.rs | 88 +- .../_helpers/result_output/aggregation.rs | 10 +- .../_helpers/result_output/documents.rs | 7 +- .../result_output/integration_values.rs | 8 +- .../_helpers/result_output/projection.rs | 45 +- .../_helpers/result_output/terminal_index.rs | 5 +- .../_helpers/rule_records/constants.rs | 10 +- .../_helpers/rules/builtin_catalogue.rs | 19 +- .../src/bindings/_helpers/rules/catalogue.rs | 13 +- .../src/bindings/_helpers/rules/constants.rs | 3 - .../_helpers/rules/custom_evaluation.rs | 39 +- .../_helpers/rules/custom_host_observation.rs | 6 +- .../bindings/_helpers/rules/custom_hosts.rs | 16 +- .../_helpers/rules/custom_rule_cache.rs | 7 +- .../_helpers/rules/custom_rule_evidence.rs | 11 +- .../bindings/_helpers/rules/dsl_headers.rs | 20 +- .../bindings/_helpers/rules/early_sql_lint.rs | 23 +- .../src/bindings/_helpers/rules/findings.rs | 10 +- .../src/bindings/_helpers/rules/fix_facts.rs | 8 +- .../bindings/_helpers/rules/fix_session.rs | 37 +- .../bindings/_helpers/rules/fix_verdict.rs | 8 +- .../bindings/_helpers/rules/lint_expansion.rs | 39 +- .../bindings/_helpers/rules/lint_headers.rs | 19 +- .../_helpers/rules/lint_preparation.rs | 49 +- .../_helpers/rules/lint_sql_bodies.rs | 12 +- .../src/bindings/_helpers/rules/lint_stop.rs | 7 +- .../src/bindings/_helpers/rules/lint_text.rs | 20 +- .../_helpers/rules/project_tree_queries.rs | 6 +- .../src/bindings/_helpers/rules/rows.rs | 11 +- .../src/bindings/_helpers/rules/rule_api.rs | 11 +- .../bindings/_helpers/rules/rule_dispatch.rs | 57 +- .../src/bindings/_helpers/rules/rule_plan.rs | 14 +- .../src/bindings/_helpers/rules/rule_run.rs | 9 +- .../bindings/_helpers/rules/rules_config.rs | 57 +- .../src/bindings/_helpers/rules/run_lint.rs | 22 +- .../src/bindings/_helpers/rules/selection.rs | 8 +- .../src/bindings/_helpers/rules/sql_rules.rs | 41 +- .../_helpers/rules/sql_test_objects.rs | 10 +- .../scenario_command/capture_command.rs | 7 +- .../_helpers/scenario_command/conversions.rs | 17 +- .../_helpers/scenario_command/presentation.rs | 14 +- .../_helpers/scenario_command/setup.rs | 52 +- .../scenario_command/snapshot_sync.rs | 26 +- .../_helpers/scenario_command/test_command.rs | 36 +- .../_helpers/scope_records/constants.rs | 13 +- .../_helpers/scopes/compile_usages.rs | 20 +- .../_helpers/scopes/relationship_names.rs | 16 +- .../bindings/_helpers/scopes/scope_command.rs | 21 +- .../bindings/_helpers/scopes/scope_errors.rs | 12 +- .../bindings/_helpers/scopes/scope_index.rs | 94 +- .../bindings/_helpers/scopes/scope_loading.rs | 42 +- .../bindings/_helpers/scopes/scope_lookup.rs | 13 +- .../bindings/_helpers/scopes/scope_objects.rs | 82 +- .../_helpers/scopes/scope_report_objects.rs | 39 +- .../bindings/_helpers/scopes/scope_reports.rs | 17 +- .../_helpers/scopes/scope_tolerant.rs | 13 +- .../_helpers/scopes/scope_validation.rs | 25 +- .../_helpers/scopes/scope_visibility.rs | 27 +- .../bindings/_helpers/seed_command/command.rs | 51 +- .../_helpers/semantic_checks/inputs.rs | 15 +- .../_helpers/semantic_checks/resource_sql.rs | 25 +- .../_helpers/semantic_checks/stage_objects.rs | 47 +- .../_helpers/semantic_shapes/shapes.rs | 36 +- .../semantic_shapes/source_bindings.rs | 26 +- .../_helpers/sql_test_command/test_command.rs | 59 +- .../_helpers/sql_test_glue/authored_ctes.rs | 10 +- .../_helpers/sql_test_glue/compiled_tests.rs | 40 +- .../_helpers/sql_test_glue/constants.rs | 3 - .../_helpers/sql_test_glue/cursor_window.rs | 8 +- .../_helpers/sql_test_glue/plan_assembly.rs | 13 +- .../_helpers/sql_test_glue/plan_driver.rs | 15 +- .../_helpers/sql_test_glue/plan_entries.rs | 6 +- .../_helpers/sql_test_glue/plan_errors.rs | 23 +- .../_helpers/sql_test_glue/plan_fixtures.rs | 5 +- .../_helpers/sql_test_glue/plan_rendering.rs | 6 +- .../_helpers/sql_test_glue/plan_rows.rs | 19 +- .../_helpers/sql_test_glue/plan_values.rs | 9 +- .../_helpers/sqltext/authored_values.rs | 28 +- .../bindings/_helpers/sqltext/code_scanner.rs | 11 +- .../bindings/_helpers/sqltext/ddl_effects.rs | 13 +- .../_helpers/sqltext/sql_references.rs | 33 +- .../bindings/_helpers/state_rows/constants.rs | 4 - .../bindings/_helpers/state_rows/events.rs | 7 +- .../bindings/_helpers/state_rows/freshness.rs | 24 +- .../_helpers/state_rows/migration_objects.rs | 12 +- .../state_rows/migration_projections.rs | 10 +- .../_helpers/state_rows/migration_rows.rs | 38 +- .../_helpers/state_rows/state_tables.rs | 10 +- .../bindings/_helpers/type_system/equality.rs | 7 +- .../_helpers/workspace_commands/commands.rs | 16 +- .../src/bindings/main/register.rs | 10 +- fensu.toml | 5 + python-boundary.toml | 5 + scripts/native_module_paths/_helpers/scan.py | 12 +- scripts/native_module_paths/constants.py | 2 + src/sqlbuild/_native.pyi | 1 + src/sqlbuild/_native_host.py | 733 +++++++++ src/sqlbuild/_native_host.pyi | 576 ++++++++ .../_helpers/_test_types.py | 27 + .../native_module_paths/_helpers/helpers.py | 15 + .../_helpers/test_module_paths.py | 12 + .../_helpers/test_native_host.py | 119 ++ 521 files changed, 10088 insertions(+), 6430 deletions(-) delete mode 100644 crates/sqlbuild-cache/src/hosted_shapes/errors.rs create mode 100644 crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs delete mode 100644 crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs delete mode 100644 crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs create mode 100644 src/sqlbuild/_native_host.py create mode 100644 src/sqlbuild/_native_host.pyi create mode 100644 tests/unit/scripts/native_module_paths/_helpers/test_native_host.py diff --git a/crates/README.md b/crates/README.md index 52618c87d0..d2172a022a 100644 --- a/crates/README.md +++ b/crates/README.md @@ -70,7 +70,15 @@ The same check fails when the workspace version, the `pyproject.toml` version an `import(MODULE)?.getattr("name")`, or written as a `("sqlbuild.module", "name")` pair; - module paths are never built at runtime: a `format!` or `concat!` template may not start with `sqlbuild.` or with a `{..PACKAGE..}.`/`{..MODULE..}.` constant. Write the complete module - path as a constant instead. + path as a constant instead; +- every row of the host table (`bindings/_helpers/native_host/`) must resolve as an attribute of + `sqlbuild._native_host`. + +Staying-Python entry points (adapter, user code, executor and runtime, third-party and public +facade) are reached through the host table and `native_host::host::entry`, which imports only +`sqlbuild._native_host`. Classes, enums, errors and functions `sqlbuild._native` registers are +reached through the typed registry (`records::registry` with `RecordId`, `ErrorId` and +`NativeAttr`), never by module path. ## Compiler diff --git a/crates/sqlbuild-cache/src/hosted_shapes/constants.rs b/crates/sqlbuild-cache/src/hosted_shapes/constants.rs index 8f30546304..9bd44bb448 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/constants.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/constants.rs @@ -1,7 +1,5 @@ //! The Python shape names of the natively hosted compiler shapes. -/// The extension module hosting the shapes. -pub const NATIVE_MODULE: &str = "sqlbuild._native"; /// The Python model type a natively hosted compiler shape is captured as. pub const HOSTED_SHAPE_TYPES: [(&str, &str); 491] = [ ( diff --git a/crates/sqlbuild-cache/src/hosted_shapes/errors.rs b/crates/sqlbuild-cache/src/hosted_shapes/errors.rs deleted file mode 100644 index 0f1f6ce65b..0000000000 --- a/crates/sqlbuild-cache/src/hosted_shapes/errors.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Why a Python shape name could not be resolved to one native class. - -/// More than one natively hosted class claims the same Python shape name. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct AmbiguousShapeName; diff --git a/crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs b/crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs new file mode 100644 index 0000000000..8c2cd7fea5 --- /dev/null +++ b/crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs @@ -0,0 +1,11 @@ +//! The Python shape name a natively hosted class is captured as. + +use crate::hosted_shapes::constants::HOSTED_SHAPE_TYPES; + +/// The `module:qualname` the native class `native` is captured as, when it hosts a Python shape. +pub fn captured_shape_name(native: &str) -> Option<&'static str> { + HOSTED_SHAPE_TYPES + .iter() + .find(|(hosted, _)| *hosted == native) + .map(|(_, python)| *python) +} diff --git a/crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs b/crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs deleted file mode 100644 index 62473e0934..0000000000 --- a/crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! The native class hosting one Python shape, found by its module and class name. - -use crate::hosted_shapes::constants::{HOSTED_SHAPE_TYPES, NATIVE_MODULE}; -use crate::hosted_shapes::errors::AmbiguousShapeName; - -/// The native class name hosting `module:class`, or `None` when the shape is still Python. -pub fn hosted_native_name( - module: &str, - class: &str, -) -> Result, AmbiguousShapeName> { - let mut found: Option<&'static str> = None; - for (native, python) in HOSTED_SHAPE_TYPES { - if python.split_once(':') != Some((module, class)) { - continue; - } - let Some(native_name) = native - .strip_prefix(NATIVE_MODULE) - .and_then(|rest| rest.strip_prefix(':')) - else { - continue; - }; - if found.is_some_and(|existing| existing != native_name) { - return Err(AmbiguousShapeName); - } - found = Some(native_name); - } - Ok(found) -} diff --git a/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs b/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs index 99b510965b..c4cfc62bbd 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs @@ -1 +1 @@ -pub mod hosted_native_name; +pub mod captured_shape_name; diff --git a/crates/sqlbuild-cache/src/hosted_shapes/mod.rs b/crates/sqlbuild-cache/src/hosted_shapes/mod.rs index 45be7c1b09..3e004db88f 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/mod.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/mod.rs @@ -1,7 +1,6 @@ -//! Natively hosted compiler shapes and the Python names they are captured and resolved as. +//! Natively hosted compiler shapes and the Python names they are captured as. pub mod constants; -pub mod errors; pub mod main; #[cfg(test)] diff --git a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs index 8070b1c832..d63720c1cb 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs @@ -1,7 +1,7 @@ use crate::hosted_shapes::constants::HOSTED_SHAPE_TYPES; -use crate::hosted_shapes::main::hosted_native_name::hosted_native_name; +use crate::hosted_shapes::main::captured_shape_name::captured_shape_name; use crate::hosted_shapes::tests::helpers::duplicates; -use crate::hosted_shapes::tests::test_types::{HostedKeyTestCase, NativeNameTestCase}; +use crate::hosted_shapes::tests::test_types::{CapturedNameTestCase, HostedKeyTestCase}; #[test] fn given_hosted_shape_table_when_keyed_then_every_native_and_python_name_is_unique() { @@ -34,28 +34,23 @@ fn given_hosted_shape_table_when_keyed_then_every_native_and_python_name_is_uniq } #[test] -fn given_python_shape_when_resolving_then_only_its_own_module_selects_the_native_class() { +fn given_native_class_when_naming_its_capture_then_only_hosted_shapes_keep_a_python_name() { let test_cases = [ - NativeNameTestCase { - description: "a scope severity resolves to the scope enum", - shape: ("sqlbuild.compiler.scopes.types", "DiagnosticSeverity"), - expected_native: Some("ScopeDiagnosticSeverity"), + CapturedNameTestCase { + description: "a hosted planner record keeps its Python name", + native: "sqlbuild._native:PlanOutput", + expected_python: Some("sqlbuild.compiler.planner.models:PlanOutput"), }, - NativeNameTestCase { - description: "a shape name from another module is not hosted", - shape: ("sqlbuild.compiler.compile.models", "DiagnosticSeverity"), - expected_native: None, - }, - NativeNameTestCase { - description: "a hosted planner record resolves by its own name", - shape: ("sqlbuild.compiler.planner.models", "PlanOutput"), - expected_native: Some("PlanOutput"), + CapturedNameTestCase { + description: "a native class hosting no shape has no Python name", + native: "sqlbuild._native:NoSuchShape", + expected_python: None, }, ]; for test_case in test_cases { assert_eq!( - hosted_native_name(test_case.shape.0, test_case.shape.1), - Ok(test_case.expected_native), + captured_shape_name(test_case.native), + test_case.expected_python, "{}", test_case.description ); diff --git a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs index 68ad02324d..a658846a2f 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs @@ -4,8 +4,8 @@ pub(crate) struct HostedKeyTestCase { pub(crate) expected_duplicates: Vec<&'static str>, } -pub(crate) struct NativeNameTestCase { +pub(crate) struct CapturedNameTestCase { pub(crate) description: &'static str, - pub(crate) shape: (&'static str, &'static str), - pub(crate) expected_native: Option<&'static str>, + pub(crate) native: &'static str, + pub(crate) expected_python: Option<&'static str>, } diff --git a/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs b/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs index b801524ca8..181b38e845 100644 --- a/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs +++ b/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs @@ -2,10 +2,10 @@ use crate::dispatch::models::{CallShape, ClassRef, Field, Route, Source}; -const DBT_IMPLEMENTATION: (&str, &str) = ("sqlbuild._native", "run_dbt_command"); +const DBT_IMPLEMENTATION: &str = "run_dbt_command"; -pub(crate) const fn class(module: &'static str, name: &'static str) -> ClassRef { - ClassRef { module, name } +pub(crate) const fn class(name: &'static str) -> ClassRef { + ClassRef { name } } pub(crate) const fn field(name: &'static str, source: Source) -> Field { @@ -26,7 +26,7 @@ pub(crate) const fn request(class_ref: ClassRef, fields: &'static [Field]) -> Fi pub(crate) const fn routed( slot: &'static str, - implementation: (&'static str, &'static str), + implementation: &'static str, arguments: &'static [Field], shape: CallShape, ) -> Route { diff --git a/crates/sqlbuild-cli/src/dispatch/constants.rs b/crates/sqlbuild-cli/src/dispatch/constants.rs index e375fd4a63..0e28af1fe5 100644 --- a/crates/sqlbuild-cli/src/dispatch/constants.rs +++ b/crates/sqlbuild-cli/src/dispatch/constants.rs @@ -11,10 +11,6 @@ pub(crate) const DBT_COMMAND_NAME: &str = "dbt"; pub(crate) const DBT_INIT_SUBCOMMAND: &str = "init"; pub(crate) const DIFF_COMMAND_NAME: &str = "diff"; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const ENTRY_MODELS: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; - const PROJECT_DIR: Field = field("project_dir", Source::ProjectDir); const SELECT: Field = field("select", Source::Select); const EXCLUDE: Field = field("exclude", Source::TupleOf("exclude")); @@ -35,7 +31,7 @@ const CURSOR_FIELDS: [Field; 6] = [ ]; const CURSOR_OVERRIDES: Field = field( "cursor_overrides", - Source::Nested(class(PLANNER_MODELS, "CursorOverrides"), &CURSOR_FIELDS), + Source::Nested(class("CursorOverrides"), &CURSOR_FIELDS), ); const SNAPSHOT_LIMIT_FIELDS: [Field; 5] = [ @@ -53,10 +49,7 @@ const SNAPSHOT_LIMIT_FIELDS: [Field; 5] = [ ]; const SNAPSHOT_LIMITS: Field = field( "limit_inputs", - Source::Nested( - class(NATIVE_RECORDS, "ScenarioSnapshotLimitInputs"), - &SNAPSHOT_LIMIT_FIELDS, - ), + Source::Nested(class("ScenarioSnapshotLimitInputs"), &SNAPSHOT_LIMIT_FIELDS), ); /// A handler receiving one request positionally. @@ -94,13 +87,10 @@ const SCOPE_FIELDS: [Field; 22] = [ same("verbose"), NO_COLOR, ]; -const SCOPE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ScopeCommandRequest"), - &SCOPE_FIELDS, -)]; +const SCOPE_ARGUMENTS: [Field; 1] = [request(class("ScopeCommandRequest"), &SCOPE_FIELDS)]; pub(crate) const SCOPE_ROUTE: Route = routed( "run_scope", - ("sqlbuild._native", "run_scope_command"), + "run_scope_command", &SCOPE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -108,7 +98,7 @@ pub(crate) const SCOPE_ROUTE: Route = routed( const DAG_ARGUMENTS: [Field; 4] = [PROJECT_DIR, NO_SQL_VALIDATION, JSON_OUTPUT, CLI_VARS]; pub(crate) const DAG_ROUTE: Route = routed( "run_dag", - ("sqlbuild._native", "run_dag_command"), + "run_dag_command", &DAG_ARGUMENTS, CallShape { slot_positional: 0, @@ -137,13 +127,10 @@ const PLAN_FIELDS: [Field; 19] = [ same("max_microbatches"), same("selection_diagnostics"), ]; -const PLAN_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "PlanCommandRequest"), - &PLAN_FIELDS, -)]; +const PLAN_ARGUMENTS: [Field; 1] = [request(class("PlanCommandRequest"), &PLAN_FIELDS)]; pub(crate) const PLAN_ROUTE: Route = routed( "run_plan", - ("sqlbuild._native", "run_plan_command"), + "run_plan_command", &PLAN_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -184,13 +171,10 @@ const BUILD_FIELDS: [Field; 34] = [ same("max_microbatches"), same("selection_diagnostics"), ]; -const BUILD_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "BuildCommandRequest"), - &BUILD_FIELDS, -)]; +const BUILD_ARGUMENTS: [Field; 1] = [request(class("BuildCommandRequest"), &BUILD_FIELDS)]; pub(crate) const BUILD_ROUTE: Route = routed( "run_build", - ("sqlbuild._native", "run_build_command"), + "run_build_command", &BUILD_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -209,13 +193,11 @@ const FRESHNESS_FIELDS: [Field; 12] = [ attr("compare_state", "state"), same("fail_on_stale"), ]; -const FRESHNESS_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "FreshnessCommandRequest"), - &FRESHNESS_FIELDS, -)]; +const FRESHNESS_ARGUMENTS: [Field; 1] = + [request(class("FreshnessCommandRequest"), &FRESHNESS_FIELDS)]; pub(crate) const FRESHNESS_ROUTE: Route = routed( "run_freshness", - ("sqlbuild._native", "run_freshness_command"), + "run_freshness_command", &FRESHNESS_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -234,13 +216,10 @@ const TEST_FIELDS: [Field; 12] = [ attr("case_name", "case"), same("inspect"), ]; -const TEST_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "TestCommandRequest"), - &TEST_FIELDS, -)]; +const TEST_ARGUMENTS: [Field; 1] = [request(class("TestCommandRequest"), &TEST_FIELDS)]; pub(crate) const TEST_ROUTE: Route = routed( "run_test", - ("sqlbuild._native", "run_test_command"), + "run_test_command", &TEST_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -256,13 +235,10 @@ const CHECK_FIELDS: [Field; 9] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const CHECK_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "CheckCommandRequest"), - &CHECK_FIELDS, -)]; +const CHECK_ARGUMENTS: [Field; 1] = [request(class("CheckCommandRequest"), &CHECK_FIELDS)]; pub(crate) const CHECK_ROUTE: Route = routed( "run_check", - ("sqlbuild._native", "run_check_command"), + "run_check_command", &CHECK_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -280,13 +256,10 @@ const AUDIT_FIELDS: [Field; 11] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const AUDIT_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "AuditCommandRequest"), - &AUDIT_FIELDS, -)]; +const AUDIT_ARGUMENTS: [Field; 1] = [request(class("AuditCommandRequest"), &AUDIT_FIELDS)]; pub(crate) const AUDIT_ROUTE: Route = routed( "run_audit", - ("sqlbuild._native", "run_audit_command"), + "run_audit_command", &AUDIT_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -304,13 +277,10 @@ const LOAD_FIELDS: [Field; 11] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const LOAD_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "LoadCommandRequest"), - &LOAD_FIELDS, -)]; +const LOAD_ARGUMENTS: [Field; 1] = [request(class("LoadCommandRequest"), &LOAD_FIELDS)]; pub(crate) const LOAD_ROUTE: Route = routed( "run_load", - ("sqlbuild._native", "run_load_command"), + "run_load_command", &LOAD_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -326,13 +296,10 @@ const SEED_FIELDS: [Field; 9] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const SEED_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "SeedCommandRequest"), - &SEED_FIELDS, -)]; +const SEED_ARGUMENTS: [Field; 1] = [request(class("SeedCommandRequest"), &SEED_FIELDS)]; pub(crate) const SEED_ROUTE: Route = routed( "run_seed", - ("sqlbuild._native", "run_seed_command"), + "run_seed_command", &SEED_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -348,21 +315,15 @@ const LINEAGE_FIELDS: [Field; 11] = [ EXCLUDE, field( "lineage_mode", - Source::Member( - class("sqlbuild._native", "ColumnLineageMode"), - "lineage_mode", - ), + Source::Member(class("ColumnLineageMode"), "lineage_mode"), ), attr("include_uses", "lineage_include_uses"), CLI_VARS, ]; -const LINEAGE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "LineageCommandRequest"), - &LINEAGE_FIELDS, -)]; +const LINEAGE_ARGUMENTS: [Field; 1] = [request(class("LineageCommandRequest"), &LINEAGE_FIELDS)]; pub(crate) const LINEAGE_ROUTE: Route = routed( "run_lineage", - ("sqlbuild._native", "run_lineage_command"), + "run_lineage_command", &LINEAGE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -381,13 +342,10 @@ const CLONE_FIELDS: [Field; 12] = [ JSON_OUTPUT_PATH, attr("event_output_path", "event_output"), ]; -const CLONE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "CloneCommandRequest"), - &CLONE_FIELDS, -)]; +const CLONE_ARGUMENTS: [Field; 1] = [request(class("CloneCommandRequest"), &CLONE_FIELDS)]; pub(crate) const CLONE_ROUTE: Route = routed( "run_clone", - ("sqlbuild._native", "run_clone_command"), + "run_clone_command", &CLONE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -432,13 +390,10 @@ const DIFF_FIELDS: [Field; 35] = [ attr("suppress_example_values", "no_example_values"), same("full_example_values"), ]; -const DIFF_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "DiffCommandRequest"), - &DIFF_FIELDS, -)]; +const DIFF_ARGUMENTS: [Field; 1] = [request(class("DiffCommandRequest"), &DIFF_FIELDS)]; pub(crate) const DIFF_ROUTE: Route = routed( "run_diff", - ("sqlbuild._native", "run_diff_command"), + "run_diff_command", &DIFF_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -453,7 +408,7 @@ const QUERY_ARGUMENTS: [Field; 6] = [ ]; pub(crate) const QUERY_ROUTE: Route = routed( "run_query", - ("sqlbuild._native", "run_query_command"), + "run_query_command", &QUERY_ARGUMENTS, CallShape { slot_positional: 0, @@ -474,13 +429,10 @@ const COST_FIELDS: [Field; 11] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const COST_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "CostCommandRequest"), - &COST_FIELDS, -)]; +const COST_ARGUMENTS: [Field; 1] = [request(class("CostCommandRequest"), &COST_FIELDS)]; pub(crate) const COST_ROUTE: Route = routed( "run_cost", - ("sqlbuild._native", "run_cost_command"), + "run_cost_command", &COST_ARGUMENTS, REQUEST_SHAPE, ); @@ -494,7 +446,7 @@ const DEBUG_ARGUMENTS: [Field; 5] = [ ]; pub(crate) const DEBUG_ROUTE: Route = routed( "run_debug", - ("sqlbuild._native", "run_debug_command"), + "run_debug_command", &DEBUG_ARGUMENTS, CallShape { slot_positional: 0, @@ -515,13 +467,10 @@ const JANITOR_FIELDS: [Field; 8] = [ SELECTED_TARGET, same("as_target"), ]; -const JANITOR_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "JanitorCommandRequest"), - &JANITOR_FIELDS, -)]; +const JANITOR_ARGUMENTS: [Field; 1] = [request(class("JanitorCommandRequest"), &JANITOR_FIELDS)]; pub(crate) const JANITOR_ROUTE: Route = routed( "run_janitor", - ("sqlbuild._native", "run_janitor_command"), + "run_janitor_command", &JANITOR_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -537,15 +486,12 @@ const REFACTOR_FIELDS: [Field; 9] = [ JSON_OUTPUT, NO_COLOR, ]; -const REFACTOR_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "RefactorCommandRequest"), - &REFACTOR_FIELDS, -)]; +const REFACTOR_ARGUMENTS: [Field; 1] = [request(class("RefactorCommandRequest"), &REFACTOR_FIELDS)]; pub(crate) const REFACTOR_ROUTE: Route = Route { unavailable_code: Some("C950"), ..routed( "run_refactor", - ("sqlbuild._native", "run_refactor_command"), + "run_refactor_command", &REFACTOR_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ) @@ -563,15 +509,12 @@ const CONTRACT_FIELDS: [Field; 10] = [ NO_COLOR, CLI_VARS, ]; -const CONTRACT_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ContractCommandRequest"), - &CONTRACT_FIELDS, -)]; +const CONTRACT_ARGUMENTS: [Field; 1] = [request(class("ContractCommandRequest"), &CONTRACT_FIELDS)]; pub(crate) const CONTRACT_ROUTE: Route = Route { unavailable_code: Some("C470"), ..routed( "run_contract", - ("sqlbuild._native", "run_contract_command"), + "run_contract_command", &CONTRACT_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ) @@ -598,27 +541,21 @@ const COMPILE_FIELDS: [Field; 15] = [ NO_COLOR, field( "lineage_mode", - Source::Member( - class("sqlbuild._native", "CompileLineageMode"), - "compile_lineage_mode", - ), + Source::Member(class("CompileLineageMode"), "compile_lineage_mode"), ), SELECT, EXCLUDE, CLI_VARS, field( "profile_flags", - Source::Nested(class(ENTRY_MODELS, "CompileProfileFlags"), &PROFILE_FIELDS), + Source::Nested(class("CompileProfileFlags"), &PROFILE_FIELDS), ), same("debug"), ]; -const COMPILE_ARGUMENTS: [Field; 1] = [request( - class(ENTRY_MODELS, "CompileCommandRequest"), - &COMPILE_FIELDS, -)]; +const COMPILE_ARGUMENTS: [Field; 1] = [request(class("CompileCommandRequest"), &COMPILE_FIELDS)]; pub(crate) const COMPILE_ROUTE: Route = routed( "run_compile", - ("sqlbuild._native", "run_compile"), + "run_compile", &COMPILE_ARGUMENTS, REQUEST_SHAPE, ); @@ -626,7 +563,7 @@ pub(crate) const COMPILE_ROUTE: Route = routed( const INIT_ARGUMENTS: [Field; 1] = [PROJECT_DIR]; pub(crate) const INIT_ROUTE: Route = routed( "run_init", - ("sqlbuild._native", "run_init_command"), + "run_init_command", &INIT_ARGUMENTS, REQUEST_SHAPE, ); @@ -637,12 +574,12 @@ const PLAYGROUND_FIELDS: [Field; 3] = [ attr("template", "playground_template"), ]; const PLAYGROUND_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "PlaygroundCommandRequest"), + class("PlaygroundCommandRequest"), &PLAYGROUND_FIELDS, )]; pub(crate) const PLAYGROUND_ROUTE: Route = routed( "run_playground", - ("sqlbuild._native", "run_playground_command"), + "run_playground_command", &PLAYGROUND_ARGUMENTS, REQUEST_SHAPE, ); @@ -655,7 +592,7 @@ const SKILLS_ARGUMENTS: [Field; 4] = [ ]; pub(crate) const SKILLS_ROUTE: Route = routed( "run_skills_update", - ("sqlbuild._native", "run_skills_command"), + "run_skills_command", &SKILLS_ARGUMENTS, CallShape { slot_positional: 0, @@ -676,7 +613,7 @@ const FORMAT_ARGUMENTS: [Field; 9] = [ ]; pub(crate) const FORMAT_ROUTE: Route = routed( "run_format", - ("sqlbuild._native", "run_format_command"), + "run_format_command", &FORMAT_ARGUMENTS, CallShape { slot_positional: 1, @@ -693,13 +630,10 @@ const RULES_FIELDS: [Field; 7] = [ field("select", Source::TupleOf("select")), EXCLUDE, ]; -const RULES_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "RulesCommandRequest"), - &RULES_FIELDS, -)]; +const RULES_ARGUMENTS: [Field; 1] = [request(class("RulesCommandRequest"), &RULES_FIELDS)]; pub(crate) const RULES_ROUTE: Route = routed( "run_rules", - ("sqlbuild._native", "run_rules_command"), + "run_rules_command", &RULES_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -715,13 +649,10 @@ const DBT_INIT_FIELDS: [Field; 9] = [ same("overwrite"), same("skip_dbt_debug"), ]; -const DBT_INIT_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "DbtInitCommandRequest"), - &DBT_INIT_FIELDS, -)]; +const DBT_INIT_ARGUMENTS: [Field; 1] = [request(class("DbtInitCommandRequest"), &DBT_INIT_FIELDS)]; pub(crate) const DBT_INIT_ROUTE: Route = routed( "run_dbt_init", - ("sqlbuild._native", "run_dbt_init_command"), + "run_dbt_init_command", &DBT_INIT_ARGUMENTS, REQUEST_SHAPE, ); @@ -731,7 +662,7 @@ const DBT_ARGUMENTS: [Field; 3] = [ field("args", Source::TupleOf("dbt_args")), NO_COLOR, ]; -const DBT_COMMAND: ClassRef = class("sqlbuild._native", "DbtInteropCommand"); +const DBT_COMMAND: ClassRef = class("DbtInteropCommand"); pub(crate) const DBT_PLAN_ROUTE: Route = dbt_route("run_dbt_plan", "plan", &DBT_ARGUMENTS, DBT_COMMAND); @@ -769,12 +700,12 @@ const SCENARIO_TEST_FIELDS: [Field; 15] = [ JSON_OUTPUT_PATH, ]; const SCENARIO_TEST_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ScenarioTestCommandRequest"), + class("ScenarioTestCommandRequest"), &SCENARIO_TEST_FIELDS, )]; pub(crate) const SCENARIO_TEST_ROUTE: Route = routed( "run_scenario", - ("sqlbuild._native", "run_scenario_test_command"), + "run_scenario_test_command", &SCENARIO_TEST_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -790,12 +721,12 @@ const SCENARIO_CAPTURE_FIELDS: [Field; 8] = [ SNAPSHOT_LIMITS, ]; const SCENARIO_CAPTURE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ScenarioCaptureCommandRequest"), + class("ScenarioCaptureCommandRequest"), &SCENARIO_CAPTURE_FIELDS, )]; pub(crate) const SCENARIO_CAPTURE_ROUTE: Route = routed( "run_scenario_capture", - ("sqlbuild._native", "run_scenario_capture_command"), + "run_scenario_capture_command", &SCENARIO_CAPTURE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); diff --git a/crates/sqlbuild-cli/src/dispatch/models.rs b/crates/sqlbuild-cli/src/dispatch/models.rs index 87230c9bf4..9e22a922ce 100644 --- a/crates/sqlbuild-cli/src/dispatch/models.rs +++ b/crates/sqlbuild-cli/src/dispatch/models.rs @@ -1,9 +1,8 @@ //! How one parsed `sqb` invocation becomes a call of its Python command handler. -/// A Python class by module path and name. +/// A class `sqlbuild._native` registers, by name. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ClassRef { - pub module: &'static str, pub name: &'static str, } @@ -74,8 +73,8 @@ pub struct CallShape { pub struct Route { /// The `CliEntrypointHandlers` attribute tests may inject. pub slot: &'static str, - /// The command implementation: module path and function name. - pub implementation: (&'static str, &'static str), + /// The name of the native function implementing the command. + pub implementation: &'static str, pub arguments: &'static [Field], pub shape: CallShape, /// A keyword the implementation alone receives, such as the dbt command. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs index e9b5ecf5ec..d14c6b8b17 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs @@ -11,11 +11,11 @@ use sqlbuild_analysis::semantic_validation::types::{BindingRequest, DiagnosticRo use crate::bindings::_helpers::analysis::conversions; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::{CatalogInput, ProjectCatalog}; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - /// Dialects whose quoted identifiers bind case-sensitively unless the session says otherwise. pub(crate) const CASE_SENSITIVE_BINDING_DIALECTS: [&str; 2] = ["snowflake", "postgresql"]; /// Adapter dialect names and the native dialect each validates as. @@ -322,12 +322,8 @@ pub(crate) fn schema_validations<'py>( None => groups.push((catalog, own, vec![index])), } } - let result_type: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("SqlBindingResult")?; - let diagnostic_type: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("SqlBindingDiagnostic")?; + let result_type: Bound<'py, PyAny> = registry::class(py, RecordId::SqlBindingResult)?; + let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::SqlBindingDiagnostic)?; let empty: Bound<'py, PyAny> = result_type.call0()?; let mut results: Vec> = vec![empty; requests.len()]; for (catalog, any_explicit, members) in groups { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs index c9b585108b..47d9c379c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs @@ -1,6 +1,5 @@ //! Compile's explicit-reference checks over plain project facts, run natively. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -27,17 +26,14 @@ use sqlbuild_pysource::literal_sql::models::FunctionSource; use crate::bindings::_helpers::analysis::reference_facts::{ProjectFacts, project_facts}; use crate::bindings::_helpers::boundary::panics::compiler_error; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; const EXPLICIT_REFERENCES_CONTEXT: &str = "native explicit-reference checks"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const REFERENCE_MODELS_MODULE: &str = "sqlbuild.compiler.references.models"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; -const LOADER_DEFINITION_MODULE: &str = "sqlbuild.python_nodes.main.read_loader_definition"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const BUILTINS_MODULE: &str = "builtins"; const VALUE_PLACEHOLDER: &str = "__sqlbuild_python_value_"; const HARD_CODED_ALLOWED: &str = "hard-coded relation names"; @@ -142,9 +138,7 @@ fn explicit_reference_report<'py>( { Ok(report) => python_report(py, &report, &owners), Err(ReferenceFailure::CompileInput(message)) => Err(PyErr::from_value( - py.import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )), Err(ReferenceFailure::UnknownDialect(message)) => Err(compiler_error(message)), Err(ReferenceFailure::Internal(reason)) => Err(compiler_error(native_failure( @@ -173,7 +167,7 @@ fn python_sql_owners<'py>( } } owners.extend(loader_owners(project, discovered_inputs)?); - let hook_names = imported(py, NATIVE_MODULE, "model_python_hook_names")?; + let hook_names = registry::native(py, NativeAttr::ModelPythonHookNames)?; let mut attached: BTreeMap> = BTreeMap::new(); for model in project.getattr("models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; @@ -222,7 +216,7 @@ fn loader_owners<'py>( .insert(source.getattr("name")?.extract()?); } } - let read_definition = imported(py, LOADER_DEFINITION_MODULE, "read_loader_definition")?; + let read_definition = host::entry(py, UserCodeHost::ReadLoaderDefinition)?; let mut owners: Vec> = Vec::new(); for loader in discovered_inputs.getattr("loader_functions")?.try_iter()? { let loader: Bound<'py, PyAny> = loader?; @@ -314,15 +308,11 @@ fn python_report<'py>( report: &ReferenceReport, owners: &[PythonSqlOwner<'py>], ) -> PyResult> { - let owner_kind: Bound<'py, PyAny> = - imported(py, REFERENCE_TYPES_MODULE, "HardCodedRelationOwnerKind")?; - let compile_models = py.import(COMPILE_MODELS_MODULE)?; + let owner_kind: Bound<'py, PyAny> = registry::class(py, RecordId::HardCodedRelationOwnerKind)?; let reference_models = py.import(REFERENCE_MODELS_MODULE)?; - let source_location: Bound<'py, PyAny> = py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?; + let source_location: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let path_class: Bound<'py, PyAny> = py.import("pathlib")?.getattr("Path")?; - let compiler_diagnostic: Bound<'py, PyAny> = compile_models.getattr("CompilerDiagnostic")?; + let compiler_diagnostic: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let mut diagnostics: Vec> = Vec::with_capacity(report.diagnostics.len()); for row in &report.diagnostics { let fields: Bound<'py, PyDict> = PyDict::new(py); @@ -350,7 +340,7 @@ fn python_report<'py>( diagnostics.push(compiler_diagnostic.call((), Some(&fields))?); } let literal_relation: Bound<'py, PyAny> = - shape_class(reference_models.as_any(), "LiteralSqlRelation")?; + registry::class(reference_models.py(), RecordId::LiteralSqlRelation)?; let relation_name: Bound<'py, PyAny> = reference_models.getattr("RelationName")?; let mut unmatched: Vec> = Vec::with_capacity(report.unmatched.len()); for row in &report.unmatched { @@ -371,7 +361,7 @@ fn python_report<'py>( let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item("diagnostics", PyTuple::new(py, diagnostics)?)?; fields.set_item("unmatched", PyTuple::new(py, unmatched)?)?; - shape_class(compile_models.as_any(), "PythonSqlReferenceReport")?.call((), Some(&fields)) + registry::class(py, RecordId::PythonSqlReferenceReport)?.call((), Some(&fields)) } /// `(method, sql, line)` for each literal SQL call to the context in a dedented function source. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs index 81e054e539..85006788c8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs @@ -1,5 +1,7 @@ //! The explicit-reference facts of a compiled project, read from its Python views. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyList, PyTuple}; use sqlbuild_analysis::explicit_references::models::{ @@ -8,9 +10,6 @@ use sqlbuild_analysis::explicit_references::models::{ }; use sqlbuild_analysis::explicit_references::types::ResourceKey; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const PYTHON_HOOK_ENTRY: &str = "PythonHookEntry"; -const SQL_HOOK_ENTRY: &str = "SqlHookEntry"; const MODEL_HOOK_KEYS: [&str; 2] = ["pre_hooks", "post_hooks"]; const MIGRATE_FROM_KEY: &str = "migrate_from"; const OLD_NAME_VIEW_KEY: &str = "old_name_view"; @@ -107,10 +106,9 @@ struct HookEntries<'py> { impl<'py> HookEntries<'py> { fn imported(py: Python<'py>) -> PyResult { - let module = py.import(DISCOVERY_MODELS_MODULE)?; Ok(Self { - python: module.getattr(PYTHON_HOOK_ENTRY)?, - sql: module.getattr(SQL_HOOK_ENTRY)?, + python: registry::class(py, RecordId::PythonHookEntry)?, + sql: registry::class(py, RecordId::SqlHookEntry)?, }) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs index 99bd354073..6a4870fc49 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs @@ -9,11 +9,11 @@ use sqlbuild_analysis::required_columns::main::resolve_required_columns::resolve use sqlbuild_analysis::required_columns::models::{ColumnRead, ReferenceInput}; use crate::bindings::_helpers::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; - /// CTE-aware column reads of `query_sql`, resolved to the external resources it references. #[pyfunction] #[pyo3(signature = (*, query_sql, references, dialect))] @@ -40,7 +40,7 @@ fn analyze_resolved_column_reads<'py>( } RequiredColumnsError::Normalization(message) => value_error(message), })?; - let fact: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledLineageSourceFact")?; + let fact: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; let mut facts: Vec> = Vec::with_capacity(reads.len()); for read in reads { facts.push(call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs index cd70197f7c..13d49e2f2b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs @@ -8,10 +8,10 @@ use sqlbuild_analysis::semantic_validation::main::normalize::normalize_analysis_ use sqlbuild_analysis::semantic_validation::models::NormalizationInput; use crate::bindings::_helpers::boundary::panics::value_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - const GENERIC_DIALECT: &str = "generic"; /// The query, relation schemas and binding context of one schema-validation request. @@ -61,7 +61,5 @@ pub(crate) fn schema_validation_request<'py>( parts.quoted_identifiers_ignore_case, )?; arguments.set_item("catalog", parts.catalog)?; - py.import(NATIVE_RECORDS_MODULE)? - .getattr("SqlSchemaValidationRequest")? - .call((), Some(&arguments)) + registry::class(py, RecordId::SqlSchemaValidationRequest)?.call((), Some(&arguments)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs index 1c6dfec894..79386119f1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs @@ -14,12 +14,10 @@ use crate::bindings::_helpers::analysis_session::query_columns::ColumnsRow; use crate::bindings::_helpers::analysis_session::stage_functions::query_column_rows; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const PROJECTION_FACTS_CONTEXT: &str = "native fixture projection facts"; const LEGACY_MODE: &str = "legacy"; @@ -38,10 +36,8 @@ fn infer_fixture_column_facts<'py>( query.set_item("sql", &query_sql)?; query.set_item("mode", LEGACY_MODE)?; query.set_item("recover_cte_facts", false)?; - let query: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeColumnQuery")? - .call((), Some(&query))?; + let query: Bound<'py, PyAny> = + registry::class(py, RecordId::NativeColumnQuery)?.call((), Some(&query))?; let empty: Bound<'py, PyAny> = PyDict::new(py).into_any(); let rows: Vec = query_column_rows( PyTuple::new(py, [query])?.as_any(), @@ -56,9 +52,7 @@ fn infer_fixture_column_facts<'py>( .compiler_detach(|| projection_facts(&query_sql, dialect.as_deref())) .map_err(|reason| compiler_error(native_failure(PROJECTION_FACTS_CONTEXT, &reason)))?; let simple_types: HashMap = facts.types.into_iter().collect(); - let column_class: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; let nullability_class: Bound<'py, PyAny> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; @@ -80,9 +74,7 @@ fn infer_fixture_column_facts<'py>( PyFrozenSet::new(py, facts.null_literal_names)?, )?; arguments.set_item("quoted_names", PyFrozenSet::new(py, facts.quoted_names)?)?; - py.import(NATIVE_RECORDS_MODULE)? - .getattr("FixtureColumnInference")? - .call((), Some(&arguments)) + registry::class(py, RecordId::FixtureColumnInference)?.call((), Some(&arguments)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs index 0db81c84a5..ff98e042bf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs @@ -1,6 +1,8 @@ //! Fixture column inference computed once per fixture body and inference profile. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyclass, pymethods}; @@ -31,7 +33,7 @@ impl FixtureColumnInferences { let py = query_sql.py(); let compute = || { call_with( - &imported(py, "sqlbuild._native", "infer_fixture_column_facts")?, + ®istry::native(py, NativeAttr::InferFixtureColumnFacts)?, &[ ("query_sql", query_sql.clone()), ("inference_profile", inference_profile.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs index 73b4c55997..0253298b28 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs @@ -13,13 +13,13 @@ use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_s use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::analysis_cache::record_analysis_cache_metrics; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::shapes::{VALID_FROM_DEFAULT, VALID_TO_DEFAULT}; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const LOG_DEBUG_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const LOGGING_MODULE: &str = "logging"; const DEBUG_LOGGER: &str = "sqlbuild.compile"; const ANALYSIS_FAILURE_MESSAGE: &str = "native compact query analysis failed"; @@ -36,9 +36,7 @@ fn analyze_native_model_sql<'py>( let request = Request::new("analyze_native_model_sql", request)?; let py: Python<'py> = request.py(); let request: Bound<'py, PyAny> = request.get("request")?; - let analyses_class: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeModelAnalyses")?; + let analyses_class: Bound<'py, PyAny> = registry::class(py, RecordId::NativeModelAnalyses)?; let model_inputs: Bound<'py, PyAny> = request.getattr("model_inputs")?; if !model_inputs.is_truthy()? { let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -66,10 +64,13 @@ fn analyze_native_model_sql<'py>( ), None => (analysis_store(&request)?, py.None().into_bound(py)), }; - let session: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("start_model_analysis_session")? - .call1((catalog.getattr("native")?, session_request, cache, adapter))?; + let session: Bound<'py, PyAny> = registry::native(py, NativeAttr::StartModelAnalysisSession)? + .call1(( + catalog.getattr("native")?, + session_request, + cache, + adapter, + ))?; run_session(&request, (&python, &catalog), &session, &analyses_class) } @@ -130,10 +131,8 @@ fn analysis_store<'py>(request: &Bound<'py, PyAny>) -> PyResult = py - .import(NATIVE_MODULE)? - .getattr("content_digest")? - .call1((parts,))?; + let environment: Bound<'py, PyAny> = + registry::native(py, NativeAttr::ContentDigest)?.call1((parts,))?; Ok(PyTuple::new(py, [path, environment])?.into_any()) } @@ -270,11 +269,10 @@ fn model_rows<'py>( py: Python<'py>, model_inputs: &[Bound<'py, PyAny>], ) -> PyResult> { - let planner = py.import(PLANNER_TYPES_MODULE)?; - let enforced: Bound<'py, PyAny> = planner.getattr("ContractPolicy")?.getattr("ENFORCED")?; - let snapshot: Bound<'py, PyAny> = planner - .getattr("MaterializationType")? - .getattr("SNAPSHOT")?; + let enforced: Bound<'py, PyAny> = + registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; + let snapshot: Bound<'py, PyAny> = + registry::class(py, RecordId::MaterializationType)?.getattr("SNAPSHOT")?; let mut rows: Vec> = Vec::with_capacity(model_inputs.len()); for model_input in model_inputs { let values: Bound<'py, PyAny> = model_input.getattr("config")?.getattr("values")?; @@ -355,9 +353,7 @@ fn log_debug(py: Python<'_>, message: &str, failure: &Bound<'_, PyAny>) -> PyRes )?; arguments.set_item("message", message)?; arguments.set_item("sqlbuild_error", failure)?; - py.import(LOG_DEBUG_MODULE)? - .getattr("log_debug_event")? - .call((), Some(&arguments))?; + host::entry(py, ExecutorHost::LogDebugEvent)?.call((), Some(&arguments))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs index 9769df9baa..cd67a2362c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs @@ -1,6 +1,5 @@ //! Finished session outcomes and pivot proof rows as Python's analysis objects. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::IntoPyObjectExt; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -10,10 +9,9 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::record_behaviours::lineage_facts::{ confidence_member, transform_kind_member, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const LINEAGE_NATIVE: &str = "native"; @@ -53,7 +51,6 @@ fn session_outcome_analysis<'py>( .call_method1("outcome_rows", (request.get("position")?,))? .extract()?; let (succeeded, columns, source, rows, has_star, star_resolved, raw, validated, cleaned): OutcomeObjects<'py> = outcome.extract()?; - let models = py.import(COMPILE_MODELS_MODULE)?; let lineage: Bound<'py, PyAny> = if source != LINEAGE_NATIVE { lineage_facts(&rows)? } else if succeeded.is_truthy()? { @@ -61,9 +58,7 @@ fn session_outcome_analysis<'py>( } else { PyTuple::empty(py).into_any() }; - let binding_class: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("SqlBindingDiagnostic")?; + let binding_class: Bound<'py, PyAny> = registry::class(py, RecordId::SqlBindingDiagnostic)?; let mut bindings: Vec> = Vec::new(); for row in raw.try_iter()? { let row: Bound<'py, PyTuple> = @@ -71,7 +66,7 @@ fn session_outcome_analysis<'py>( bindings.push(binding_class.call1(row)?); } let result: Bound<'py, PyAny> = call( - &shape_class(models.as_any(), "PolyglotAnalysisResult")?, + ®istry::class(py, RecordId::PolyglotAnalysisResult)?, &[ ("analysis_succeeded", succeeded), ("columns", inferred_columns(&columns)?), @@ -86,7 +81,7 @@ fn session_outcome_analysis<'py>( ], )?; call( - &shape_class(models.as_any(), "ModelSqlAnalysis")?, + ®istry::class(py, RecordId::ModelSqlAnalysis)?, &[ ("polyglot_analysis", result), ("placeholders", request.get("placeholders")?), @@ -110,8 +105,7 @@ fn proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { } let (output_proven, columns, families, inputs, failure_reason, bare): ProofObjects<'py> = row.extract()?; - let models = py.import(COMPILE_MODELS_MODULE)?; - let family_class: Bound<'py, PyAny> = shape_class(models.as_any(), "DynamicColumnFamilyProof")?; + let family_class: Bound<'py, PyAny> = registry::class(py, RecordId::DynamicColumnFamilyProof)?; let mut proofs: Vec> = Vec::new(); for family in families.try_iter()? { let (name, inferred_type): (Bound<'py, PyAny>, Bound<'py, PyAny>) = family?.extract()?; @@ -127,7 +121,7 @@ fn proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { PyTuple::empty(py).into_any() }; call( - &shape_class(models.as_any(), "DynamicColumnContractProof")?, + ®istry::class(py, RecordId::DynamicColumnContractProof)?, &[ ("output_proven", output_proven), ("fixed_columns", fixed), @@ -145,9 +139,7 @@ fn inferred_columns<'py>(rows: &Bound<'py, PyAny>) -> PyResult if rows.is_none() { return Ok(rows.clone()); } - let column_class: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; let nullability_class: Bound<'py, PyAny> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; @@ -220,8 +212,7 @@ fn compact_lineage<'py>(rows: &Bound<'py, PyAny>) -> PyResult> } let keys: Bound<'py, PyAny> = PyTuple::new(py, pool.keys())?.into_any(); call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompactLineageFacts")?, + ®istry::class(py, RecordId::CompactLineageFacts)?, &[ ("string_pool", keys), ("rows", PyTuple::new(py, compact)?.into_any()), @@ -232,10 +223,8 @@ fn compact_lineage<'py>(rows: &Bound<'py, PyAny>) -> PyResult> /// Native lineage rows as the plain facts Python's re-analysis returns. fn lineage_facts<'py>(rows: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = rows.py(); - let models = py.import(COMPILE_MODELS_MODULE)?; - let column_class: Bound<'py, PyAny> = - shape_class(models.as_any(), "CompiledLineageColumnFact")?; - let source_class: Bound<'py, PyAny> = models.getattr("CompiledLineageSourceFact")?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageColumnFact)?; + let source_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; let mut facts: Vec> = Vec::new(); for row in rows.try_iter()? { let (output_column, transform_code, confidence_code, sources): ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs index ec8c4e31ba..2115c45c4f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs @@ -7,27 +7,20 @@ use sqlbuild_analysis::assembly::analysis_session::types::{Pairs, Shapes}; use crate::bindings::_helpers::analysis::binding_catalog::{ BindingCatalog, CASE_SENSITIVE_BINDING_DIALECTS, NATIVE_DIALECT_ALIASES, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const GENERIC_DIALECT: &str = "generic"; const ADAPTER_RULE_ID: &str = "python"; -/// `(module, function, native rule id)` of the nullability rules the native analysis knows. -const KNOWN_RULES: [(&str, &str, &str); 3] = [ +/// The nullability rules the native analysis knows, with their native rule ids. +const KNOWN_RULES: [(AdapterHost, &str); 3] = [ + (AdapterHost::FirstArgNullability, "first_arg"), ( - "sqlbuild.adapter.type_system.main.first_arg_nullability", - "first_arg_nullability", - "first_arg", - ), - ( - "sqlbuild.adapter.type_system.main.conditional_result_nullability", - "conditional_result_nullability", + AdapterHost::ConditionalResultNullability, "conditional_result", ), - ( - "sqlbuild.adapter.type_system.main.safe_cast_nullability", - "safe_cast_nullability", - "safe_cast", - ), + (AdapterHost::SafeCastNullability, "safe_cast"), ]; /// The adapter's own nullability rules and `InferredNullability`, which native analysis calls. @@ -37,8 +30,8 @@ pub(crate) type AdapterRules = Option<(Py, Py)>; pub(crate) fn nullability_rules(profile: &Bound<'_, PyAny>) -> PyResult<(Pairs, AdapterRules)> { let py: Python<'_> = profile.py(); let known: Bound<'_, PyDict> = PyDict::new(py); - for (module, function, id) in KNOWN_RULES { - known.set_item(py.import(module)?.getattr(function)?, id)?; + for (rule, id) in KNOWN_RULES { + known.set_item(host::entry(py, rule)?, id)?; } let adapter: Bound<'_, PyDict> = PyDict::new(py); let mut rows: Pairs = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs index 3635f6b608..ba26c4d945 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs @@ -20,6 +20,8 @@ use crate::bindings::_helpers::analysis_session::session::{ infer_expression_source_shapes, prove_dynamic_column_contracts, }; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; const BATCH_MODE: &str = "batch"; @@ -32,8 +34,6 @@ const LINEAGE_RESOURCE_TYPES: [(&str, &str); 4] = [ ("seed", "seed"), ("table_fn", "table_fn"), ]; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; /// Each query's `(succeeded, column rows, has star)`; a native internal failure raises. @@ -70,12 +70,8 @@ fn infer_native_query_columns<'py>( &request.get("profile")?, (&types, &nullability), )?; - let query_columns: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeQueryColumns")?; - let column_class: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let query_columns: Bound<'py, PyAny> = registry::class(py, RecordId::NativeQueryColumns)?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; let nullability_class: Bound<'py, PyAny> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs b/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs index 262ad5285a..158acd1818 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs @@ -8,9 +8,9 @@ use sqlbuild_attachments::cursor_intrinsics::main::validated_model_intrinsics::v use sqlbuild_attachments::cursor_intrinsics::models::IntrinsicModel; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const INCREMENTAL_MATERIALIZATION: &str = "incremental"; const CURSOR_START: &str = "__cursor_start"; const CURSOR_START_CALL: &str = "__cursor_start()"; @@ -37,7 +37,7 @@ pub(crate) fn reserved_cursor_markers() -> Vec { /// A `CompileInputError` carrying `message`. pub(crate) fn compile_input_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs b/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs index 849c6f09cf..f259791419 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs @@ -8,8 +8,9 @@ use sqlbuild_attachments::reference_markers::main::unresolved_reference_marker:: use sqlbuild_attachments::sql_lexing::main::unclosed_message::unclosed_message; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; /// The reference function and diagnostic code of each marker kind, in the scanner's order. const MARKER_KINDS: [(&str, &str); 5] = [ ("__ref", "R001"), @@ -45,9 +46,7 @@ fn assert_no_unresolved_sql_markers( })?; match outcome { (_, Some(unclosed)) => Err(PyErr::from_value( - py.import(COMPILE_ERRORS_MODULE)? - .getattr("CompileInputError")? - .call1((unclosed,))?, + registry::error(py, ErrorId::CompileInput)?.call1((unclosed,))?, )), (Some(marker), None) => { let (function, code) = MARKER_KINDS.get(marker).copied().ok_or_else(|| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs index b70e9eaabe..1dc77c5478 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -12,17 +12,12 @@ use sqlbuild_core::audit_command::models::AuditCounts; use sqlbuild_core::cli_text::models::CliStyle; use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_command_invocation; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const AUDIT_PLAN_MODULE: &str = "sqlbuild._native"; -const COST_CONTEXT_MODULE: &str = "sqlbuild._native"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; -const PROJECTION_MODULE: &str = "sqlbuild.executor.auditing.main._current_audit_result_projection"; const EXECUTION_COMMAND: &str = "sqb audit"; const PASS_OUTCOME: &str = "pass"; const WARN_OUTCOME: &str = "warn"; @@ -109,18 +104,14 @@ fn compile_audits<'py>( let discovered: Bound<'py, PyAny> = invocation.getattr("discovered_inputs")?; let adapter: Bound<'py, PyAny> = invocation.getattr("adapter")?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -136,7 +127,7 @@ fn compile_audits<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -150,7 +141,7 @@ fn compile_audits<'py>( ], )?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, AUDIT_PLAN_MODULE, "build_audit_command_plan")?, + ®istry::native(py, NativeAttr::BuildAuditCommandPlan)?, &[ ("project", context.getattr("project")?), ("adapter", adapter), @@ -159,7 +150,7 @@ fn compile_audits<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -195,7 +186,7 @@ fn prepare_run<'py>( let stream: Bound<'py, PyAny> = invocation.getattr("progress_stream")?; let use_color: Bound<'py, PyAny> = invocation.getattr("use_color")?; let progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "AuditProgressReporter")?, + ®istry::native(py, NativeAttr::AuditProgressReporter)?, &[ ("entries", entries.clone()), ("worker_limit", PyInt::new(py, worker_count).into_any()), @@ -204,7 +195,7 @@ fn prepare_run<'py>( ], )?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { for entry in entries.try_iter()? { call_with( @@ -214,7 +205,7 @@ fn prepare_run<'py>( } } let connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream), @@ -245,7 +236,7 @@ fn write_headers( header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", &use_color)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&header))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; let entries: Bound<'_, PyAny> = pipeline.getattr("plan_output")?.getattr("audit_entries")?; let mut models: BTreeSet = BTreeSet::new(); for entry in entries.try_iter()? { @@ -275,7 +266,7 @@ fn execute<'py>( let plan: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let project: Bound<'py, PyAny> = pipeline.getattr("project")?; let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let relay: Bound<'py, AuditCallbackRelay> = Bound::new( py, AuditCallbackRelay { @@ -288,7 +279,7 @@ fn execute<'py>( run.progress .call_method1("set_result_callback", (relay.getattr("on_result")?,))?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { let ordinals: Bound<'py, PyDict> = PyDict::new(py); let mut total: usize = 0; @@ -312,7 +303,7 @@ fn execute<'py>( .call_method1("__truediv__", (&run_id,))? .call_method1("__truediv__", ("statements.jsonl",))?; let scope: Bound<'py, PyAny> = call_with( - &imported(py, COST_CONTEXT_MODULE, "CostContext")?.getattr("scope")?, + ®istry::native(py, NativeAttr::CostContext)?.getattr("scope")?, &[ ("run_id", run_id.clone()), ("resource_type", text(py, "run")), @@ -323,7 +314,7 @@ fn execute<'py>( )?; within(&scope, |_| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_RUN_MODULE, "AuditPipelineCallbacks")?, + &host::entry(py, ExecutorHost::AuditPipelineCallbacks)?, &[ ( "on_connection_start", @@ -347,7 +338,7 @@ fn execute<'py>( ], )?; call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_audit_pipeline_with_callbacks")?, + &host::entry(py, ExecutorHost::RunAuditPipelineWithCallbacks)?, &[ ("plan", plan.clone()), ( @@ -390,7 +381,7 @@ fn audit_payload<'py>( ) -> PyResult> { let py: Python<'py> = results.py(); let payload: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_audit_execution_json")?, + ®istry::native(py, NativeAttr::FormatAuditExecutionJson)?, &[ ("results", results.clone()), ( @@ -401,7 +392,7 @@ fn audit_payload<'py>( ], )?; let projection: Bound<'py, PyAny> = - imported(py, PROJECTION_MODULE, "current_audit_result_projection")?.call0()?; + host::entry(py, ExecutorHost::CurrentAuditResultProjection)?.call0()?; if projection.is_none() { return Ok(payload); } @@ -468,7 +459,7 @@ fn complete( (run.concurrency, run.worker_count), )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -484,7 +475,7 @@ fn complete( fn run_audit_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - &imported(py, NATIVE_MODULE, "AuditInvocation")?, + ®istry::class(py, RecordId::AuditInvocation)?, true, )?; let pipeline: Bound<'_, PyAny> = compile_audits(request, &invocation)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs b/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs index 2bdbaebbfb..c1c762345f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs @@ -1,5 +1,7 @@ //! Audit gate identities of planned audits, built as `AuditGateIdentity` objects. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -8,7 +10,6 @@ use sqlbuild_analysis::auditing::models::{ GateIdentity, MeasurementFacts, PlannedAudit, ThresholdBound, Thresholds, }; -const AUDITING_MODELS_MODULE: &str = "sqlbuild._native"; const MEASUREMENT_MODE: &str = "measurement"; fn enum_value(value: &Bound<'_, PyAny>) -> PyResult { @@ -92,8 +93,7 @@ fn build_audit_gate_identity<'py>( let entries: Vec> = audits.try_iter()?.collect::>()?; let planned: Vec = entries.iter().map(planned_audit).collect::>()?; let gate: GateIdentity = audit_gate_identity(&planned); - let models: Bound<'py, PyModule> = py.import(AUDITING_MODELS_MODULE)?; - let identity_type: Bound<'py, PyAny> = models.getattr("AuditIdentity")?; + let identity_type: Bound<'py, PyAny> = registry::class(py, RecordId::AuditIdentity)?; let mut identities: Vec> = Vec::with_capacity(gate.audits.len()); for identity in gate.audits { let entry: &Bound<'py, PyAny> = &entries[identity.index]; @@ -138,7 +138,7 @@ fn build_audit_gate_identity<'py>( )?); } keyword_call( - &models.getattr("AuditGateIdentity")?, + ®istry::class(py, RecordId::AuditGateIdentity)?, vec![ ( "binding_set_hash", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs b/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs index 30abd51eec..cd764d371c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs @@ -8,16 +8,13 @@ use sqlbuild_analysis::auditing::main::evaluate_measurement::evaluate_measuremen use sqlbuild_analysis::auditing::models::{MeasurementOutcome, Thresholds}; use crate::bindings::_helpers::auditing::identity::thresholds as read_thresholds; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const AUDITING_ERRORS_MODULE: &str = "sqlbuild._native"; fn measurement_error(py: Python<'_>, message: &str) -> PyErr { - match py - .import(AUDITING_ERRORS_MODULE) - .and_then(|module| module.getattr("MeasurementAuditError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::MeasurementAudit).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs b/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs index a139607174..0504011309 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs @@ -13,8 +13,8 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::contract_classes::contract_classes; use crate::bindings::_helpers::model_config::header_metadata::{ContractClasses, audit_tuple}; - -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// A resource identity error as `ResourceIdentityError`, anything else as `error_class(message)`. fn raised(py: Python<'_>, error: ConfigError, error_class: &Bound<'_, PyAny>) -> PyResult { @@ -24,8 +24,7 @@ fn raised(py: Python<'_>, error: ConfigError, error_class: &Bound<'_, PyAny>) -> if let Some(help) = error.help { keywords.set_item("help", help)?; } - py.import(IDENTITY_ERRORS_MODULE)? - .getattr("ResourceIdentityError")? + registry::error(py, ErrorId::ResourceIdentity)? } _ => error_class.clone(), }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs b/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs index 9a09f3ab30..55bdbb7ed1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs @@ -13,11 +13,11 @@ use sqlbuild_core::identifier_text::main::is_lowercase_identifier::is_lowercase_ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; const ERRORS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; const SHARED_INPUT_ERROR: &str = "SharedInputError"; -const MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const RESOURCE_REF_CLASS: &str = "SqlResourceRef"; const RANDOM_MODULE: &str = "random"; const MAX_ATTEMPTS_TOO_LOW: &str = "RetryPolicy max_attempts must be at least 1"; const INITIAL_DELAY_NEGATIVE: &str = "RetryPolicy initial_delay_seconds must be non-negative"; @@ -177,7 +177,7 @@ fn validate_audit_case( #[pyfunction] fn normalize_hook_reads<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); - let reference: Bound<'py, PyAny> = imported(py, MODELS_MODULE, RESOURCE_REF_CLASS)?; + let reference: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let reads: Vec> = if value.is_instance(&reference)? { vec![value.clone()] } else { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs index 9069c440bf..cdcbc0c17a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs @@ -15,17 +15,17 @@ use crate::bindings::_helpers::build_command::planning::{ compile_build_plan, enforce_build_plan_policies, run_defer_clone_prephase, write_compile_target, write_plan_text, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; - -const TIMING_TRACKER_MODULE: &str = "sqlbuild._native"; -const RESOURCE_REPORTING_MODULE: &str = "sqlbuild.diagnostics.main.process_resource_reporting"; -const DEADLINE_MODULE: &str = "sqlbuild.runtime.execution_limits.main.execution_deadline_scope"; -const PROVIDER_SESSION_MODULE: &str = "sqlbuild.provider.main.session"; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; fn run_with_providers(context: &BuildContext<'_>) -> PyResult { let py: Python<'_> = context.py(); let session: Bound<'_, PyAny> = call_with( - &imported(py, PROVIDER_SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[( "discovered_providers", context.get("discovered_inputs")?.getattr("providers")?, @@ -71,7 +71,7 @@ fn run_within_limits( let context: BuildContext<'_> = resolve_build_context(request, tracker, started)?; let limits: Bound<'_, PyAny> = context.get("execution_limits")?; let deadline: Bound<'_, PyAny> = call_with( - &imported(py, DEADLINE_MODULE, "execution_deadline_scope")?, + &host::entry(py, ExecutorHost::ExecutionDeadlineScope)?, &[ ( "max_duration_seconds", @@ -90,9 +90,9 @@ fn run_within_limits( #[pyo3(signature = (*, request))] fn run_build_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let tracker: Bound<'_, PyAny> = - imported(py, TIMING_TRACKER_MODULE, "BuildPhaseTimingTracker")?.call0()?; + registry::native(py, NativeAttr::BuildPhaseTimingTracker)?.call0()?; let reporting: Bound<'_, PyAny> = call_with( - &imported(py, RESOURCE_REPORTING_MODULE, "process_resource_reporting")?, + &host::entry(py, ExecutorHost::ProcessResourceReporting)?, &[("enabled", request.getattr("debug")?)], )?; within(&reporting, |_| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs index 2800f33c32..a93c335b2f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs @@ -17,18 +17,18 @@ use crate::bindings::_helpers::build_command::invocation::BuildContext; use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, none, text, utc_now, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_outputs::runtime_target::{ write_python_check_runtime_target, write_runtime_target, }; use crate::bindings::_helpers::cost_report::degradation::{ finalize_build_cost as finalize_cost, render_build_cost, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COST_TYPES: &str = "sqlbuild.cost.types"; -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const BUILD_TYPES: &str = "sqlbuild.executor.build.types"; const SUCCESS_STATUS: &str = "success"; const PENDING_MESSAGE: &str = "Build cost collection has not completed."; const INTERRUPTED_MESSAGE: &str = "Build was interrupted before cost collection completed."; @@ -75,14 +75,14 @@ pub(crate) fn finalize_build_cost<'py>( ]; fields.extend(cost.overrides); let finalization: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "BuildCostFinalization")?, + ®istry::class(py, RecordId::BuildCostFinalization)?, &fields, )?; finalize_cost(&finalization) } fn cost_status<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, COST_TYPES, "CostStatus")?.getattr(name) + host::entry(py, ExecutorHost::CostStatus)?.getattr(name) } /// Record a pending cost state before execution starts. @@ -154,7 +154,7 @@ fn cost_payload<'py>(py: Python<'py>, record: &Bound<'py, PyAny>) -> PyResult( fn write_json(context: &BuildContext<'_>, payload: Bound<'_, PyAny>) -> PyResult<()> { call_with( - &imported(context.py(), NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(context.py(), NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", context.request.getattr("json_output")?), @@ -189,7 +189,7 @@ fn finalize_no_work(context: &BuildContext<'_>, pipeline: &Bound<'_, PyAny>) -> let py: Python<'_> = context.py(); let plan: Bound<'_, PyAny> = pipeline.getattr("plan_output")?; if call_with( - &imported(py, NATIVE_MODULE, "plan_has_executable_work")?, + ®istry::native(py, NativeAttr::PlanHasExecutableWork)?, &[ ("plan", plan.clone()), ( @@ -220,14 +220,14 @@ fn finalize_no_work(context: &BuildContext<'_>, pipeline: &Bound<'_, PyAny>) -> }, )?; let result: Bound<'_, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildExecutionResult")?, + &host::entry(py, ExecutorHost::BuildExecutionResult)?, &[( "status", - imported(py, BUILD_TYPES, "BuildStatus")?.getattr("SUCCESS")?, + host::entry(py, ExecutorHost::BuildStatus)?.getattr("SUCCESS")?, )], )?; let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_build_execution_json")?, + ®istry::native(py, NativeAttr::FormatBuildExecutionJson)?, &[ ("result", result), ("plan", plan), @@ -347,7 +347,7 @@ pub(crate) fn complete_direct_build<'py>( let cost_seconds: f64 = finish_cost_timing(context, cost_started); let record: Bound<'py, PyAny> = record?; let footer: String = call_with( - &imported(py, NATIVE_MODULE, "format_build_footer")?, + ®istry::native(py, NativeAttr::FormatBuildFooter)?, &[ ("result", result.clone()), ("elapsed", preparation.callbacks.getattr("elapsed")?), @@ -358,7 +358,7 @@ pub(crate) fn complete_direct_build<'py>( .extract()?; emit(&context.stream, &format!("\n{footer}\n"))?; let payload: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_degradable_build_execution_json")?, + ®istry::native(py, NativeAttr::FormatDegradableBuildExecutionJson)?, &[ ("result", result.clone()), ("plan", plan), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs index 7b183ca425..b67e8007ac 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs @@ -14,19 +14,11 @@ use sqlbuild_core::build_command::errors::BuildCommandError; use crate::bindings::_helpers::build_command::invocation::BuildContext; use crate::bindings::_helpers::build_command::policies::build_command_error; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CLONE_MODELS: &str = "sqlbuild.executor.clone.models"; -const CLONE_TYPES: &str = "sqlbuild.executor.clone.types"; -const SPEC_TYPES: &str = "sqlbuild._native"; -const TRANSIENT_MODELS_MODULE: &str = - "sqlbuild.executor.clone.main._build_destination_transient_models"; -const RETENTION_REQUESTS_MODULE: &str = "sqlbuild.executor.clone.main._build_retention_requests"; -const EXECUTE_CLONE_MODULE: &str = "sqlbuild.executor.clone.main.execute"; -const FINGERPRINTING_MODULE: &str = "sqlbuild.executor.clone.main._fingerprinting"; -const CLONE_STREAM_MODULE: &str = "sqlbuild.executor.clone.main._run_prephase_clone_stream"; const VIEW_MATERIALIZATION: &str = "view"; const RECREATED_TYPES: [&str; 2] = ["udf", "table_fn"]; const CLONABLE_TYPES: [&str; 4] = ["model", "seed", "udf", "table_fn"]; @@ -107,12 +99,12 @@ fn membership<'py>( fn traversed_names<'py>( scope: &Bound<'py, PyAny>, - (module, function): (&str, &str), + function: NativeAttr, (is_clonable, is_view): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = scope.py(); let keys: Bound<'py, PyAny> = call_with( - &imported(py, module, function)?, + ®istry::native(py, function)?, &[ ("selected", scope.getattr("selected_keys")?), ("upstream", scope.getattr("upstream_deps")?), @@ -162,12 +154,8 @@ fn defer_clone_selection(scope: &Bound<'_, PyAny>) -> PyResult<(Vec, Vec let is_view: Bound<'_, PyAny> = membership(py, &keys, |key| recreated_over_clones(scope, key))?; let callbacks: (&Bound<'_, PyAny>, &Bound<'_, PyAny>) = (&is_clonable, &is_view); Ok(( - traversed_names(scope, (NATIVE_MODULE, "resolve_clone_boundary"), callbacks)?, - traversed_names( - scope, - (NATIVE_MODULE, "resolve_skipped_view_chain"), - callbacks, - )?, + traversed_names(scope, NativeAttr::ResolveCloneBoundary, callbacks)?, + traversed_names(scope, NativeAttr::ResolveSkippedViewChain, callbacks)?, selected_executable_model_count(scope)?, )) } @@ -183,10 +171,10 @@ fn resolve_defer_clone_selection( fn native<'py>( py: Python<'py>, - name: &str, + name: NativeAttr, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, NATIVE_MODULE, name)?, fields) + call_with(®istry::native(py, name)?, fields) } fn external_resolver<'py>( @@ -195,7 +183,7 @@ fn external_resolver<'py>( ) -> PyResult> { native( project_dir.py(), - "resolve_external_sql_reference_resolver", + NativeAttr::ResolveExternalSqlReferenceResolver, &[ ("project_dir", project_dir.clone()), ("discovered_inputs", discovered.clone()), @@ -232,7 +220,7 @@ fn clone_input<'py>( let destination_models: Bound<'py, PyAny> = pipeline.getattr("destination_model_entries")?; let target: Bound<'py, PyAny> = native( py, - "resolve_target_config", + NativeAttr::ResolveTargetConfig, &[ ( "project_config", @@ -247,11 +235,7 @@ fn clone_input<'py>( selected.push(entry?.getattr("name")?); } let retention: Bound<'py, PyAny> = call_with( - &imported( - py, - RETENTION_REQUESTS_MODULE, - "build_destination_retention_requests", - )?, + &host::entry(py, ExecutorHost::BuildDestinationRetentionRequests)?, &[ ("project", project.clone()), ("adapter_name", prephase.adapter.getattr("adapter_name")?), @@ -267,9 +251,9 @@ fn clone_input<'py>( )?; let allow_decrease: bool = target .getattr("time_travel_retention_decrease")? - .eq(imported(py, SPEC_TYPES, "RetentionDecreasePolicy")?.getattr("ALLOW")?)?; + .eq(registry::class(py, RecordId::RetentionDecreasePolicy)?.getattr("ALLOW")?)?; let sources: Bound<'py, PyAny> = call_with( - &imported(py, CLONE_MODELS, "CloneSourceEntries")?, + &host::entry(py, ExecutorHost::CloneSourceEntries)?, &[ ("origin", pipeline.getattr("origin_source_entries")?), ( @@ -279,7 +263,7 @@ fn clone_input<'py>( ], )?; call_with( - &imported(py, CLONE_MODELS, "CloneExecutionInput")?, + &host::entry(py, ExecutorHost::CloneExecutionInput)?, &[ ("source_entries", sources), ( @@ -316,11 +300,7 @@ fn clone_input<'py>( ( "destination_transient_models", call_with( - &imported( - py, - TRANSIENT_MODELS_MODULE, - "build_destination_transient_models", - )?, + &host::entry(py, ExecutorHost::BuildDestinationTransientModels)?, &[("project", project.clone())], )?, ), @@ -342,7 +322,7 @@ fn executed_clone<'py>( ) -> PyResult> { let py: Python<'py> = context.py(); let progress_stream: Bound<'py, PyAny> = context.invocation.getattr("progress_stream")?; - let execute: Bound<'py, PyAny> = imported(py, EXECUTE_CLONE_MODULE, "execute_clone")?; + let execute: Bound<'py, PyAny> = host::entry(py, ExecutorHost::ExecuteClone)?; if progress_stream.is_none() { let inputs: Bound<'py, PyAny> = clone_input( context, @@ -382,7 +362,7 @@ fn executed_clone<'py>( }, )?; call_with( - &imported(py, CLONE_STREAM_MODULE, "run_prephase_clone_stream")?, + &host::entry(py, ExecutorHost::RunPrephaseCloneStream)?, &[ ("stream", progress_stream), ("title", text(py, "defer clone")), @@ -395,7 +375,7 @@ fn executed_clone<'py>( fn failed_items(result: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = result.py(); - let status: Bound<'_, PyAny> = imported(py, CLONE_TYPES, "CloneStatus")?; + let status: Bound<'_, PyAny> = host::entry(py, ExecutorHost::CloneStatus)?; let (failed, warning) = (status.getattr("FAILED")?, status.getattr("WARNING")?); let mut items: Vec = Vec::new(); for item in result.getattr("item_results")?.try_iter()? { @@ -434,7 +414,7 @@ fn clone_boundaries<'py>( let request: &Bound<'py, PyAny> = &context.request; let pipeline: Bound<'py, PyAny> = native( py, - "run_clone_pipeline_with_options", + NativeAttr::RunClonePipelineWithOptions, &[ ("discovered_inputs", prephase.discovered.clone()), ("adapter", prephase.adapter.clone()), @@ -443,7 +423,7 @@ fn clone_boundaries<'py>( ( "destination_connection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineConnection")?, + ®istry::class(py, RecordId::ClonePipelineConnection)?, &[ ("config", context.invocation.getattr("connection_config")?), ("handle", connection.clone()), @@ -453,7 +433,7 @@ fn clone_boundaries<'py>( ( "options", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineOptions")?, + ®istry::class(py, RecordId::ClonePipelineOptions)?, &[ ("no_sql_validation", request.getattr("no_sql_validation")?), ("no_cache", request.getattr("no_cache")?), @@ -489,7 +469,7 @@ fn clone_boundaries<'py>( } let project: Bound<'py, PyAny> = pipeline.getattr("destination_project")?; call_with( - &imported(py, FINGERPRINTING_MODULE, "copy_clone_fingerprints")?, + &host::entry(py, ExecutorHost::CopyCloneFingerprints)?, &[ ("result", result), ( @@ -571,7 +551,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( let discovered: Bound<'_, PyAny> = invocation.getattr("discovered_inputs")?; let split: Bound<'_, PyAny> = native( py, - "split_unit_test_selectors", + NativeAttr::SplitUnitTestSelectors, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -589,7 +569,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( let project_dir: Bound<'_, PyAny> = invocation.getattr("effective_project_dir")?; let project: Bound<'_, PyAny> = native( py, - "build_compiled_project", + NativeAttr::BuildCompiledProject, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -598,7 +578,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( ( "analysis_selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", request.getattr("no_cache")?)], )?, ), @@ -611,7 +591,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( )?; let scope: Bound<'_, PyAny> = native( py, - "build_planner_scope", + NativeAttr::BuildPlannerScope, &[ ("project", project.clone()), ("select", split.getattr("select")?), @@ -626,7 +606,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( defer_clone_selection(&scope)?; native( py, - "enforce_model_execution_limit", + NativeAttr::EnforceModelExecutionLimit, &[ ("model_count", model_count.into_pyobject(py)?.into_any()), ("target_name", invocation.getattr("effective_target_name")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs index b5e49386a6..9d96806187 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs @@ -18,19 +18,15 @@ use crate::bindings::_helpers::check::check_core::{ build_check_relation_targets, load_results_by_loader_name, record_python_run_state_results, relevant_check_functions, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; use crate::bindings::_helpers::cursor::overrides::{cursor_integer, cursor_timestamp}; use crate::bindings::_helpers::freshness::publication::append_freshness_records; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CURSOR_OVERRIDES_MODULE: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const PIPELINE_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; -const COST_CONTEXT_MODULE: &str = "sqlbuild._native"; -const CHECKS_MODULE: &str = "sqlbuild.executor.python_nodes.main._checks"; -const EXECUTOR_PYTHON_MODELS: &str = "sqlbuild.executor.python_nodes.models"; const EXECUTION_COMMAND: &str = "sqb build"; const SUCCESS_STATUS: &str = "success"; const NODE_START: [&str; 2] = ["name", "resource_kind"]; @@ -104,7 +100,7 @@ fn run_context_facts( + count(&discovered, "task_functions")? + count(&discovered, "asset_functions")?; let resolved: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "describe_connection_warehouse")?, + ®istry::native(py, NativeAttr::DescribeConnectionWarehouse)?, &[ ("discovered_inputs", discovered), ("selected_target", project.getattr("effective_target_name")?), @@ -178,7 +174,7 @@ fn cursors<'py>( let mut overrides: Bound<'py, PyAny> = plan.getattr("cursor_overrides")?; if !overrides.is_truthy()? { overrides = call_with( - &imported(py, CURSOR_OVERRIDES_MODULE, "type_cursor_overrides")?, + ®istry::native(py, NativeAttr::TypeCursorOverrides)?, &[( "cursor_overrides", context.request.getattr("cursor_overrides")?, @@ -186,7 +182,7 @@ fn cursors<'py>( )?; } if !overrides.is_truthy()? { - overrides = imported(py, PLANNER_MODELS, "CursorOverrides")?.call0()?; + overrides = registry::class(py, RecordId::CursorOverrides)?.call0()?; } Ok([ cursor_timestamp(&overrides.getattr("start_ts")?)?, @@ -206,7 +202,7 @@ pub(crate) fn prepare_build_execution<'py>( let plan: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let debug: bool = context.request_flag("debug")? || context.request_flag("json_output")?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "BuildProgressCallbacks")?, + ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, &[ ("plan", plan.clone()), ("use_color", boolean(py, context.use_color)), @@ -223,7 +219,7 @@ pub(crate) fn prepare_build_execution<'py>( write_run_context(context, pipeline)?; } else { call_with( - &imported(py, NATIVE_MODULE, "write_execution_header")?, + ®istry::native(py, NativeAttr::WriteExecutionHeader)?, &[ ("stream", context.stream.clone()), ("command", text(py, EXECUTION_COMMAND)), @@ -234,7 +230,7 @@ pub(crate) fn prepare_build_execution<'py>( )?; } let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.get("adapter_name")?), ("stream", context.stream.clone()), @@ -292,7 +288,7 @@ pub(crate) fn execute_build_plan<'py>( let [start_ts, end_ts, start_int, end_int] = &preparation.cursors; let request = |name: &str| context.request.getattr(name); let runtime: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildRuntimeParams")?, + &host::entry(py, ExecutorHost::BuildRuntimeParams)?, &[ ("run_id", project.getattr("run_id")?), ( @@ -324,7 +320,7 @@ pub(crate) fn execute_build_plan<'py>( ( "project_relations", call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[("adapter", adapter.clone()), ("plan_output", plan.clone())], )?, ), @@ -332,7 +328,7 @@ pub(crate) fn execute_build_plan<'py>( )?; let connection: &Bound<'py, PyAny> = &preparation.connection_progress; let build_callbacks: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildCallbacks")?, + &host::entry(py, ExecutorHost::BuildCallbacks)?, &[ ("on_migration_progress", line_writer(&context.stream)?), ( @@ -373,7 +369,7 @@ pub(crate) fn execute_build_plan<'py>( ], )?; let customizations: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildCustomizations")?, + &host::entry(py, ExecutorHost::BuildCustomizations)?, &[ ( "custom_materializations", @@ -383,7 +379,7 @@ pub(crate) fn execute_build_plan<'py>( ], )?; let initial_state: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildInitialState")?, + &host::entry(py, ExecutorHost::BuildInitialState)?, &[ ("precompleted_keys", lifecycle.precompleted_keys(py)?), ("initial_load_results", lifecycle.ingress_load_results(py)), @@ -391,7 +387,7 @@ pub(crate) fn execute_build_plan<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &imported(py, PIPELINE_RUN_MODULE, "run_build_pipeline")?, + &host::entry(py, ExecutorHost::RunBuildPipeline)?, &[ ("plan", plan.clone()), ("connection_config", connection_config.clone()), @@ -430,7 +426,7 @@ fn check_runtime<'py>( .map(|check| check.getattr("name")) .collect::>()?; call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "PythonNodeRuntime")?, + &host::entry(py, ExecutorHost::PythonNodeRuntime)?, &[ ("adapter", adapter.clone()), ("connection_config", context.get("connection_config")?), @@ -451,7 +447,7 @@ fn check_runtime<'py>( ( "project_relations", call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[ ("adapter", adapter), ("plan_output", pipeline.getattr("plan_output")?), @@ -511,7 +507,7 @@ pub(crate) fn run_post_build_python_checks<'py>( adapter.call_method1("connect", (context.get("connection_config")?,))?; let executed: PyResult> = (|| { let run_state: Bound<'py, PyAny> = - imported(py, EXECUTOR_PYTHON_MODELS, "PythonNodeRunState")?.call0()?; + host::entry(py, ExecutorHost::PythonNodeRunState)?.call0()?; record_python_run_state_results( &discovered, &run_state, @@ -519,14 +515,14 @@ pub(crate) fn run_post_build_python_checks<'py>( Some(&plan.getattr("source_map")?), )?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "PythonCheckCallbacks")?, + &host::entry(py, ExecutorHost::PythonCheckCallbacks)?, &[( "on_check_complete", preparation.callbacks.getattr("write_execution_event")?, )], )?; call_with( - &imported(py, CHECKS_MODULE, "execute_python_checks")?, + &host::entry(py, ExecutorHost::ExecutePythonChecks)?, &[ ("check_functions", tuple(py, &checks)?), ("python_graph", graph.clone()), @@ -546,7 +542,7 @@ pub(crate) fn run_post_build_python_checks<'py>( let check_results: Bound<'py, PyAny> = executed?; closed?; call_with( - &imported(py, NATIVE_MODULE, "write_check_results")?, + ®istry::native(py, NativeAttr::WriteCheckResults)?, &[ ("stream", context.stream.clone()), ("results", check_results.clone()), @@ -573,7 +569,7 @@ pub(crate) fn execute_direct_build<'py>( } else { context.get("adapter_name")? }; - let cost_context: Bound<'py, PyAny> = imported(py, COST_CONTEXT_MODULE, "CostContext")?; + let cost_context: Bound<'py, PyAny> = registry::native(py, NativeAttr::CostContext)?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("run_id", &run_id)?; options.set_item("resource_type", "run")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs index b7dd57683a..06f649fff6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -12,13 +12,9 @@ use crate::bindings::_helpers::build_command::support::{boolean, or_none, text}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const SETTINGS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS: &str = "sqlbuild._native"; const DEFER_CONFLICT_CODE: &str = "C408"; /// One build's request, resolved invocation and output settings. @@ -59,7 +55,7 @@ fn target_config<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -75,7 +71,7 @@ fn execution_limits<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let limits: Bound<'py, PyAny> = if target_name.is_none() { - imported(py, CONTRACT_MODELS, "ExecutionLimitsConfig")?.call0()? + registry::class(py, RecordId::ExecutionLimitsConfig)?.call0()? } else { target_config(discovered, target_name)?.getattr("execution_limits")? }; @@ -106,7 +102,7 @@ pub(crate) fn resolve_build_context<'py>( imported(py, "pathlib", "Path")?.call_method0("cwd") })?; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("extract_output_column_locations", boolean(py, false)), @@ -114,7 +110,7 @@ pub(crate) fn resolve_build_context<'py>( )?; let selected_target: Bound<'py, PyAny> = request.getattr("selected_target")?; let target_name: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -139,7 +135,7 @@ pub(crate) fn resolve_build_context<'py>( )); } let adapter_name: String = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -150,7 +146,7 @@ pub(crate) fn resolve_build_context<'py>( let limits: Bound<'py, PyAny> = execution_limits(&discovered, &target_name, (&adapter_name, &adapter))?; let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("project_dir", project_dir.clone()), @@ -168,7 +164,7 @@ pub(crate) fn resolve_build_context<'py>( "stdout" })?; let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", text(py, &adapter_name)), ("stream", stream.clone()), @@ -176,7 +172,7 @@ pub(crate) fn resolve_build_context<'py>( ], )?; let planning_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", boolean(py, use_color)), @@ -186,7 +182,7 @@ pub(crate) fn resolve_build_context<'py>( let should_load_sources: bool = request.getattr("reload_sources")?.is_truthy()? || if load_sources.is_none() { call_with( - &imported(py, SETTINGS_MODULE, "build_effective_settings_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", discovered.clone())], )? .getattr("auto_load_sources")? @@ -195,7 +191,7 @@ pub(crate) fn resolve_build_context<'py>( load_sources.is_truthy()? }; let invocation: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "BuildInvocation")?, + ®istry::class(py, RecordId::BuildInvocation)?, &[ ("effective_project_dir", project_dir), ("discovered_inputs", discovered), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs index 968c5fc8c1..6cece1234f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs @@ -16,16 +16,12 @@ use crate::bindings::_helpers::build_command::support::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LOADER_DAG_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const INGRESS_MODULE: &str = "sqlbuild.executor.python_nodes.main._ingress"; -const READ_SIDE_MODULE: &str = "sqlbuild.executor.python_nodes.main.read_side"; -const EXECUTOR_PYTHON_MODELS: &str = "sqlbuild.executor.python_nodes.models"; const SUCCESS_STATUS: &str = "success"; /// The cursor window and output settings the Python lifecycle runs with. @@ -265,7 +261,7 @@ fn node_runtime<'py>( let py: Python<'py> = connection.py(); let [start_ts, end_ts, start_int, end_int] = &parts.inputs.cursors; call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "PythonNodeRuntime")?, + &host::entry(py, ExecutorHost::PythonNodeRuntime)?, &[ ("adapter", parts.adapter.clone()), ("connection_config", parts.connection_config.clone()), @@ -302,7 +298,7 @@ fn lifecycle_plan<'py>( let plan_output: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let graph: Bound<'py, PyAny> = python_node_graph(discovered)?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ( "sql_keys", @@ -313,12 +309,12 @@ fn lifecycle_plan<'py>( ], )?; let plan: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_sql_run_lifecycle")?, + ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[("selection", selection), ("python_graph", graph.clone())], )?; let source_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS, "CompiledObjectKey")?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let mut keys: Vec> = Vec::new(); for name in plan.getattr("ingress_loader_names")?.try_iter()? { keys.push(call_with( @@ -331,7 +327,7 @@ fn lifecycle_plan<'py>( source_map.call_method1( "update", (call_with( - &imported(py, LOADER_DAG_MODULE, "build_intermediate_source_map")?, + ®istry::native(py, NativeAttr::BuildIntermediateSourceMap)?, &[ ("project", pipeline.getattr("project")?), ("selected_keys", frozenset(py, &keys)?), @@ -395,7 +391,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( )], )?; let relation_targets: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_relation_targets")?, + ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, &[ ("adapter", adapter.clone()), ("project", project.clone()), @@ -404,7 +400,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( ], )?; let project_relations: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[ ("adapter", adapter.clone()), ("plan_output", plan_output.clone()), @@ -433,7 +429,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( adapter.call_method1("connect", (connection_config.clone(),))?; let executed: PyResult> = (|| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "IngressCallbacks")?, + &host::entry(py, ExecutorHost::IngressCallbacks)?, &[ ("use_color", boolean(py, context.use_color)), ("on_node_start", inputs.on_node_start.clone()), @@ -441,7 +437,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( ], )?; call_with( - &imported(py, INGRESS_MODULE, "run_ingress_python_loader_nodes")?, + &host::entry(py, ExecutorHost::RunIngressPythonLoaderNodes)?, &[ ("python_graph", lifecycle.graph.clone()), ("selected_python_names", ingress_names.clone()), @@ -480,11 +476,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( } let scheduled: Vec = scheduled.into_iter().collect(); let tracker: Bound<'py, PyAny> = call_with( - &imported( - py, - READ_SIDE_MODULE, - "create_read_side_python_execution_tracker", - )?, + &host::entry(py, ExecutorHost::CreateReadSidePythonExecutionTracker)?, &[ ("python_graph", lifecycle.graph.clone()), ("selected_python_names", names_set(py, &read_side)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs index 6ba0587a4c..e81babcaa9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs @@ -14,13 +14,12 @@ use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, keyword_relay, none, }; use crate::bindings::_helpers::cli::progress_text::with_verbose_inspection; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const RUNTIME_CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; const CONNECTION_TIMING: [&str; 2] = ["connection_count", "elapsed_seconds"]; /// Clone out-of-selection boundaries from the defer-clone origin before planning. @@ -40,18 +39,14 @@ fn request_options<'py>(context: &BuildContext<'py>) -> PyResult = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", context.get("effective_project_dir")?), ("discovered_inputs", context.get("discovered_inputs")?), ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("no_sql_validation", field("no_sql_validation")?), ("no_cache", field("no_cache")?), @@ -85,7 +80,7 @@ pub(crate) fn compile_build_plan<'py>(context: &BuildContext<'py>) -> PyResult = context.get("connection_progress")?; let hooks: Bound<'py, PyAny> = call_with( - &imported(py, RUNTIME_CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_progress", @@ -114,7 +109,7 @@ pub(crate) fn compile_build_plan<'py>(context: &BuildContext<'py>) -> PyResult = pipeline.getattr("plan_output")?; let manifest: Bound<'_, PyAny> = if context.request_flag("manifest")? { call_with( - &imported(py, NATIVE_MODULE, "build_manifest")?, + ®istry::native(py, NativeAttr::BuildManifest)?, &[ ("project", pipeline.getattr("project")?), ("plan_output", plan.clone()), @@ -216,7 +211,7 @@ pub(crate) fn write_compile_target( none(py) }; call_with( - &imported(py, NATIVE_MODULE, "write_compile_target")?, + ®istry::native(py, NativeAttr::WriteCompileTarget)?, &[ ( "target_dir", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs index 3b0714a9e5..d00dc913d3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs @@ -16,9 +16,10 @@ use sqlbuild_core::build_command::models::{ TableTypeFact, }; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const ERRORS_MODULE: &str = "sqlbuild._native"; const SNAPSHOT_VALUE: &str = "snapshot"; const FULL_REFRESH_REASON: &str = "full_refresh"; const SKIP_ACTION: &str = "skip"; @@ -45,7 +46,7 @@ pub(crate) fn build_command_error(py: Python<'_>, error: BuildCommandError) -> P let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; options.set_item("help", error.help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs index 472ad71d1d..84c81681e2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs @@ -30,8 +30,9 @@ use crate::bindings::_helpers::build_progress::terminal::{ terminal_columns, tty_flag, write, }; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; const RUNTIME_LOGGER: &str = "sqlbuild.executor.runtime"; const PASS_OUTCOME: &str = "pass"; const SCHEDULER_DIAGNOSTIC_LIMIT: usize = 50; @@ -407,7 +408,8 @@ impl BuildProgressCallbacks { let writer_options: Bound<'_, PyDict> = PyDict::new(py); writer_options.set_item("path", path)?; let event_writer: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call((), Some(&writer_options))?; + registry::native(py, NativeAttr::ExecutionEventWriter)? + .call((), Some(&writer_options))?; let facts: BuildPlanFacts = build_plan_facts(&plan)?; let projector: Option> = projector(py)?; if let Some(projector) = &projector { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs index ebf0e651f7..83cf0620ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs @@ -6,6 +6,8 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::build_progress::transient_lines::shared_coordinator; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const DEFAULT_TERMINAL_SIZE: (i64, i64) = (120, 24); const SPINNER_JOIN_SECONDS: f64 = 0.2; @@ -48,7 +50,7 @@ pub(crate) fn tty_flag(stream: &Bound<'_, PyAny>) -> PyResult { /// The current native progress projector, if a projection is active. pub(crate) fn projector(py: Python<'_>) -> PyResult>> { let projector: Bound<'_, PyAny> = - imported(py, "sqlbuild._native", "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; Ok((!projector.is_none()).then(|| projector.unbind())) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs b/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs index 63cd1b55b9..b11fdbce05 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs @@ -17,11 +17,12 @@ use sqlbuild_cache::store::models::NativeStore; use std::collections::HashSet; use std::path::{Path, PathBuf}; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PACKAGE_MODULE: &str = "sqlbuild"; - /// Hexadecimal digest of length-prefixed UTF-8 `parts`, the digest every native cache key uses. #[pyfunction] pub(crate) fn content_digest(parts: Vec) -> String { @@ -92,17 +93,13 @@ pub(crate) fn save_store( pub(crate) fn compiled_code_identity(py: Python<'_>) -> PyResult { static INSTALLED: PyOnceLock = PyOnceLock::new(); let installed: &String = INSTALLED.get_or_try_init(py, || installed_identity(py))?; - let build_identity: String = py - .import(NATIVE_MODULE)? - .getattr("BUILD_IDENTITY")? - .extract()?; + let build_identity: String = registry::native(py, NativeAttr::BuildIdentity)?.extract()?; Ok(compiled(installed, &build_identity)) } /// `compiled_code_identity`, looked up on the extension module so tests can replace it. pub(crate) fn current_code_identity(py: Python<'_>) -> PyResult { - py.import(NATIVE_MODULE)? - .getattr("compiled_code_identity")? + registry::native(py, NativeAttr::CompiledCodeIdentity)? .call0()? .extract() } @@ -129,7 +126,7 @@ fn installed_identity(py: Python<'_>) -> PyResult { .getattr("minor")? .extract()?, ); - let package_file: PathBuf = py.import(PACKAGE_MODULE)?.getattr("__file__")?.extract()?; + let package_file: PathBuf = host::entry(py, FacadeHost::PackageFile)?.extract()?; let (mut facts, editable) = py .compiler_detach(|| Ok(installed_code_facts(&search_paths))) .map_err(PyRuntimeError::new_err)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs index 7e3ff2fac1..f23c9beec0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs @@ -24,23 +24,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::compile_outputs::runtime_target::write_python_check_runtime_target; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; use crate::bindings::_helpers::graph::python_selection::python_node_names; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const RELATION_PLAN_MODULE: &str = "sqlbuild._native"; -const SELECTOR_PARSE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const PYTHON_NODE_TYPES: &str = "sqlbuild.compiler.python_nodes.types"; -const EXECUTOR_MODELS: &str = "sqlbuild.executor.python_nodes.models"; -const CHECKS_MODULE: &str = "sqlbuild.executor.python_nodes.main._checks"; -const INGRESS_MODULE: &str = "sqlbuild.executor.python_nodes.main._ingress"; -const PROVIDER_SESSION_MODULE: &str = "sqlbuild.provider.main.session"; const GRAPH_OPERATOR_CODE: &str = "C681"; const GRAPH_OPERATOR_MESSAGE: &str = "sqb check selectors do not support graph operators; \ select checks directly without '+', '~', or path-between expansion"; @@ -62,7 +52,7 @@ fn user_error(py: Python<'_>, (code, message): (&str, &str)) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -80,8 +70,8 @@ fn empty_tuple(py: Python<'_>) -> Bound<'_, PyAny> { /// Refuse `+`, `~` and path selectors, splitting selectors as Python's `split` does. fn refuse_graph_selectors(py: Python<'_>, selectors: &[Bound<'_, PyAny>]) -> PyResult<()> { - let parse: Bound<'_, PyAny> = imported(py, SELECTOR_PARSE_MODULE, "parse_project_selector")?; - let path_selector: Bound<'_, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PathSelector")?; + let parse: Bound<'_, PyAny> = registry::native(py, NativeAttr::ParseProjectSelector)?; + let path_selector: Bound<'_, PyAny> = registry::class(py, RecordId::PathSelector)?; for selector in selectors { for token in selector.call_method0("split")?.try_iter()? { for part in token?.call_method1("split", (",",))?.try_iter()? { @@ -132,7 +122,7 @@ fn selected_check_names<'py>( .collect::>()?; refuse_graph_selectors(py, &selectors)?; let check_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("CHECK")?; + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("CHECK")?; if select.is_truthy()? { let selected: Bound<'py, PyAny> = python_nodes(graph, (select, exclude))?; let mut non_checks: Vec = Vec::new(); @@ -174,7 +164,7 @@ fn compile_pipeline<'py>( &[("selected_names", check_names.clone().into_any())], )?; let closure: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "transitive_closure_many")?, + ®istry::native(py, NativeAttr::TransitiveClosureMany)?, &[ ("starts", check_names.clone().into_any()), ("edges", graph.getattr("upstream_deps")?), @@ -185,7 +175,7 @@ fn compile_pipeline<'py>( ], )?; let loader_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("LOADER")?; + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("LOADER")?; let mut loaders: Vec> = Vec::new(); for name in closure.try_iter()? { let name: Bound<'py, PyAny> = name?; @@ -194,10 +184,10 @@ fn compile_pipeline<'py>( } } let loaders: Bound<'py, PyFrozenSet> = PyFrozenSet::new(py, loaders)?; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS, "CompiledObjectKey")?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let source_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?; - let ref_key: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "sql_ref_key")?; + let ref_key: Bound<'py, PyAny> = registry::native(py, NativeAttr::SqlRefKey)?; let mut keys: Vec> = Vec::new(); for reference in required_refs.try_iter()? { keys.push(ref_key.call1((reference?,))?); @@ -218,18 +208,14 @@ fn compile_pipeline<'py>( } let keys: Bound<'py, PyAny> = PyFrozenSet::new(py, keys)?.into_any(); let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -242,7 +228,7 @@ fn compile_pipeline<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", invocation.getattr("adapter")?), @@ -258,7 +244,7 @@ fn compile_pipeline<'py>( ], )?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, RELATION_PLAN_MODULE, "build_relation_command_plan")?, + ®istry::native(py, NativeAttr::BuildRelationCommandPlan)?, &[ ("project", context.getattr("project")?), ("scope", context.getattr("scope")?), @@ -266,7 +252,7 @@ fn compile_pipeline<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -292,7 +278,7 @@ fn python_selection<'py>( let py: Python<'py> = request.py(); let python_graph: Bound<'py, PyAny> = python_node_graph(discovered)?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -322,14 +308,14 @@ fn prepare<'py>( } } let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ("sql_keys", PyFrozenSet::empty(py)?.into_any()), ("python_node_names", PyFrozenSet::empty(py)?.into_any()), ], )?; let lifecycle_plan: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_sql_run_lifecycle")?, + ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[ ("selection", selection), ("python_graph", python_graph.clone()), @@ -342,7 +328,7 @@ fn prepare<'py>( let project: Bound<'py, PyAny> = pipeline.getattr("project")?; let plan_output: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let relation_targets: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_relation_targets")?, + ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, &[ ("adapter", adapter.clone()), ("project", project.clone()), @@ -368,7 +354,7 @@ fn prepare<'py>( )?, ), project_relations: call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[("adapter", adapter), ("plan_output", plan_output)], )?, python_graph, @@ -403,10 +389,7 @@ fn node_runtime<'py>( fields.push(("project_relations", prepared.project_relations.clone())); } fields.push(("providers", providers.clone())); - call_with( - &imported(py, EXECUTOR_MODELS, "PythonNodeRuntime")?, - &fields, - ) + call_with(&host::entry(py, ExecutorHost::PythonNodeRuntime)?, &fields) } fn record_run_state<'py>( @@ -440,11 +423,11 @@ fn run_checks<'py>( let plan_output: Bound<'py, PyAny> = prepared.pipeline.getattr("plan_output")?; let source_map: Bound<'py, PyAny> = plan_output.getattr("source_map")?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_MODELS, "IngressCallbacks")?, + &host::entry(py, ExecutorHost::IngressCallbacks)?, &[("use_color", invocation.getattr("use_color")?)], )?; let ingress: Bound<'py, PyAny> = call_with( - &imported(py, INGRESS_MODULE, "execute_ingress_python_loader_nodes")?, + &host::entry(py, ExecutorHost::ExecuteIngressPythonLoaderNodes)?, &[ ("python_graph", prepared.python_graph.clone()), ( @@ -511,11 +494,11 @@ fn run_checks<'py>( )? .into_any(); let check_callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_MODELS, "PythonCheckCallbacks")?, + &host::entry(py, ExecutorHost::PythonCheckCallbacks)?, &[("on_check_complete", on_complete)], )?; call_with( - &imported(py, CHECKS_MODULE, "execute_python_checks")?, + &host::entry(py, ExecutorHost::ExecutePythonChecks)?, &[ ("check_functions", prepared.check_functions.clone()), ("python_graph", prepared.python_graph.clone()), @@ -571,10 +554,10 @@ fn execute<'py>( let connection: Bound<'py, PyAny> = adapter.call_method1("connect", (invocation.getattr("connection_config")?,))?; let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let executed: PyResult>> = (|| { let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { let ordinals: Bound<'py, PyDict> = PyDict::new(py); let mut total: usize = 0; @@ -635,7 +618,7 @@ fn complete<'py>( &tuple, )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ( "payload", @@ -654,7 +637,7 @@ fn complete<'py>( fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - &imported(py, NATIVE_MODULE, "CheckInvocation")?, + ®istry::class(py, RecordId::CheckInvocation)?, true, )?; let stream: Bound<'_, PyAny> = invocation.getattr("progress_stream")?; @@ -665,10 +648,10 @@ fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult = prepare(request, &invocation)?; let session: Bound<'_, PyAny> = call_with( - &imported(py, PROVIDER_SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[( "discovered_providers", invocation diff --git a/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs b/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs index b9ccb8007e..44a352eb20 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs @@ -10,18 +10,14 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::build_command::errors::BuildCommandError; use crate::bindings::_helpers::build_command::policies::build_command_error; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const BUILD_TYPES: &str = "sqlbuild.executor.build.types"; -const PYTHON_NODE_TYPES: &str = "sqlbuild.compiler.python_nodes.types"; -const EXECUTOR_MODELS: &str = "sqlbuild.executor.python_nodes.models"; -const READ_SIDE_MODULE: &str = "sqlbuild.executor.python_nodes.main.read_side"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; -const QUALIFIED_NAME_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; const MISSING_RELATION_CODE: &str = "C682"; const MODEL_KIND: &str = "model"; const SEED_KIND: &str = "seed"; @@ -56,8 +52,8 @@ fn load_result_as_python_result<'py>( result: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = name.py(); - let execution: Bound<'py, PyAny> = imported(py, BUILD_TYPES, "ExecutionStatus")?; - let node_status: Bound<'py, PyAny> = imported(py, PYTHON_NODE_TYPES, "PythonNodeStatus")?; + let execution: Bound<'py, PyAny> = host::entry(py, ExecutorHost::ExecutionStatus)?; + let node_status: Bound<'py, PyAny> = host::entry(py, UserCodeHost::PythonNodeStatus)?; let status: Bound<'py, PyAny> = result.getattr("status")?; let mapped: Bound<'py, PyAny> = if status.eq(execution.getattr("SUCCESS")?)? { node_status.getattr("SUCCESS")? @@ -67,12 +63,12 @@ fn load_result_as_python_result<'py>( node_status.getattr("FAILED")? }; call_with( - &imported(py, EXECUTOR_MODELS, "PythonNodeExecutionResult")?, + &host::entry(py, ExecutorHost::PythonNodeExecutionResult)?, &[ ("node_name", name.clone()), ( "kind", - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("LOADER")?, + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("LOADER")?, ), ("status", mapped), ("payload", result.clone()), @@ -175,7 +171,7 @@ pub(crate) fn build_check_relation_targets<'py>( &[("selected_names", selected_python_names)], )?; call_with( - &imported(py, NATIVE_MODULE, "build_python_relation_targets")?, + ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, &[ ("adapter", adapter.clone()), ("project", pipeline_result.getattr("project")?), @@ -194,11 +190,11 @@ pub(crate) fn relevant_check_functions<'py>( ) -> PyResult>> { let py: Python<'py> = discovered_inputs.py(); let excluded: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_python_check_excludes")?, + ®istry::native(py, NativeAttr::ResolvePythonCheckExcludes)?, &[("graph", python_graph.clone()), ("exclude", exclude)], )?; let check_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("CHECK")?; + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("CHECK")?; let upstream_deps: Bound<'py, PyAny> = python_graph.getattr("upstream_deps")?; let mut check_names: Vec> = Vec::new(); for node in python_graph.getattr("nodes")?.try_iter()? { @@ -367,11 +363,7 @@ fn validate_reference_exists( ), )?; let relation: Bound<'_, PyAny> = call_with( - &imported( - py, - QUALIFIED_NAME_MODULE, - "resolve_relation_location_qualified_name", - )?, + &host::entry(py, AdapterHost::ResolveRelationLocationQualifiedName)?, &[("adapter", inputs.adapter.clone()), ("location", location)], )?; (exists, relation) @@ -438,7 +430,7 @@ fn node_runtime<'py>(inputs: &ReadSideInputs<'py>) -> PyResult let py: Python<'py> = inputs.adapter.py(); let project: Bound<'py, PyAny> = inputs.pipeline_result.getattr("project")?; call_with( - &imported(py, EXECUTOR_MODELS, "PythonNodeRuntime")?, + &host::entry(py, ExecutorHost::PythonNodeRuntime)?, &[ ("adapter", inputs.adapter.clone()), ("connection_config", inputs.connection_config.clone()), @@ -484,7 +476,7 @@ pub(crate) fn run_read_side_dependencies<'py>( } } let lookup: Bound<'py, PyAny> = call_with( - &imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")?, + &host::entry(py, AdapterHost::BuildRelationLookup)?, &[ ("adapter", inputs.adapter.clone()), ("connection", inputs.connection.clone()), @@ -498,11 +490,7 @@ pub(crate) fn run_read_side_dependencies<'py>( return Ok(PyTuple::empty(py).into_any()); } let tracker: Bound<'py, PyAny> = call_with( - &imported( - py, - READ_SIDE_MODULE, - "create_read_side_python_execution_tracker", - )?, + &host::entry(py, ExecutorHost::CreateReadSidePythonExecutionTracker)?, &[ ("python_graph", inputs.python_graph.clone()), ("selected_python_names", read_side_names), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs b/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs index 92955c0b94..8688e368a1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs @@ -14,10 +14,11 @@ use sqlbuild_core::check_command::models::{CheckGrouping, CheckResultText, Check use sqlbuild_core::cli_text::models::CliStyle; use sqlbuild_core::json::models::JsonValue; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::integration_values::json_value; -const NATIVE_MODULE: &str = "sqlbuild._native"; const WARN_SEVERITY: &str = "warn"; fn optional_text(value: Bound<'_, PyAny>) -> PyResult> { @@ -124,7 +125,7 @@ pub(crate) fn write_results( checks.insert(check.getattr("name")?.extract()?, check); } let projector: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; let mut groups: Vec<(String, Vec)> = Vec::new(); for result in results { let text: CheckResultText = check_result_text(result)?; @@ -169,7 +170,7 @@ pub(crate) fn results_json(results: &[Bound<'_, PyAny>]) -> PyResult { }; let py: Python<'_> = first.py(); let index: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "current_terminal_event_index")?.call0()?; + registry::native(py, NativeAttr::CurrentTerminalEventIndex)?.call0()?; let mut texts: Vec = Vec::new(); for result in results { let name: Bound<'_, PyAny> = result.getattr("node_name")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs index 80a6fa14cc..24d8e42913 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs @@ -13,9 +13,10 @@ use sqlbuild_core::text::main::python_str_repr::python_str_repr; use crate::bindings::_helpers::cli::namespace::new_cli_namespace; use crate::bindings::_helpers::cli::surrogates::{python_text, rust_text, uses_placeholders}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const ENTRY_MODELS_MODULE: &str = "sqlbuild._native"; const INT_CONVERTER: &str = "int"; const POSITIVE_INTEGER_CONVERTER: &str = "_positive_integer"; const VARS_CONVERTER: &str = "parse_cli_vars"; @@ -255,7 +256,7 @@ pub(crate) fn parse_cli_invocation<'py>( .extract()?; let settings: ArgvSettings = ArgvSettings { columns, use_color }; let (rows, exit_code): ParsedRows = parse_cli_arguments(&argv, &defaults, settings)?; - let parsed: Bound<'py, PyAny> = imported(py, ENTRY_MODELS_MODULE, "ParsedCliInvocation")?; + let parsed: Bound<'py, PyAny> = registry::class(py, RecordId::ParsedCliInvocation)?; let Some(rows) = rows else { return call_with( &parsed, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs index 531979fcc0..f2b472f2ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs @@ -19,10 +19,9 @@ use sqlbuild_core::format_command::main::format_path_selectors::format_path_sele use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const ENTRY_MODELS_MODULE: &str = "sqlbuild._native"; -const WAREHOUSES_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const COMMENT_PREFIX: &str = "#"; const UTF8: &str = "utf-8"; @@ -120,7 +119,7 @@ impl<'py> Invocation<'py> { |error| { cli_error( py, - "CliUserError", + ErrorId::CliUser, &DiffUserError { message: error.message, code: error.code.to_owned(), @@ -148,7 +147,7 @@ impl<'py> Invocation<'py> { } let py: Python<'py> = self.py(); let (from_name, to_name) = parse_diff_name_range(&range.extract::()?) - .map_err(|error| cli_error(py, "CliUserError", &error))?; + .map_err(|error| cli_error(py, ErrorId::CliUser, &error))?; match side { DiffSide::From => from_name.into_bound_py_any(py), DiffSide::To => to_name.into_bound_py_any(py), @@ -176,7 +175,7 @@ fn path_class(py: Python<'_>) -> PyResult> { } fn class_object<'py>(py: Python<'py>, class: ClassRef) -> PyResult> { - imported(py, class.module, class.name) + registry::class(py, registry::record_named(class.name)?) } fn tuple_of<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -200,7 +199,7 @@ fn user_error(py: Python<'_>, failure: DispatchFailure) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", failure.code)?; keywords.set_item("help", failure.help)?; - imported(py, EXCEPTIONS_MODULE, "CliUserError")?.call((failure.message,), Some(&keywords)) + registry::error(py, ErrorId::CliUser)?.call((failure.message,), Some(&keywords)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -217,8 +216,7 @@ fn selector_inputs<'py>( .getattr("select")? .try_iter()? .collect::>()?; - let summary_class: Bound<'py, PyAny> = - imported(py, ENTRY_MODELS_MODULE, "SelectorFileSummary")?; + let summary_class: Bound<'py, PyAny> = registry::class(py, RecordId::SelectorFileSummary)?; let mut files: Vec> = Vec::new(); for raw_path in args.getattr("select_file")?.try_iter()? { let path: Bound<'py, PyAny> = path_class(py)?.call1((raw_path?,))?; @@ -260,7 +258,7 @@ fn call_route<'py>( let (callable, positional_count) = match handler { Some(handler) => (handler, route.shape.slot_positional), None => { - let (module, function) = route.implementation; + let function: NativeAttr = registry::attribute_named(route.implementation)?; if let Some(extra) = route.implementation_extra { keywords.set_item(extra.name, invocation.value(extra.source)?)?; } @@ -269,7 +267,7 @@ fn call_route<'py>( } else { route.shape.slot_positional }; - (imported(py, module, function)?, count) + (registry::native(py, function)?, count) } }; for (index, field) in route.arguments.iter().enumerate() { @@ -347,7 +345,7 @@ pub(crate) fn dispatch_command<'py>( .call_method0("resolve")? }; call_with( - &imported(py, WAREHOUSES_MODULE, "validate_cli_warehouse")?, + ®istry::native(py, NativeAttr::ValidateCliWarehouse)?, &[ ("cli_warehouse", warehouse.clone()), ("project_dir", project_dir), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs index b6bec490be..ba0bec4ff1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs @@ -17,46 +17,30 @@ use crate::bindings::_helpers::cli::dispatch::{command_facts, dispatch_command}; use crate::bindings::_helpers::cli::launch_state::offer_launch_record; use crate::bindings::_helpers::cli::observability::cli_observability_scope; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::output_capture::records::invocation_context; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_cli::launcher::constants::LAUNCH_RECORD_OPTION; use sqlbuild_core::cli_text::main::format_coded_error::format_coded_error; use sqlbuild_core::cli_text::models::CliStyle; const NO_COLOR_OPTION: &str = "--no-color"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPUTE_LOGS_MODULE: &str = - "sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs"; -const LIFECYCLE_EVENT_MODULE: &str = "sqlbuild.runtime.observability.main.create_lifecycle_event"; -const INVOCATION_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.invocation_scope"; -const EXTERNAL_SCOPE_MODULE: &str = - "sqlbuild.runtime.observability.main.invocation_external_context_scope"; -const OUTPUT_CAPTURE_ERRORS_MODULE: &str = "sqlbuild.runtime.output_capture.exceptions"; -const DIAGNOSTICS_MODULE: &str = "sqlbuild.diagnostics.main.diagnostics_context"; -const TRANSIENT_LINE_MODULE: &str = "sqlbuild._native"; -const SKILLS_MODULE: &str = "sqlbuild._native"; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const LINT_ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const CLI_LOGGER: &str = "sqlbuild.cli"; const UNKNOWN_COMMAND: &str = "unknown"; const DIFF_PUBLICATION_CODE: &str = "C257"; const DIFF_EXECUTION_STATUS: &str = "execution_failed"; const DIFF_OUTCOME_PREFIX: &str = "Query diff outcome "; -/// Expected command failures by module and class, loaded only once a command raised. -const EXPECTED_COMMAND_FAILURES: [(&str, &str); 4] = [ - ( - "sqlbuild.executor.pipeline.exceptions", - "AuditExecutionError", - ), - ("sqlbuild._native", "DiscoveryError"), - ( - "sqlbuild.runtime.execution_limits.exceptions", - "ExecutionDurationLimitError", - ), - ("sqlbuild._native", "SpecConfigError"), +/// Expected staying-Python command failures, loaded only once a command raised. +const EXPECTED_COMMAND_FAILURES: [ExecutorHost; 2] = [ + ExecutorHost::AuditExecutionError, + ExecutorHost::ExecutionDurationLimitError, ]; +/// Expected native command failures. +const EXPECTED_NATIVE_FAILURES: [ErrorId; 2] = [ErrorId::Discovery, ErrorId::SpecConfig]; /// One parsed invocation: its namespace, injected handlers and error colour. struct Invocation<'py> { @@ -113,10 +97,6 @@ fn print_object(value: &Bound<'_, PyAny>, stream: Option<&str>, end: Option<&str Ok(()) } -fn class<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult> { - imported(py, module, name) -} - /// `(code, message, help)` of an expected error as Python objects, as the CLI renders it. fn error_parts<'py>( error: &Bound<'py, PyAny>, @@ -183,13 +163,13 @@ fn write_compile_machine_error( .getattr_opt("location")? .unwrap_or_else(|| py.None().into_bound(py)); let location: Bound<'_, PyAny> = - if location.is_instance(&class(py, CONTRACT_MODELS_MODULE, "SourceLocation")?)? { + if location.is_instance(®istry::class(py, RecordId::SourceLocation)?)? { location } else { py.None().into_bound(py) }; let document: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_compile_error_json")?, + ®istry::native(py, NativeAttr::FormatCompileErrorJson)?, &[ ("code", code), ("message", message), @@ -205,7 +185,7 @@ fn render_diff_error<'py>( fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { call_with( - &imported(py, NATIVE_MODULE, "render_diff_error_json")?, + ®istry::native(py, NativeAttr::RenderDiffErrorJson)?, fields, ) } @@ -262,8 +242,8 @@ fn write_diff_machine_error<'py>( } let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("code", DIFF_PUBLICATION_CODE)?; - let publication: Bound<'py, PyAny> = - class(py, EXCEPTIONS_MODULE, "QueryDiffExecutionError")?.call( + let publication: Bound<'py, PyAny> = registry::error(py, ErrorId::QueryDiffExecution)? + .call( (format!( "failed to publish query diff output: {}", value.str()? @@ -307,19 +287,24 @@ fn exit<'py>(py: Python<'py>, code: i64) -> PyResult> { /// The kind of expected failure `error` is, or `None` when it propagates. fn failure_kind(py: Python<'_>, error: &Bound<'_, PyAny>) -> PyResult> { - if error.is_instance(&class(py, EXCEPTIONS_MODULE, "CliUserError")?)? - || error.is_instance(&class(py, RULES_ERRORS_MODULE, "RulesError")?)? + if error.is_instance(®istry::error(py, ErrorId::CliUser)?)? + || error.is_instance(®istry::error(py, ErrorId::Rules)?)? { return Ok(Some(CommandFailure::User)); } - if error.is_instance(&class(py, LINT_ERRORS_MODULE, "LintError")?)? { + if error.is_instance(®istry::error(py, ErrorId::Lint)?)? { return Ok(Some(CommandFailure::Lint)); } if error.is_instance_of::() { return Ok(Some(CommandFailure::Command)); } - for (module, name) in EXPECTED_COMMAND_FAILURES { - if error.is_instance(&class(py, module, name)?)? { + for failure in EXPECTED_COMMAND_FAILURES { + if error.is_instance(&host::entry(py, failure)?)? { + return Ok(Some(CommandFailure::Command)); + } + } + for native in EXPECTED_NATIVE_FAILURES { + if error.is_instance(®istry::error(py, native)?)? { return Ok(Some(CommandFailure::Command)); } } @@ -335,7 +320,7 @@ fn report_failure<'py>( let py: Python<'py> = invocation.py(); let (fallback_code, log_message) = failure_report(kind); log_failure(py, log_message, &error)?; - let user_error_class: Bound<'py, PyAny> = class(py, EXCEPTIONS_MODULE, "CliUserError")?; + let user_error_class: Bound<'py, PyAny> = registry::error(py, ErrorId::CliUser)?; let is_user_error: bool = error.is_instance(&user_error_class)?; let effective: Bound<'py, PyAny> = if is_user_error { write_diff_machine_error(invocation, &error)?.unwrap_or(error) @@ -343,7 +328,7 @@ fn report_failure<'py>( error }; write_compile_machine_error(invocation, &effective, fallback_code)?; - if effective.is_instance(&class(py, EXCEPTIONS_MODULE, "QueryDiffOutcomeError")?)? { + if effective.is_instance(®istry::error(py, ErrorId::QueryDiffOutcome)?)? { let line: Bound<'py, PyAny> = PyString::new(py, DIFF_OUTCOME_PREFIX) .into_any() .add(effective.getattr("status")?.str()?)?; @@ -367,13 +352,13 @@ fn report_skill_freshness(invocation: &Invocation<'_>, policy: InvocationPolicy) let py: Python<'_> = invocation.py(); let project_dir: Bound<'_, PyAny> = invocation.project_dir()?; let result: Bound<'_, PyAny> = match call_with( - &imported(py, SKILLS_MODULE, "maintain_sqlbuild_skills")?, + ®istry::native(py, NativeAttr::MaintainSqlbuildSkills)?, &[("project_dir", project_dir)], ) { Ok(result) => result, Err(error) => { let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); - if value.is_instance(&class(py, EXCEPTIONS_MODULE, "CliUserError")?)? + if value.is_instance(®istry::error(py, ErrorId::CliUser)?)? || value.is_instance_of::() || value.is_instance_of::() { @@ -395,7 +380,7 @@ fn lifecycle_event<'py>( payload: Bound<'py, PyDict>, ) -> PyResult> { call_with( - &imported(py, LIFECYCLE_EVENT_MODULE, "create_lifecycle_event")?, + &host::entry(py, ExecutorHost::CreateLifecycleEvent)?, &[ ("event_type", PyString::new(py, event_type).into_any()), ("payload", payload.into_any()), @@ -467,13 +452,9 @@ fn handled_dispatch<'py>(invocation: &Invocation<'py>) -> PyResult> = observed_dispatch(invocation, policy); - imported( - py, - TRANSIENT_LINE_MODULE, - "shared_transient_line_coordinator", - )? - .call0()? - .call_method0("clear_active")?; + registry::native(py, NativeAttr::SharedTransientLineCoordinator)? + .call0()? + .call_method0("clear_active")?; let error: PyErr = match dispatched { Ok(exit_code) => return Ok(exit_code), Err(error) => error, @@ -498,10 +479,7 @@ fn external_context(py: Python<'_>) -> PyResult> { match invocation_context(py, None) { Ok(context) => Ok(context), Err(error) - if error.is_instance( - py, - &class(py, OUTPUT_CAPTURE_ERRORS_MODULE, "OutputCaptureInputError")?, - ) => + if error.is_instance(py, &host::entry(py, ExecutorHost::OutputCaptureInputError)?) => { Ok(PyDict::new(py).into_any()) } @@ -551,7 +529,7 @@ fn parsed_invocation<'py>( }, )?; call_with( - &imported(py, COMPUTE_LOGS_MODULE, "dispatch_with_compute_logs")?, + &host::entry(py, FacadeHost::DispatchWithComputeLogs)?, &[ ("args", args), ("identity", identity.clone()), @@ -604,19 +582,15 @@ fn run_cli<'py>( let argv: Bound<'py, PyAny> = offered_launch_record(&argv)?; let external: Bound<'py, PyAny> = external_context(py)?; let invocation_scope: Bound<'py, PyAny> = - imported(py, INVOCATION_SCOPE_MODULE, "invocation_scope")?.call0()?; + host::entry(py, ExecutorHost::InvocationScope)?.call0()?; within(&invocation_scope, |identity| { let external_scope: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_SCOPE_MODULE, - "invocation_external_context_scope", - )?, + &host::entry(py, ExecutorHost::InvocationExternalContextScope)?, &[("external_context", external.clone())], )?; within(&external_scope, |_| { let diagnostics: Bound<'py, PyAny> = call_with( - &imported(py, DIAGNOSTICS_MODULE, "diagnostics_context")?, + &host::entry(py, ExecutorHost::DiagnosticsContext)?, &[("sqlbuild_invocation_id", identity.getattr("invocation_id")?)], )?; within(&diagnostics, |_| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs index be0bb290ed..78d618c392 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs @@ -3,9 +3,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The module attributes holding the offered path's and the withheld flag's `ContextVar`s. const OFFERED_ATTRIBUTE: &str = "_launch_record_offered"; const WITHHELD_ATTRIBUTE: &str = "_launch_record_withheld"; @@ -15,26 +15,26 @@ const WITHHELD_NAME: &str = "sqlbuild_launch_record_withheld"; /// Remember where `sqb` looks for this invocation's launch record, or that it offered none. pub(crate) fn offer_launch_record(py: Python<'_>, path: Option>) -> PyResult<()> { let path: Bound<'_, PyAny> = path.unwrap_or_else(|| py.None().into_bound(py)); - imported(py, NATIVE_MODULE, OFFERED_ATTRIBUTE)?.call_method1("set", (path,))?; - imported(py, NATIVE_MODULE, WITHHELD_ATTRIBUTE)?.call_method1("set", (false,))?; + registry::native(py, NativeAttr::LaunchRecordOffered)?.call_method1("set", (path,))?; + registry::native(py, NativeAttr::LaunchRecordWithheld)?.call_method1("set", (false,))?; Ok(()) } /// Withhold the launch record: `sqb` cannot replay what this invocation also depends on. pub(crate) fn withhold_launch_record(py: Python<'_>) -> PyResult<()> { - imported(py, NATIVE_MODULE, WITHHELD_ATTRIBUTE)?.call_method1("set", (true,))?; + registry::native(py, NativeAttr::LaunchRecordWithheld)?.call_method1("set", (true,))?; Ok(()) } /// The offered launch record path, unless an input outside its key was read. pub(crate) fn launch_record_path(py: Python<'_>) -> PyResult> { - if imported(py, NATIVE_MODULE, WITHHELD_ATTRIBUTE)? + if registry::native(py, NativeAttr::LaunchRecordWithheld)? .call_method0("get")? .is_truthy()? { return Ok(py.None().into_bound(py)); } - imported(py, NATIVE_MODULE, OFFERED_ATTRIBUTE)?.call_method0("get") + registry::native(py, NativeAttr::LaunchRecordOffered)?.call_method0("get") } fn context_variable<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs index d12c2955c8..777b09895a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs @@ -7,11 +7,8 @@ use pyo3::{IntoPyObjectExt, pyclass, pymethods}; use sqlbuild_cli::argv::main::cli_namespace_defaults::cli_namespace_defaults; use sqlbuild_cli::argv::models::NamespaceDefault; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_LINEAGE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Typed namespace for all CLI arguments across all commands. #[pyclass(module = "sqlbuild._native", dict, frozen)] @@ -26,10 +23,10 @@ fn default_object(py: Python<'_>, default: NamespaceDefault) -> PyResult Ok(PyList::empty(py).into_any()), NamespaceDefault::EmptyDict => Ok(PyDict::new(py).into_any()), NamespaceDefault::CompileLineageFast => { - imported(py, COMPILE_LINEAGE_MODULE, "CompileLineageMode")?.getattr("FAST") + registry::class(py, RecordId::CompileLineageMode)?.getattr("FAST") } NamespaceDefault::ColumnLineageRich => { - imported(py, NATIVE_RECORDS_MODULE, "ColumnLineageMode")?.getattr("RICH") + registry::class(py, RecordId::ColumnLineageMode)?.getattr("RICH") } } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs index f90097dd99..798536cf2e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs @@ -15,16 +15,13 @@ use sqlbuild_core::event_export::constants::DEFAULT_EVENT_EXPORT_SHUTDOWN_TIMEOU use crate::bindings::_helpers::cli::launch_state::withhold_launch_record; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EVENT_DISPATCHER_MODULE: &str = "sqlbuild.runtime.observability.classes.event_dispatcher"; -const DISPATCHER_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.dispatcher_scope"; -const IDENTITY_MODULE: &str = "sqlbuild.runtime.observability.main.current_execution_identity"; -const EXTENSIONS_MODULE: &str = "sqlbuild.compiler.discovery.main.runtime_extensions"; -const OUTPUT_CAPTURE_MODULE: &str = - "sqlbuild.runtime.output_capture.main.configured_output_capture_scope"; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const OBSERVABILITY_LOGGER: &str = "sqlbuild.cli.observability"; const DEBUG_COMMAND: &str = "debug"; const LINEAGE_COMMAND: &str = "lineage"; @@ -46,10 +43,7 @@ fn log_debug<'py>( ("message", PyString::new(py, message).into_any()), ]; pairs.extend(fields.iter().cloned()); - call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, - &pairs, - )?; + call_with(&host::entry(py, ExecutorHost::LogDebugEvent)?, &pairs)?; Ok(()) } @@ -329,7 +323,7 @@ impl Observed { let mut command_output_sinks: bool = false; if args.getattr("command")?.ne(LINEAGE_COMMAND)? { let extensions: Bound<'_, PyAny> = call_with( - &imported(py, EXTENSIONS_MODULE, "discover_runtime_extensions")?, + &host::entry(py, UserCodeHost::DiscoverRuntimeExtensions)?, &[("project_dir", project_dir.clone())], )?; let event_exporters: Bound<'_, PyAny> = extensions.getattr("event_exporters")?; @@ -346,7 +340,7 @@ impl Observed { }; let timeout: Bound<'_, PyAny> = timeout.into_bound_py_any(py)?; let delivery: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "EventExporterDispatcher")?, + ®istry::native(py, NativeAttr::EventExporterDispatcher)?, &[ ("shutdown_timeout_seconds", timeout), ( @@ -360,7 +354,7 @@ impl Observed { ], )?; let scope: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "EventExporterCommandScope")?, + ®istry::native(py, NativeAttr::EventExporterCommandScope)?, &[("dispatcher", delivery.clone())], )?; self.exporter_scope = Some(scope.clone().unbind()); @@ -380,11 +374,11 @@ impl Observed { } } self.enter( - imported(py, DISPATCHER_SCOPE_MODULE, "dispatcher_scope")? - .call1((self.dispatcher.bind(py),))?, + host::entry(py, ExecutorHost::DispatcherScope)?.call1((self.dispatcher.bind(py),))?, )?; self.enter( - imported(py, NATIVE_MODULE, "terminal_event_index_scope")?.call1((terminal_index,))?, + registry::native(py, NativeAttr::TerminalEventIndexScopeFunction)? + .call1((terminal_index,))?, )?; let Some(scope) = self .exporter_scope @@ -393,12 +387,15 @@ impl Observed { else { return Ok(()); }; - self.enter(imported(py, NATIVE_MODULE, "event_exporter_command_scope")?.call1((&scope,))?)?; + self.enter( + registry::native(py, NativeAttr::EventExporterCommandScopeFunction)? + .call1((&scope,))?, + )?; let identity: Bound<'_, PyAny> = - imported(py, IDENTITY_MODULE, "current_execution_identity")?.call0()?; + host::entry(py, ExecutorHost::CurrentExecutionIdentity)?.call0()?; if !identity.is_none() && command_output_sinks { self.enter(call_with( - &imported(py, OUTPUT_CAPTURE_MODULE, "configured_output_capture_scope")?, + &host::entry(py, ExecutorHost::ConfiguredOutputCaptureScope)?, &[ ("exporter_scope", scope), ("identity", identity), @@ -434,14 +431,14 @@ impl CliObservabilityScope { fn __enter__(&self, py: Python<'_>) -> PyResult> { let args: &Bound<'_, PyAny> = self.args.bind(py); let dispatcher: Bound<'_, PyAny> = call_with( - &imported(py, EVENT_DISPATCHER_MODULE, "EventDispatcher")?, + &host::entry(py, ExecutorHost::EventDispatcher)?, &[( "health_callback", wrap_pyfunction!(log_dispatch_failure, py)?.into_any(), )], )?; let terminal_index: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "TerminalEventIndex")?.call0()?; + registry::native(py, NativeAttr::TerminalEventIndex)?.call0()?; let subscribe = |subscriber: Bound<'_, PyAny>| -> PyResult> { Ok(call_with( &dispatcher.getattr("subscribe_lifecycle")?, @@ -456,7 +453,7 @@ impl CliObservabilityScope { .unbind()) }; let unsubscribe_terminal_index: Py = subscribe(terminal_index.getattr("consume")?)?; - let machine_output: bool = imported(py, NATIVE_MODULE, "machine_output_mode")? + let machine_output: bool = registry::native(py, NativeAttr::MachineOutputMode)? .call1((args,))? .is_truthy()?; let debug: Bound<'_, PyAny> = args.getattr("debug")?; @@ -466,7 +463,7 @@ impl CliObservabilityScope { && !args.getattr("no_color")?.is_truthy()? && stdout_supports_color(py)?; let projector: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "NativeProgressProjector")?, + ®istry::native(py, NativeAttr::NativeProgressProjector)?, &[ ( "stream", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs index 9348de724d..bf1bb54efb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs @@ -17,13 +17,12 @@ use sqlbuild_core::progress::models::{ use sqlbuild_core::terminal_events::models::TerminalRequest; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// The module attribute holding the `ContextVar` of the installed projector. const CONTEXT_ATTRIBUTE: &str = "_native_progress_projector_context"; const CONTEXT_NAME: &str = "sqlbuild_native_progress_projector"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COORDINATOR_MODULE: &str = "sqlbuild._native"; -const COORDINATOR_FUNCTION: &str = "shared_transient_line_coordinator"; const CLAIM_OPTIONS: (&[&str], &[&str]) = ( &["resource_name"], &["resource_id", "resource_attempt_id", "attempt_number"], @@ -134,9 +133,7 @@ fn progress_event(event: &Bound<'_, PyAny>) -> PyResult { /// The coordinator shared by every terminal writer in this process. pub(crate) fn shared_coordinator(py: Python<'_>) -> PyResult> { - py.import(COORDINATOR_MODULE)? - .getattr(COORDINATOR_FUNCTION)? - .call0() + registry::native(py, NativeAttr::SharedTransientLineCoordinator)?.call0() } fn persist(coordinator: &Bound<'_, PyAny>, stream: &Bound<'_, PyAny>, text: &str) -> PyResult<()> { @@ -312,7 +309,7 @@ impl NativeProgressProjector { } fn context_variable(py: Python<'_>) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(CONTEXT_ATTRIBUTE) + registry::native(py, NativeAttr::NativeProgressProjectorContext) } /// Return the projector installed for the current CLI invocation. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs index 6fb23a1220..a5e428ab38 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs @@ -16,9 +16,9 @@ use sqlbuild_core::progress::models::{ExpectationStep, InspectionRecord}; use sqlbuild_core::progress::types::DifferenceSample; use crate::bindings::_helpers::cli::progress::write_persistent; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; - -const INSPECTION_SINK_MODULE: &str = "sqlbuild.adapter.relations.main.inspection_query_sink"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, within}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; const HEADER_OPTIONS: (&[&str], &[&str]) = ( &["stream", "command", "target", "concurrency", "use_color"], @@ -174,8 +174,8 @@ pub(crate) fn with_verbose_inspection<'py, T>( } let writer: Bound<'py, VerboseInspectionWriter> = Bound::new(py, VerboseInspectionWriter::new())?; - let sink: Bound<'py, PyAny> = imported(py, INSPECTION_SINK_MODULE, "inspection_query_sink")? - .call1((writer.getattr("write")?,))?; + let sink: Bound<'py, PyAny> = + host::entry(py, AdapterHost::InspectionQuerySink)?.call1((writer.getattr("write")?,))?; let value: T = within(&sink, |_| body())?; writer.get().write_total(py)?; Ok(value) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs index dc63e2b38a..26b988e82b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -24,19 +24,13 @@ use crate::bindings::_helpers::clone::clone_execution::{ use crate::bindings::_helpers::clone::clone_pipeline::{ClonePipeline, prepared_clone_pipeline}; use crate::bindings::_helpers::clone::clone_progress::{clone_item_text, write_line}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const NO_DESTINATION: (&str, &str) = ( "C406", "clone requires --to when the project has no active target", @@ -60,7 +54,7 @@ fn user_error(py: Python<'_>, (code, message): (&str, &str)) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -84,12 +78,12 @@ fn progress_reporter<'py>( let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("stream", stream)?; options.set_item("use_color", style.use_color)?; - imported(py, NATIVE_MODULE, "PlanningProgressReporter")?.call((), Some(&options)) + registry::native(py, NativeAttr::PlanningProgressReporter)?.call((), Some(&options)) } fn lifecycle<'py>(py: Python<'py>, (kind, name): (&str, &str)) -> PyResult> { call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", text(py, kind)), ("operation_name", text(py, name)), @@ -110,7 +104,7 @@ fn target_names(discovered: &Bound<'_, PyAny>) -> PyResult> { fn clone_policy(discovered: &Bound<'_, PyAny>, (target, flag): (&str, &str)) -> PyResult { let py: Python<'_> = discovered.py(); call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -135,12 +129,12 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = progress_reporter(&stream, style)?; progress.call_method1("start", ("Discovering project...",))?; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; progress.call_method1("complete", ("Discovered project.",))?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -150,7 +144,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("destination_target_name")?; if destination.is_none() { destination = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -172,7 +166,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -197,7 +191,7 @@ fn connect<'py>( ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py: Python<'py> = request.py(); let config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_target_connection_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConnectionConfig)?, &[ ("discovered_inputs", invocation.discovered.clone()), ("project_dir", invocation.project_dir.clone()), @@ -244,22 +238,18 @@ fn prepare<'py>( within(&lifecycle(py, ("project", "project_compile"))?, |_| { progress.call_method1("on_progress", ("Preparing clone plan...",))?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.project_dir.clone()), ("discovered_inputs", invocation.discovered.clone()), ], )?; let destination_connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineConnection")?, + ®istry::class(py, RecordId::ClonePipelineConnection)?, &[("config", config.clone()), ("handle", connection.clone())], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineOptions")?, + ®istry::class(py, RecordId::ClonePipelineOptions)?, &[ ("no_sql_validation", request.getattr("no_sql_validation")?), ("select", request.getattr("select")?), @@ -343,14 +333,14 @@ fn run_clone_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "format_clone_execution_json")?, + ®istry::native(py, NativeAttr::FormatCloneExecutionJson)?, &[ ("result", result.clone()), ("resource_types_by_name", resource_types.into_any()), ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", PyBool::new(py, false).to_owned().into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs index a0f6fd6b81..60cceba449 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs @@ -13,19 +13,12 @@ use crate::bindings::_helpers::clone::clone_progress::{ CloneFingerprintReporter, CloneItemPrinter, }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const CLONE_MODELS: &str = "sqlbuild.executor.clone.models"; -const RETENTION_REQUESTS_MODULE: &str = "sqlbuild.executor.clone.main._build_retention_requests"; -const TRANSIENT_MODELS_MODULE: &str = - "sqlbuild.executor.clone.main._build_destination_transient_models"; -const FINGERPRINTING_MODULE: &str = "sqlbuild.executor.clone.main._fingerprinting"; -const EXECUTE_MODULE: &str = "sqlbuild.executor.clone.main.execute"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const DESTINATION_ENTRY_GROUPS: [&str; 4] = [ "destination_source_entries", "destination_model_entries", @@ -121,9 +114,9 @@ fn dependency_locations<'py>( } else { plan.getattr("source_map")? }; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS, "CompiledObjectKey")?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let location_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS, "CompiledRelationLocation")?; + registry::class(py, RecordId::CompiledRelationLocation)?; let source_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?; for pair in source_map.call_method0("items")?.try_iter()? { @@ -157,7 +150,7 @@ fn dependency_locations<'py>( fn target_config<'py>(invocation: &Invocation<'py>) -> PyResult> { let py: Python<'py> = invocation.adapter.py(); call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ( "project_config", @@ -191,11 +184,7 @@ fn execution_input<'py>( .map(|entry| entry?.getattr("name")) .collect::>()?; let retention: Bound<'py, PyAny> = call_with( - &imported( - py, - RETENTION_REQUESTS_MODULE, - "build_destination_retention_requests", - )?, + &host::entry(py, ExecutorHost::BuildDestinationRetentionRequests)?, &[ ("project", project.clone()), ( @@ -214,9 +203,9 @@ fn execution_input<'py>( )?; let allow_decrease: bool = config .getattr("time_travel_retention_decrease")? - .eq(imported(py, CONTRACT_TYPES, "RetentionDecreasePolicy")?.getattr("ALLOW")?)?; + .eq(registry::class(py, RecordId::RetentionDecreasePolicy)?.getattr("ALLOW")?)?; let source_entries: Bound<'py, PyAny> = call_with( - &imported(py, CLONE_MODELS, "CloneSourceEntries")?, + &host::entry(py, ExecutorHost::CloneSourceEntries)?, &[ ("origin", pipeline.getattr("origin_source_entries")?), ( @@ -226,11 +215,7 @@ fn execution_input<'py>( ], )?; let transient: Bound<'py, PyAny> = call_with( - &imported( - py, - TRANSIENT_MODELS_MODULE, - "build_destination_transient_models", - )?, + &host::entry(py, ExecutorHost::BuildDestinationTransientModels)?, &[("project", project.clone())], )?; let mut fields: Vec<(&str, Bound<'py, PyAny>)> = vec![("source_entries", source_entries)]; @@ -268,7 +253,10 @@ fn execution_input<'py>( ), ("on_item", on_item), ]); - call_with(&imported(py, CLONE_MODELS, "CloneExecutionInput")?, &fields) + call_with( + &host::entry(py, ExecutorHost::CloneExecutionInput)?, + &fields, + ) } /// Copy fingerprints of the cloned relations, reporting what was not confirmed on failure. @@ -309,7 +297,7 @@ fn copy_fingerprints<'py>( ("on_progress", reporter.clone().into_any()), ]); let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "clone").into_any()), ( @@ -320,7 +308,7 @@ fn copy_fingerprints<'py>( )?; let copied: PyResult> = within(&lifecycle, |_| { call_with( - &imported(py, FINGERPRINTING_MODULE, "copy_clone_fingerprints")?, + &host::entry(py, ExecutorHost::CopyCloneFingerprints)?, &fields, ) }); @@ -340,7 +328,7 @@ pub(crate) fn execute_clone_plan<'py>( let py: Python<'py> = pipeline.py(); let started: Instant = Instant::now(); let event_writer: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ExecutionEventWriter")?, + ®istry::native(py, NativeAttr::ExecutionEventWriter)?, &[("path", request.getattr("event_output_path")?)], )?; let printer: Py = Py::new( @@ -363,7 +351,7 @@ pub(crate) fn execute_clone_plan<'py>( ), )?; call_with( - &imported(py, EXECUTE_MODULE, "execute_clone")?, + &host::entry(py, ExecutorHost::ExecuteClone)?, &[("inputs", input)], ) })(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs index ffe3319388..4ce45f6f2b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs @@ -5,13 +5,11 @@ use pyo3::types::{PyDict, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CLONE_PLANNING_MODULE: &str = "sqlbuild._native"; /// The `ClonePipelineResult` fields after the two projects, in `run_clone_planning` order. const PLANNED_FIELDS: [&str; 8] = [ "clone_plan", @@ -41,7 +39,7 @@ fn compiled_environment<'py>( let py: Python<'py> = target_name.py(); let options: &Bound<'py, PyAny> = &pipeline.options; call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", pipeline.discovered.clone()), ("adapter", pipeline.adapter.clone()), @@ -54,7 +52,7 @@ fn compiled_environment<'py>( ( "analysis_selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", options.getattr("no_cache")?)], )?, ), @@ -110,7 +108,7 @@ pub(crate) fn prepared_clone_pipeline<'py>( let destination_source_project: Bound<'py, PyAny> = source_project(pipeline, &destination_project, destination_target)?; let planned: Bound<'py, PyTuple> = call_with( - &imported(py, CLONE_PLANNING_MODULE, "run_clone_planning")?, + ®istry::native(py, NativeAttr::RunClonePlanning)?, &[ ("project", destination_project.clone()), ("select", pipeline.options.getattr("select")?), @@ -131,7 +129,7 @@ pub(crate) fn prepared_clone_pipeline<'py>( fields.push((*name, planned.get_item(index)?)); } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineResult")?, + ®istry::class(py, RecordId::ClonePipelineResult)?, &fields, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs index 3edb6eb057..e367ed907b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs @@ -6,11 +6,12 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const REGISTRY_MODULE: &str = "sqlbuild.adapter.discovery.main.resolve_adapter"; -const ADAPTER_ERRORS_MODULE: &str = "sqlbuild.adapter.contract.exceptions"; -const CLI_ERRORS_MODULE: &str = "sqlbuild._native"; const ADAPTER_CONFIGURATION_CODE: &str = "C601"; /// `CliUserError(error.message, code="C601", help=error.help)` raised from `error`. @@ -20,7 +21,7 @@ fn cli_adapter_error(py: Python<'_>, error: &PyErr) -> PyResult { keywords.set_item("code", ADAPTER_CONFIGURATION_CODE)?; keywords.set_item("help", value.getattr("help")?)?; let raised: PyErr = PyErr::from_value( - imported(py, CLI_ERRORS_MODULE, "CliUserError")? + registry::error(py, ErrorId::CliUser)? .call((value.getattr("message")?,), Some(&keywords))?, ); raised.set_cause(py, Some(error.clone_ref(py))); @@ -34,19 +35,14 @@ pub(crate) fn resolve_cli_adapter<'py>( ) -> PyResult> { let py: Python<'py> = adapter_name.py(); let resolved: PyResult> = call_with( - &imported(py, REGISTRY_MODULE, "resolve_adapter")?, + &host::entry(py, AdapterHost::ResolveAdapter)?, &[ ("adapter_name", adapter_name.clone()), ("project_dir", project_dir.clone()), ], ); match resolved { - Err(error) - if error.is_instance( - py, - &imported(py, ADAPTER_ERRORS_MODULE, "AdapterUserError")?, - ) => - { + Err(error) if error.is_instance(py, &host::entry(py, AdapterHost::AdapterUserError)?) => { Err(cli_adapter_error(py, &error)?) } resolved => resolved, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs index 0530f4ec01..2ccae8393e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs @@ -18,13 +18,11 @@ use crate::bindings::_helpers::command_runtime::warehouses::{WarehouseInputs, ap use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const DBT_PROFILE_MODULE: &str = "sqlbuild._native"; -const DBT_ERRORS_MODULE: &str = "sqlbuild.integrations.dbt.exceptions"; -const EFFECTIVE_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; const CONNECTION_OPTIONS: (&[&str], &[&str]) = ( &["raw_config", "project_dir", "adapter_name"], &["discovered_inputs", "cli_vars", "selected_target"], @@ -52,7 +50,7 @@ fn warn(py: Python<'_>, message: &str) -> PyResult<()> { } fn dbt_profile_error(py: Python<'_>) -> PyErr { - match imported(py, DBT_ERRORS_MODULE, "DbtProfileError") + match host::entry(py, ThirdPartyHost::DbtProfileError) .and_then(|class| class.call1((DBT_PROFILE_REQUIRES_PROJECT,))) { Ok(instance) => PyErr::from_value(instance), @@ -69,7 +67,7 @@ pub(crate) fn with_dbt_profile<'py>( ) -> PyResult> { let py: Python<'py> = raw_config.py(); let profile: Bound<'py, PyAny> = call_with( - &imported(py, DBT_PROFILE_MODULE, "resolve_raw_dbt_profile_connection")?, + ®istry::native(py, NativeAttr::ResolveRawDbtProfileConnection)?, &[ ("raw_config", config.clone().into_any()), ("project_dir", project_dir.clone()), @@ -208,7 +206,7 @@ fn require_configured_connection( return Ok(()); } let target_name: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -223,7 +221,7 @@ fn require_configured_connection( return Err(raised(py, missing_connection_error(&project_file, None))); } let target_config: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", project_config), ("local_config", local_config), @@ -252,11 +250,7 @@ pub(crate) fn project_connection_config<'py>( require_configured_connection(discovered_inputs, project_dir, selected_target)?; } let raw_config: Bound<'py, PyDict> = call_with( - &imported( - py, - EFFECTIVE_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered_inputs.clone()), ("selected_target", selected_target.clone()), @@ -265,7 +259,7 @@ pub(crate) fn project_connection_config<'py>( )? .cast_into()?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ( "project_config", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs index 9d52b3acc5..cb825263ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs @@ -6,8 +6,8 @@ use pyo3::types::PyBool; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; - -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// The project directory, discovered inputs, adapter context, colour and progress stream. type InvocationContext<'py> = ( @@ -31,7 +31,7 @@ pub(crate) fn resolve_adapter_connection_context<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -39,7 +39,7 @@ pub(crate) fn resolve_adapter_connection_context<'py>( )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("project_dir", project_dir.clone()), @@ -49,7 +49,7 @@ pub(crate) fn resolve_adapter_connection_context<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "AdapterConnectionContext")?, + ®istry::class(py, RecordId::AdapterConnectionContext)?, &[ ("adapter_name", adapter_name), ("adapter", adapter), @@ -70,7 +70,7 @@ fn invocation_context<'py>( requested }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let adapter_context: Bound<'py, PyAny> = resolve_adapter_connection_context( @@ -132,7 +132,7 @@ pub(crate) fn resolve_reported_command_invocation<'py>( fields.push(( "connection_progress", call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", adapter_context.getattr("adapter_name")?), ("stream", stream.clone()), @@ -143,7 +143,7 @@ pub(crate) fn resolve_reported_command_invocation<'py>( fields.push(( "planning_progress", call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", boolean(py, *use_color)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs index 33da4de19c..37e5a2be94 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs @@ -4,11 +4,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_config::command_connection::errors::CommandConnectionError; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const CLI_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// The `CliUserError` or `ProjectConfigError` for `error`. pub(crate) fn raised(py: Python<'_>, error: CommandConnectionError) -> PyErr { @@ -22,11 +19,10 @@ pub(crate) fn raised(py: Python<'_>, error: CommandConnectionError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; keywords.set_item("help", help)?; - imported(py, CLI_ERRORS_MODULE, "CliUserError")? - .call((message,), Some(&keywords))? + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&keywords))? } CommandConnectionError::ProjectConfig(message) => { - imported(py, DISCOVERY_ERRORS_MODULE, "ProjectConfigError")?.call1((message,))? + registry::error(py, ErrorId::ProjectConfig)?.call1((message,))? } }; Ok(PyErr::from_value(instance)) @@ -37,18 +33,15 @@ pub(crate) fn raised(py: Python<'_>, error: CommandConnectionError) -> PyErr { /// Whether `error` is a `CompileInputError` or a `CliUserError`, the failures resolution reports. pub(crate) fn expected_resolution_error(py: Python<'_>, error: &PyErr) -> PyResult { Ok( - error.is_instance(py, &imported(py, CLI_ERRORS_MODULE, "CliUserError")?) - || error.is_instance( - py, - &imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, - ), + error.is_instance(py, ®istry::error(py, ErrorId::CliUser)?) + || error.is_instance(py, ®istry::error(py, ErrorId::CompileInput)?), ) } /// The message a resolution error shows: `CliUserError.message`, else `str(error)`. pub(crate) fn resolution_message(py: Python<'_>, error: &PyErr) -> PyResult { let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); - if error.is_instance(py, &imported(py, CLI_ERRORS_MODULE, "CliUserError")?) { + if error.is_instance(py, ®istry::error(py, ErrorId::CliUser)?) { return value.getattr("message")?.str()?.extract(); } value.str()?.extract() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs index 25cd0771dd..4806e45ac7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs @@ -9,17 +9,15 @@ use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_config::command_connection::constants::COMMAND_WAREHOUSE_GROUPS; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ENTRY_MODELS: &str = "sqlbuild._native"; -const WAREHOUSE_GROUP: (&str, &str) = ("sqlbuild._native", "WarehouseGroup"); -const COMMAND_TYPES: &str = "sqlbuild._native"; const CONTEXT_ATTRIBUTE: &str = "_command_warehouse_scope_context"; const CONTEXT_NAME: &str = "sqlbuild_command_warehouse_scope"; fn context_variable(py: Python<'_>) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(CONTEXT_ATTRIBUTE) + registry::native(py, NativeAttr::CommandWarehouseScopeContext) } /// Installs one command's warehouse scope for the duration of a `with` block. @@ -67,7 +65,7 @@ pub(crate) fn command_warehouse_scope<'py>( let group: Bound<'py, PyAny> = if command.is_none() { py.None().into_bound(py) } else { - let name: String = imported(py, COMMAND_TYPES, "CliCommand")? + let name: String = registry::class(py, RecordId::CliCommand)? .call1((command,))? .str()? .to_string(); @@ -76,7 +74,7 @@ pub(crate) fn command_warehouse_scope<'py>( .find(|(candidate, _)| *candidate == name) .and_then(|(_, group)| *group) { - Some(group) => imported(py, WAREHOUSE_GROUP.0, WAREHOUSE_GROUP.1)?.call1((group,))?, + Some(group) => registry::class(py, RecordId::WarehouseGroup)?.call1((group,))?, None => py.None().into_bound(py), } }; @@ -85,7 +83,7 @@ pub(crate) fn command_warehouse_scope<'py>( None => PyDict::new(py).into_any(), }; let scope: Bound<'py, PyAny> = call_with( - &imported(py, ENTRY_MODELS, "CommandWarehouseScope")?, + ®istry::class(py, RecordId::CommandWarehouseScope)?, &[ ("group", group), ("cli_warehouse", cli_warehouse.clone()), @@ -106,7 +104,7 @@ pub(crate) fn current_command_warehouse_scope(py: Python<'_>) -> PyResult( source: &str, ) -> PyResult> { call_with( - &imported(py, ENTRY_MODELS, "ResolvedWarehouse")?, + ®istry::class(py, RecordId::ResolvedWarehouse)?, &[ ( "warehouse", @@ -140,7 +137,7 @@ pub(crate) fn find_authored<'py>( let project_config: Bound<'py, PyAny> = inputs.discovered_inputs.getattr("project_config")?; let local_config: Bound<'py, PyAny> = inputs.discovered_inputs.getattr("local_config")?; let target_name: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -177,11 +174,7 @@ fn expand_authored( ) -> PyResult { let py: Python<'_> = found.group.py(); let runtime: Bound<'_, PyAny> = call_with( - &imported( - py, - EFFECTIVE_RUNTIME_MODULE, - "build_effective_runtime_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", inputs.discovered_inputs.clone()), ("selected_target", inputs.selected_target.clone()), @@ -221,11 +214,7 @@ fn session_warehouse_support( project_dir: &Bound<'_, PyAny>, ) -> PyResult { call_with( - &imported( - py, - SESSION_WAREHOUSE_MODULE, - "adapter_supports_session_warehouse", - )?, + &host::entry(py, AdapterHost::AdapterSupportsSessionWarehouse)?, &[ ("adapter_name", adapter_name.clone()), ("project_dir", project_dir.clone()), @@ -296,7 +285,7 @@ fn validate_cli_warehouse( checked_cli_warehouse(py, &cli_warehouse)?; let discovered: Bound<'_, PyAny> = discover_project_configuration(py, &project_dir)?; let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs index ebd1627a41..fd1f70a8ff 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs @@ -4,9 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyString}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const CURSOR_INPUTS_KEY: &str = "cursor_inputs"; const FILTER_ROLE: &str = "filter"; @@ -94,7 +94,7 @@ pub(crate) fn resolve_cursor_input_roles<'py>( } } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CursorInputRoles")?, + ®istry::class(py, RecordId::CursorInputRoles)?, &[ ("filter_inputs", filter.into_any()), ("watermark_inputs", watermark.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs index 30b2b65cc1..725d880c7f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs @@ -5,22 +5,17 @@ use pyo3::pyfunction; use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::declaration_scope_resolver; use crate::bindings::_helpers::scopes::scope_index::open_native_scope_index; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; use crate::bindings::_helpers::scopes::scope_validation::validate_scope_index; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_EXPECTED_MODELS_SCAN_ALGORITHM; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const SCAN_CACHE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; - fn compile_error(py: Python<'_>, message: Bound<'_, PyAny>) -> PyResult { Ok(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )) } @@ -34,7 +29,7 @@ fn scope_build<'py>( let resolver: Bound<'py, PyAny> = declaration_scope_resolver(discovered, &index, (loaded_macros, &lookup))?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeclarationScopeBuild")?, + ®istry::class(py, RecordId::DeclarationScopeBuild)?, &[ ("loaded_macros", loaded_macros.clone()), ("index", index), @@ -55,7 +50,7 @@ pub(crate) fn build_declaration_scope<'py>( ) -> PyResult> { let py: Python<'py> = discovered_inputs.py(); let scan_cache: Bound<'py, PyAny> = call_with( - &imported(py, SCAN_CACHE_MODULE, "SqlTestScanCache")?, + ®istry::native(py, NativeAttr::SqlTestScanCache)?, &[( "cache_dir", compile_cache_dir.unwrap_or_else(|| py.None().into_bound(py)), @@ -63,7 +58,7 @@ pub(crate) fn build_declaration_scope<'py>( )?; let test_files: Bound<'py, PyAny> = discovered_inputs.getattr("test_files")?; let scenario_files: Bound<'py, PyAny> = discovered_inputs.getattr("scenario_files")?; - imported(py, NATIVE_MODULE, "validate_authored_sql_test_ctes")?.call1(( + registry::native(py, NativeAttr::ValidateAuthoredSqlTestCtes)?.call1(( &test_files, &scenario_files, sql_lexical_syntax.getattr("native_mapping")?, @@ -75,10 +70,7 @@ pub(crate) fn build_declaration_scope<'py>( .into_any(); let validated: Result<(), PyErr> = validate_scope_index(&native.getattr("index")?); if let Err(error) = validated { - if !error.is_instance( - py, - &imported(py, SCOPE_ERRORS_MODULE, "ScopeValidationError")?, - ) { + if !error.is_instance(py, ®istry::error(py, ErrorId::ScopeValidation)?) { return Err(error); } let raised: PyErr = compile_error(py, error.value(py).str()?.into_any())?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs index 0a524f17fa..174e0e20b9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs @@ -13,10 +13,9 @@ use crate::bindings::_helpers::contracts::spec_resolution::{ use crate::bindings::_helpers::model_config::template_data::{ expanded_effective_vars, expanded_template_data, run_id, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const CONNECTION_LABEL: &str = "effective connection"; /// Merge base project connection with the selected environment overrides. @@ -30,12 +29,12 @@ pub(crate) fn build_effective_connection<'py>( ) -> PyResult> { let py: Python<'py> = project_config.py(); let (connection, error): (Bound<'py, PyAny>, Option) = - imported(py, NATIVE_MODULE, "effective_config_connection")? + registry::native(py, NativeAttr::EffectiveConfigConnection)? .call1((project_config, local_config, target_config))? .extract()?; if connection.is_none() { - let raised: Bound<'py, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call1((error.unwrap_or_default(),))?; + let raised: Bound<'py, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call1((error.unwrap_or_default(),))?; return Err(PyErr::from_value(raised)); } expanded_template_data( @@ -60,13 +59,13 @@ pub(crate) fn build_effective_settings<'py>( local_config: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_config.py(); - let settings_class: Bound<'py, PyAny> = imported(py, CONTRACT_MODELS_MODULE, "SettingsConfig")?; + let settings_class: Bound<'py, PyAny> = registry::class(py, RecordId::SettingsConfig)?; let names: Vec> = imported(py, "dataclasses", "fields")? .call1((&settings_class,))? .try_iter()? .map(|field| field?.getattr("name")) .collect::>()?; - let values: Bound<'py, PyDict> = imported(py, NATIVE_MODULE, "effective_config_settings")? + let values: Bound<'py, PyDict> = registry::native(py, NativeAttr::EffectiveConfigSettings)? .call1(( ( project_config.getattr("settings")?, @@ -89,7 +88,7 @@ pub(crate) fn build_effective_vars<'py>( cli_vars: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_config.py(); - let merged: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "effective_config_vars")? + let merged: Bound<'py, PyAny> = registry::native(py, NativeAttr::EffectiveConfigVars)? .call1(((project_config, local_config), target_config, cli_vars))?; expanded_effective_vars(merged.downcast()?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs index 42bf143684..6f2fcef8ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs @@ -1,20 +1,15 @@ //! The native config builder, validator and inherited storage policies of one compile. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::compile_attachment::storage_policies::inherited_storage; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compiled_project::loop_classes::table_backed; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Capture the project inputs every model's native config build and validation read. #[pyfunction] @@ -30,21 +25,25 @@ pub(crate) fn native_model_config_session<'py>( let materialization_defaults: Bound<'py, PyAny> = if declared.is_truthy()? { declared } else { - imported(py, CONTRACT_MODELS_MODULE, "MaterializationDefaultsConfig")?.call0()? + registry::class(py, RecordId::MaterializationDefaultsConfig)?.call0()? }; let hook_entries: Bound<'py, PyTuple> = PyTuple::new( py, - ["SqlHookEntry", "NamedSqlHookEntry", "PythonHookEntry"] - .iter() - .map(|name| shape_class_at(py, DISCOVERY_MODELS_MODULE, name)) - .collect::>>()?, + [ + RecordId::SqlHookEntry, + RecordId::NamedSqlHookEntry, + RecordId::PythonHookEntry, + ] + .iter() + .map(|class| registry::class(py, *class)) + .collect::>>()?, )?; let target_namespace: Bound<'py, PyAny> = if target.is_none() { target.clone() } else { PyTuple::new(py, [target.getattr("database")?, target.getattr("schema")?])?.into_any() }; - let builder: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeModelConfigBuilder")? + let builder: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeModelConfigBuilder)? .call1(( ( project_config.getattr("defaults")?, @@ -69,7 +68,7 @@ pub(crate) fn native_model_config_session<'py>( inherited.set_item(&materialized, storage)?; } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NativeModelConfigSession")?, + ®istry::class(py, RecordId::NativeModelConfigSession)?, &[ ("path_defaults", project_config.getattr("path_defaults")?), ("builder", builder), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs index a8a4f3bac9..96063ddc63 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs @@ -16,28 +16,18 @@ use crate::bindings::_helpers::model_config::unicode_environment::unicode_enviro use crate::bindings::_helpers::render::declaration_values::constant_declaration_sql; use crate::bindings::_helpers::render::macro_expansion_result::macro_expansion_classes; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const PROJECT_VARS_MODULE: &str = "sqlbuild._native"; -const MACRO_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.main.active_macro_bridge"; -const SQL_ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const PYTHON_NODE_MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const PYTHON_NODE_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; /// The explicit reference call kinds and the `SqlResourceRefKind` member each resolves to. const EXPLICIT_REFERENCE_KINDS: [(&str, &str); 3] = [("ref", "MODEL"), ("source", "SOURCE"), ("seed", "SEED")]; /// The explicit reference call kinds mapped to their `SqlResourceRefKind` members. fn explicit_reference_kinds(py: Python<'_>) -> PyResult> { - let kinds: Bound<'_, PyAny> = imported(py, PYTHON_NODE_TYPES_MODULE, "SqlResourceRefKind")?; + let kinds: Bound<'_, PyAny> = host::entry(py, UserCodeHost::SqlResourceRefKind)?; let built: Bound<'_, PyDict> = PyDict::new(py); for (call_kind, member) in EXPLICIT_REFERENCE_KINDS { built.set_item(call_kind, kinds.getattr(member)?)?; @@ -97,7 +87,7 @@ fn macro_inputs<'py>(context: &Bound<'py, PyAny>) -> PyResult ("macro_context", context.getattr("macro_context")?), ( "bridge", - imported(py, MACRO_BRIDGE_MODULE, "active_macro_bridge")?.call0()?, + host::entry(py, UserCodeHost::ActiveMacroBridge)?.call0()?, ), ( "scoped", @@ -107,16 +97,16 @@ fn macro_inputs<'py>(context: &Bound<'py, PyAny>) -> PyResult ), ( "declaration_context", - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?, + registry::class(py, RecordId::DeclarationResolutionContext)?, ), ( "macro_argument_error", - imported(py, COMPILE_ERRORS_MODULE, "MacroArgumentError")?, + registry::error(py, ErrorId::MacroArgument)?, ), ("expansion", macro_expansion_classes(py)?.into_any()), ( "generic_syntax", - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")? + registry::class(py, RecordId::SqlLexicalSyntax)? .call0()? .getattr("native_mapping")?, ), @@ -134,31 +124,31 @@ fn sql_hook_inputs<'py>( vec![ ( "named_sql_hook_entry", - imported(py, NATIVE_RECORDS_MODULE, "NamedSqlHookEntry")?, + registry::class(py, RecordId::NamedSqlHookEntry)?, ), ( "python_hook_entry", - imported(py, DISCOVERY_MODELS_MODULE, "PythonHookEntry")?, + registry::class(py, RecordId::PythonHookEntry)?, ), ("hook_files", discovered.getattr("sql_hook_files")?), ("effective_vars", context.getattr("effective_vars")?), ("environment", unicode_environment(py)?), ( "render_project_var", - imported(py, PROJECT_VARS_MODULE, "render_project_var_text")?, + registry::native(py, NativeAttr::RenderProjectVarText)?, ), ( "sql_resource_ref", - imported(py, PYTHON_NODE_MODELS_MODULE, "SqlResourceRef")?, + host::entry(py, UserCodeHost::SqlResourceRef)?, ), ("explicit_kinds", explicit_reference_kinds(py)?.into_any()), ( "declaration_identity", - imported(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, ), ( "sql_hook_kind", - imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?.getattr("SQL_HOOK")?, + registry::class(py, RecordId::DeclarationKind)?.getattr("SQL_HOOK")?, ), ], ) @@ -191,7 +181,7 @@ pub(crate) fn build_model_inputs<'py>( let defer_model_sql_validation: bool = arguments.flag("defer_model_sql_validation")?; let external: Bound<'py, PyAny> = arguments.value("external_sql_reference_resolver")?; let settings: Bound<'py, PyAny> = context.getattr("effective_settings")?; - let validator: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeModelValidator")? + let validator: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeModelValidator)? .call_method1( "from_inputs", ( @@ -213,7 +203,7 @@ pub(crate) fn build_model_inputs<'py>( }; let project_config_path: Bound<'py, PyAny> = root.div(PROJECT_CONFIG_FILENAME)?; let config_inputs: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NativeModelConfigInputs")?, + ®istry::class(py, RecordId::NativeModelConfigInputs)?, &[ ( "project_config", @@ -243,15 +233,15 @@ pub(crate) fn build_model_inputs<'py>( vec![ ( "report_diagnostic", - imported(py, COLLECTOR_MODULE, "report_compile_diagnostic")?, + registry::native(py, NativeAttr::ReportCompileDiagnostic)?, ), ( "record_template_reads", - imported(py, NATIVE_MODULE, "record_template_reads")?, + registry::native(py, NativeAttr::RecordTemplateReads)?, ), ( "visible_declarations", - imported(py, NATIVE_RECORDS_MODULE, "VisibleModelDeclarations")?, + registry::class(py, RecordId::VisibleModelDeclarations)?, ), ("config_session", session), ("syntax_help", opt_out_help(py, true)?), @@ -279,7 +269,7 @@ pub(crate) fn build_model_inputs<'py>( )?; let syntax: Bound<'py, PyAny> = context.getattr("sql_lexical_syntax")?; let native_loop: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "NativeModelLoop")?.call1(( + registry::native(py, NativeAttr::NativeModelLoop)?.call1(( (validator, contract_classes(py)?.into_any(), classes), syntax.getattr("native_mapping")?, reserved_cursor_markers(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs index bf28cee411..e2588c9eea 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs @@ -4,15 +4,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyBool, PyDict, PyDictMethods}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const USER_MACROS_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Build the declaration scope of `discovered`; a static scope lends only its index to macros. fn declaration_scope<'py>( @@ -21,11 +17,11 @@ fn declaration_scope<'py>( sql_lexical_syntax: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = discovered.py(); - let loaded_macros: Bound<'py, PyAny> = imported(py, USER_MACROS_MODULE, "load_project_macros")? + let loaded_macros: Bound<'py, PyAny> = host::entry(py, UserCodeHost::LoadProjectMacros)? .call1((discovered.getattr("macro_files")?,))?; if !static_scope.is_none() { let rebound: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "rebind_declaration_scope")?, + ®istry::native(py, NativeAttr::RebindDeclarationScope)?, &[ ("scope", static_scope), ("discovered_inputs", discovered.clone()), @@ -37,7 +33,7 @@ fn declaration_scope<'py>( } } call_with( - &imported(py, NATIVE_MODULE, "build_declaration_scope")?, + ®istry::native(py, NativeAttr::BuildDeclarationScope)?, &[ ("discovered_inputs", discovered.clone()), ("loaded_macros", loaded_macros), @@ -49,8 +45,7 @@ fn declaration_scope<'py>( /// Each model file's own enum and constant declarations, by file path. fn local_declarations<'py>(discovered: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = discovered.py(); - let context: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?; + let context: Bound<'py, PyAny> = registry::class(py, RecordId::DeclarationResolutionContext)?; let by_name = |declarations: Bound<'py, PyAny>| -> PyResult> { let named: Bound<'py, PyDict> = PyDict::new(py); for declaration in declarations.try_iter()? { @@ -104,7 +99,7 @@ pub(crate) fn build_sql_expansion_context<'py>( let mut discovered: Bound<'py, PyAny> = arguments.value("discovered_inputs")?; if discovered.is_none() { discovered = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", arguments.value("project_dir")?), ( @@ -121,7 +116,7 @@ pub(crate) fn build_sql_expansion_context<'py>( cli_vars = PyDict::new(py).into_any(); } let effective_vars: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_effective_vars")?, + ®istry::native(py, NativeAttr::BuildEffectiveVars)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -138,23 +133,23 @@ pub(crate) fn build_sql_expansion_context<'py>( )?; } let (enums, constants): (Bound<'py, PyAny>, Bound<'py, PyAny>) = - imported(py, NATIVE_MODULE, "public_declaration_indexes")? + registry::native(py, NativeAttr::PublicDeclarationIndexes)? .call1((&discovered,))? .extract()?; let collection_rendering: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_collection_rendering")?, + ®istry::native(py, NativeAttr::ResolveEffectiveCollectionRendering)?, &[ ("project_config", project_config.clone()), ("declaration_override", py.None().into_bound(py)), ], )?; let macro_context: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "MacroContext")?, + ®istry::class(py, RecordId::MacroContext)?, &[ ( "adapter_name", call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config), @@ -178,7 +173,7 @@ pub(crate) fn build_sql_expansion_context<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlExpansionContext")?, + ®istry::class(py, RecordId::SqlExpansionContext)?, &[ ("effective_vars", effective_vars), ("loaded_macros", scope.getattr("loaded_macros")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs index 9d91bb850e..321a245879 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs @@ -3,18 +3,17 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyString, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `value`, unless it is `None`. fn present(value: Bound<'_, PyAny>) -> Option> { Some(value).filter(|value| !value.is_none()) } -fn source<'py>(py: Python<'py>, class: &str, member: &str) -> PyResult> { - imported(py, CONTRACT_TYPES_MODULE, class)?.getattr(member) +fn source<'py>(py: Python<'py>, class: RecordId, member: &str) -> PyResult> { + registry::member(py, class, member) } /// The `materialized` defaults entry, or `None` for a model without a table-backed materialization. @@ -35,9 +34,8 @@ fn retention<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = materialized.py(); - let resolved: Bound<'py, PyAny> = - imported(py, CONTRACT_MODELS_MODULE, "ResolvedTimeTravelRetention")?; - let class = "TimeTravelRetentionSource"; + let resolved: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedTimeTravelRetention)?; + let class = RecordId::TimeTravelRetentionSource; let mut policy: Option<(Bound<'py, PyAny>, &str)> = None; if let Some(entry) = entry { if !target.is_none() { @@ -74,8 +72,8 @@ fn table_type<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = target.py(); - let resolved: Bound<'py, PyAny> = imported(py, CONTRACT_MODELS_MODULE, "ResolvedTableType")?; - let class = "TableTypeSource"; + let resolved: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedTableType)?; + let class = RecordId::TableTypeSource; let mut declared: Option<(Bound<'py, PyAny>, &str)> = None; if let Some(entry) = entry { if !target.is_none() { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs index 54674bfb26..3100046009 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs @@ -16,9 +16,9 @@ use crate::bindings::_helpers::frontier::constants::{ COMPILER_CACHE_DIRECTORY_NAME, RETIRED_CACHE_NAMESPACE_SUFFIXES, }; use crate::bindings::_helpers::model_config::template_data::{early_model_templates, run_id}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; const DATABASE_KEY: &str = "database"; const SCHEMA_KEY: &str = "schema"; @@ -31,7 +31,7 @@ fn resolved_target<'py>( let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let local_config: Bound<'py, PyAny> = discovered.getattr("local_config")?; let target_name: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -42,7 +42,7 @@ fn resolved_target<'py>( return Ok((target_name, py.None().into_bound(py))); } let target_config: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", project_config), ("local_config", local_config), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs index a5141150a8..6b80fbf6ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs @@ -15,6 +15,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::compile_outputs::artifacts::staged_artifact_files; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const MIN_PREPARED_ARTIFACT_MODELS: usize = 128; const MAX_PREPARED_ARTIFACT_MODELS: usize = 5000; @@ -286,7 +288,7 @@ impl PreparedCompileArtifacts { -> PyResult> { let py: Python<'_> = arguments.py(); Ok(call_with( - &imported(py, "sqlbuild._native", "plan_static_sql_tests")?, + ®istry::native(py, NativeAttr::PlanStaticSqlTests)?, &[ ("target_dir", planned_target.bind(py).clone()), ("adapter", planned_adapter.bind(py).clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs index eeb8de479d..dab6864f69 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs @@ -20,12 +20,12 @@ use crate::bindings::_helpers::compile_outputs::json_report::{ }; use crate::bindings::_helpers::discovery::constants::SQL_HOOK_OUTPUT_FIELDS; use crate::bindings::_helpers::discovery::hook_entries::hook_entries; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::coverage::semantic_coverage; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const METADATA_MODULE: &str = "importlib.metadata"; const TARGET_DIRECTORY: &str = "target"; const COMPILED_DIRECTORY: &str = "compiled"; @@ -661,7 +661,7 @@ fn hook_definitions<'py>(project: &Bound<'py, PyAny>) -> PyResult = project.getattr("hook_functions")?; let identities: Bound<'py, PyAny> = - imported(py, HOOK_IDENTITIES_MODULE, "build_hook_identities")?.call1((&functions,))?; + host::entry(py, UserCodeHost::BuildHookIdentities)?.call1((&functions,))?; for hook in functions.try_iter()? { let hook: Bound<'py, PyAny> = hook?; let identity: Bound<'py, PyAny> = identities.get_item(hook.getattr("name")?)?; @@ -761,21 +761,18 @@ fn audit_resource<'py>(audit: &Bound<'py, PyAny>) -> PyResult fn test_resource<'py>(test: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = test.py(); let payload: Bound<'py, PyAny> = test.getattr("payload")?; - let expected_models: Bound<'py, PyList> = if payload.is_instance(&imported( - py, - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", - )?)? { - PyList::new( - py, - payload - .getattr("expected_model_names")? - .try_iter()? - .collect::>>()?, - )? - } else { - PyList::empty(py) - }; + let expected_models: Bound<'py, PyList> = + if payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { + PyList::new( + py, + payload + .getattr("expected_model_names")? + .try_iter()? + .collect::>>()?, + )? + } else { + PyList::empty(py) + }; let test_file_path: Bound<'py, PyAny> = test.getattr("test_file")?.getattr("relative_path")?; let item: Bound<'py, PyDict> = dict( py, @@ -1042,7 +1039,7 @@ pub(crate) fn format_compile_error_json<'py>( ) }; let diagnostic: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ ("phase", any(py, "compile")?), ("severity", any(py, ERROR_SEVERITY)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs index dfaef4e9b9..134f294821 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs @@ -34,12 +34,14 @@ use crate::bindings::_helpers::compile_driver::phases::{elapsed_ms, monotonic}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; use crate::bindings::_helpers::compile_outputs::json_report::plain_json; use crate::bindings::_helpers::frontier::cache_directory::cache_directory; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const REUSE_REQUEST: (&str, &str) = ("sqlbuild.cli.compile_reuse.models", "CompileReuseRequest"); -const STARTUP_INPUTS_MODULE: &str = "sqlbuild.cli.compile_reuse._helpers.startup_inputs"; -const COMPUTE_LOG_TEE_MODULE: &str = "sqlbuild.runtime.compute_logs.classes.text_tee"; +const REUSE_REQUEST: FacadeHost = FacadeHost::CompileReuseRequest; const HIT: &str = "hit"; const CHECKED_HIT: &str = "hit"; const CHECKED_BYPASS: &str = "bypass"; @@ -60,10 +62,9 @@ pub(crate) fn reuse_rules(py: Python<'_>, with_output_files: bool) -> PyResult, with_output_files: bool) -> PyResult( let capture: Bound<'py, PyAny> = if py .import("sys")? .getattr("stdout")? - .is_instance(&imported(py, COMPUTE_LOG_TEE_MODULE, "TextComputeLogTee")?)? + .is_instance(&host::entry(py, ExecutorHost::TextComputeLogTee)?)? { let logs: Bound<'py, PyAny> = resolved .call_method1("__truediv__", (COMPUTE_LOG_DIRECTORY_NAME,))? @@ -181,7 +182,7 @@ fn launch_payload<'py>( payload.set_item("search_path", search_path)?; payload.set_item( "startup_inputs", - imported(py, STARTUP_INPUTS_MODULE, "startup_inputs")?.call0()?, + host::entry(py, FacadeHost::StartupInputs)?.call0()?, )?; payload.set_item("json_output", attempt.getattr("json_output")?)?; payload.set_item("rules", reuse_rules(py, false)?)?; @@ -380,7 +381,7 @@ fn attempt_compile_reuse<'py>(request: &Bound<'py, PyAny>) -> PyResult = imported(py, NATIVE_MODULE, "check_compile_reuse")? + let native: Bound<'py, PyAny> = registry::native(py, NativeAttr::CheckCompileReuse)? .call1((reuse_check(request, &project_dir)?,))?; let checked: String = native.call_method0("outcome")?.extract()?; let json_output: Bound<'py, PyAny> = request.getattr("json_output")?; @@ -467,7 +468,7 @@ fn reuse_request<'py>(request: &Bound<'py, PyAny>) -> PyResult fields.push((name, request.getattr(name)?)); } fields.push(("profiling", profiling.into_bound_py_any(py)?)); - call_with(&imported(py, REUSE_REQUEST.0, REUSE_REQUEST.1)?, &fields) + call_with(&host::entry(py, REUSE_REQUEST)?, &fields) } /// Run one compile command request, replaying the previous compile when nothing changed. @@ -479,7 +480,7 @@ fn run_compile<'py>(request: &Bound<'py, PyAny>) -> PyResult> if attempt.getattr("outcome")?.eq(outcome(py, HIT)?)? && !exit_code.is_none() { return Ok(exit_code); } - imported(py, NATIVE_MODULE, "run_compile_command")?.call1((request, attempt)) + registry::native(py, NativeAttr::RunCompileCommand)?.call1((request, attempt)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs index c0f5edb951..558889c3e9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs @@ -18,10 +18,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::compile_outputs::reuse::artifact_digest; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; -const PROVIDER_SETTINGS_MODULE: &str = "sqlbuild.cli.compile_reuse._helpers.provider_settings"; -const MACRO_STORE_PATHS_MODULE: &str = - "sqlbuild.compiler.macro_bridge.main._macro_store_module_paths"; const MISS_OUTCOME: &str = "miss"; const HIT_OUTCOME: &str = "hit"; const BYPASS_OUTCOME: &str = "bypass"; @@ -292,12 +292,10 @@ fn module_paths(py: Python<'_>) -> PyResult> { paths.append(path)?; } } - let extra: Bound<'_, PyAny> = py.import("builtins")?.getattr("sorted")?.call1((imported( - py, - MACRO_STORE_PATHS_MODULE, - "macro_store_module_paths", - )? - .call0()?,))?; + let extra: Bound<'_, PyAny> = py + .import("builtins")? + .getattr("sorted")? + .call1((host::entry(py, UserCodeHost::MacroStoreModulePaths)?.call0()?,))?; for path in extra.try_iter()? { let path: Bound<'_, PyAny> = path?; if path.is_instance_of::() { @@ -351,7 +349,7 @@ struct CompileFacts<'py> { fn record<'py>(facts: &CompileFacts<'py>) -> PyResult { let py: Python<'py> = facts.native.py(); let settings: Bound<'_, PyAny> = call_with( - &imported(py, PROVIDER_SETTINGS_MODULE, "provider_settings_inputs")?, + &host::entry(py, FacadeHost::ProviderSettingsInputs)?, &[( "settings_classes", facts.input_reads.getattr("settings_classes")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs index 9e4f5df9cf..4a0af16010 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs @@ -6,8 +6,9 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use sqlbuild_cli::replay::main::output_file_identities::output_file_identities; use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The version of the runtime identity a stored compile records. const REUSE_FORMAT_VERSION: &str = "1"; /// Each `CompileReuseRequest` field the invocation identity records unchanged. @@ -54,7 +55,7 @@ pub(crate) fn runtime_identity(py: Python<'_>) -> PyResult> { identity.set_item("cache_tag", cache_tag)?; identity.set_item( "native_build", - py.import(NATIVE_MODULE)?.getattr("BUILD_IDENTITY")?.str()?, + registry::native(py, NativeAttr::BuildIdentity)?.str()?, )?; Ok(identity) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs index c0ede29fd4..60946e133f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs @@ -32,6 +32,10 @@ use crate::bindings::_helpers::compile_outputs::artifacts::{ use crate::bindings::_helpers::compile_outputs::runtime_target::{ compiled_sql_test_path, planned_sql_test_path, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; const COMPILED_DIR: &str = "compiled"; const MODELS_DIR: &str = "models"; @@ -46,13 +50,6 @@ const SQL_FILE_SUFFIX: &str = ".sql"; const LANGUAGE_DIR_DEPTH: usize = 2; const PUBLISHED_STAGING_CHANGED: &str = "changed"; const PUBLISHED_TREE: &str = "tree"; -const OUTPUT_MODELS_MODULE: &str = "sqlbuild._native"; -const CLI_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const MODEL_CHAIN_MODULE: &str = "sqlbuild._native"; -const PLAN_ARTIFACTS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const TARGET_OPTIONS: (&[&str], &[&str]) = (&["target_dir", "adapter", "project"], &["manifest"]); const STAGING_CHANGED_HELP: &str = "Rerun the compile; avoid deleting target/ while a compile is running."; @@ -204,7 +201,7 @@ fn written_target<'py>( let py: Python<'py> = target_dir.py(); let [models, seeds, functions, audits, tests] = counts; call_with( - &imported(py, OUTPUT_MODELS_MODULE, "WrittenTarget")?, + ®istry::class(py, RecordId::WrittenTarget)?, &[ ("model_count", models.into_bound_py_any(py)?), ("seed_count", seeds.into_bound_py_any(py)?), @@ -349,7 +346,7 @@ fn planned_artifacts<'py>( return Ok(Vec::new()); } let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", "project".into_bound_py_any(py)?), ("operation_name", "sql_test_planning".into_bound_py_any(py)?), @@ -357,11 +354,7 @@ fn planned_artifacts<'py>( )?; within(&lifecycle, |entered| { let artifacts: Vec> = call_with( - &imported( - py, - PLAN_ARTIFACTS_MODULE, - "plan_compiled_sql_test_artifacts", - )?, + ®istry::native(py, NativeAttr::PlanCompiledSqlTestArtifacts)?, &[ ("project", project.clone()), ("tests", PyTuple::new(py, tests)?.into_any()), @@ -402,7 +395,7 @@ fn plan_tests<'py>( } else { let context: IdentityContext<'py> = identity_context(project, adapter)?; let chains: Bound<'py, PyAny> = call_with( - &imported(py, MODEL_CHAIN_MODULE, "sql_test_model_chain_names_by_key")?, + ®istry::native(py, NativeAttr::SqlTestModelChainNamesByKey)?, &[("project", project.clone()), ("tests", tests.clone())], )?; Some((context, chains)) @@ -483,14 +476,13 @@ fn artifact_diagnostics<'py>( let mut diagnostics: Vec> = Vec::new(); for message in artifact.getattr("error_messages")?.try_iter()? { diagnostics.push(call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ ("phase", "test".into_bound_py_any(py)?), ("severity", "error".into_bound_py_any(py)?), ( "code", - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")? - .getattr("code")?, + registry::error(py, ErrorId::PlannerInput)?.getattr("code")?, ), ("message", message?), ("resource_type", "sql_test".into_bound_py_any(py)?), @@ -693,14 +685,13 @@ pub(crate) fn publish_staged_target<'py>( /// `StagedArtifactsChangedError(message, help=...)`. pub(crate) fn staging_changed(py: Python<'_>, message: &str) -> PyResult { - let error: Bound<'_, PyAny> = - imported(py, CLI_EXCEPTIONS_MODULE, "StagedArtifactsChangedError")?.call( - (message,), - Some(&keywords( - py, - &[("help", STAGING_CHANGED_HELP.into_bound_py_any(py)?)], - )?), - )?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::StagedArtifactsChanged)?.call( + (message,), + Some(&keywords( + py, + &[("help", STAGING_CHANGED_HELP.into_bound_py_any(py)?)], + )?), + )?; Ok(PyErr::from_value(error)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs index a9c0a96441..045eab9cf4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs @@ -12,8 +12,8 @@ use sqlbuild_cache::digest::main::sha256_hex::sha256_hex; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_outputs::json_report::{compact_sorted_json, orjson_sorted}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const CACHE_VERSION: i64 = 4; const ALGORITHM_FINGERPRINT: &str = "sql-test-artifact-v4-orjson-identity"; @@ -216,11 +216,7 @@ fn test_payload<'py>(test: &Bound<'py, PyAny>) -> PyResult> { ], )?; let test_payload: Bound<'py, PyAny> = test.getattr("payload")?; - if test_payload.is_instance(&imported( - py, - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", - )?)? { + if test_payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { let block: Bound<'py, PyAny> = test.getattr("test_block")?; payload.set_item( "model", @@ -255,10 +251,9 @@ fn test_payload<'py>(test: &Bound<'py, PyAny>) -> PyResult> { ], )?, )?; - } else if test_payload.is_instance(&imported( + } else if test_payload.is_instance(®istry::class( py, - NATIVE_RECORDS_MODULE, - "CompiledDirectLogicSqlTestPayload", + RecordId::CompiledDirectLogicSqlTestPayload, )?)? { payload.set_item( "direct", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs index 1700417c93..8c038f3b60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs @@ -10,35 +10,26 @@ use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{ Status, call_with, imported, keywords, within, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::RULE_FIX_AVAILABLE_NOTE; use crate::bindings::_helpers::rules::early_sql_lint::EarlySqlLint; use crate::bindings::_helpers::rules::results::rules_progress; use crate::bindings::_helpers::rules::rules_config::load_rules_config; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const DISCOVER_MODULE: &str = "sqlbuild._native"; -const REJECT_SELECTORS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const SELECTED_GRAPH_MODULE: &str = "sqlbuild._native"; const RICH_LINEAGE: &str = "rich"; const FAST_LINEAGE: &str = "fast"; const RICH_COLUMN_LINEAGE: &str = "RICH"; const FAST_COLUMN_LINEAGE: &str = "FAST"; -const CONTRACTS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SELECTION_MODULE: &str = "sqlbuild._native"; -const RULE_THREADS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const CLI_COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const TARGET_DIRECTORY: &str = "target"; /// `ColumnLineageMode.`. fn column_lineage_mode<'py>(py: Python<'py>, member: &str) -> PyResult> { - imported(py, NATIVE_RECORDS_MODULE, "ColumnLineageMode")?.getattr(member) + registry::class(py, RecordId::ColumnLineageMode)?.getattr(member) } /// Column lineage in the compile's mode; `none` skips the analyzer. @@ -55,7 +46,7 @@ fn compile_lineage<'py>( } else { return Ok(py.None().into_bound(py)); }; - imported(py, "sqlbuild._native", "build_project_column_lineage")?.call1(( + registry::native(py, NativeAttr::BuildProjectColumnLineage)?.call1(( graph.getattr("project")?, dialect, column_lineage_mode(py, member)?, @@ -194,7 +185,7 @@ fn analyzed<'py>( let (lineage, lineage_ms): (Bound<'py, PyAny>, u64) = driver.lineage(&graph, &dialect)?; let contracts: ContractPhase<'py> = driver.contracts(&project, &dialect)?; let selected_keys: Bound<'py, PyAny> = call_with( - &imported(py, SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", driver.select.clone()), @@ -223,7 +214,7 @@ fn analyzed<'py>( let diagnostics: Bound<'py, PyTuple> = with_rule_diagnostics(py, &core_diagnostics, &rules_result)?; call_with( - &imported(py, CLI_COMPILE_MODELS_MODULE, "CompileAnalysis")?, + ®istry::class(py, RecordId::CompileAnalysis)?, &[ ("discovered_inputs", discovered), ("adapter", adapter), @@ -263,7 +254,7 @@ fn analyzed<'py>( fn empty_rules_result(py: Python<'_>) -> PyResult> { let zero: Bound<'_, PyAny> = 0_i64.into_pyobject(py)?.into_any(); call_with( - &imported(py, NATIVE_RECORDS_MODULE, "RulesRunResult")?, + ®istry::class(py, RecordId::RulesRunResult)?, &[ ("findings", PyTuple::empty(py).into_any()), ("evaluated_models", zero.clone()), @@ -291,7 +282,7 @@ impl<'py> Driver<'_, 'py> { py.None().into_bound(py) }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, DISCOVER_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", self.options.project_dir.clone()), ("extract_output_column_locations", false_value), @@ -299,7 +290,7 @@ impl<'py> Driver<'_, 'py> { ], )?; call_with( - &imported(py, REJECT_SELECTORS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", self.select.clone()), ("exclude", self.exclude.clone()), @@ -315,11 +306,7 @@ impl<'py> Driver<'_, 'py> { /// The adapter the project and local configuration select. fn adapter(&self, discovered: &Bound<'py, PyAny>) -> PyResult> { let adapter_name: Bound<'py, PyAny> = call_with( - &imported( - self.py, - ADAPTER_NAME_MODULE, - "resolve_effective_adapter_name", - )?, + ®istry::native(self.py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -339,7 +326,7 @@ impl<'py> Driver<'_, 'py> { let started: Instant = Instant::now(); self.status.start("Compiling project graph...")?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "project").into_any()), ( @@ -366,11 +353,7 @@ impl<'py> Driver<'_, 'py> { )?; let graph: Bound<'py, PyAny> = within(&lifecycle, |_| { call_with( - &imported( - py, - SELECTED_GRAPH_MODULE, - "build_project_graph_with_analysis_selection", - )?, + ®istry::native(py, NativeAttr::BuildProjectGraphWithAnalysisSelection)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -427,13 +410,13 @@ impl<'py> Driver<'_, 'py> { .is_truthy()?; let result: Bound<'py, PyAny> = if skip { call_with( - &imported(py, CONTRACT_MODELS_MODULE, "ContractValidationResult")?, + ®istry::class(py, RecordId::ContractValidationResult)?, &[("diagnostics", PyTuple::empty(py).into_any())], )? } else { self.status.start("Validating model contracts...")?; call_with( - &imported(py, CONTRACTS_MODULE, "evaluate_model_contracts")?, + ®istry::native(py, NativeAttr::EvaluateModelContracts)?, &[("project", project.clone()), ("dialect", dialect.clone())], )? }; @@ -492,7 +475,7 @@ impl<'py> Driver<'_, 'py> { py.None().into_bound(py) }; let result: Bound<'py, PyAny> = call_with( - &imported(py, RULE_THREADS_MODULE, "evaluate_rules")?, + ®istry::native(py, NativeAttr::EvaluateRules)?, &[ ("graph", graph.clone()), ("discovered_inputs", discovered.clone()), @@ -521,12 +504,11 @@ fn with_rule_diagnostics<'py>( core: &Bound<'py, PyTuple>, rules_result: &Bound<'py, PyAny>, ) -> PyResult> { - let diagnostic_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?; + let diagnostic_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let phase: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticPhase")?.getattr("RULE")?; + registry::class(py, RecordId::DiagnosticPhase)?.getattr("RULE")?; let severity: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticSeverity")?.getattr("ERROR")?; + registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?; let fix_note: Bound<'py, PyAny> = PyString::new(py, RULE_FIX_AVAILABLE_NOTE).into_any(); let mut diagnostics: Vec> = core.iter().collect(); for fault in rules_result.getattr("findings")?.try_iter()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs index 051a46aec1..410eb0dec9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs @@ -13,8 +13,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::planner_identity::json_values::{COMPACT, unicode_sorted_dumps}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const ANALYSIS_CACHE_VERSION: i64 = 13; const ANALYSIS_ALGORITHM_FINGERPRINT: &str = "model-sql-analysis-v21-completed-star-shapes"; @@ -74,7 +74,7 @@ pub(crate) fn analysis_cache_context<'py>( Err(error) => return Err(error), }; let context = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "AnalysisCacheContext")?, + ®istry::class(py, RecordId::AnalysisCacheContext)?, &[ ("root", root.clone()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs index e21a80bc05..a25a389139 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs @@ -7,15 +7,12 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::assembly_project::assembled_project; use crate::bindings::_helpers::compile_driver::project::lineage_mode; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, within, + KeywordArguments, call_with, within, }; - -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const PROJECT_WITH_MODULE: &str = "sqlbuild._native"; -const RECORD_ANALYSES_MODULE: &str = "sqlbuild._native"; -const EXPLICIT_REFERENCES_MODULE: &str = "sqlbuild._native"; -const ACTIVE_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.main.active_macro_bridge"; -const RUN_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.classes.macro_bridge"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const OPTIONAL: [&str; 5] = [ "inference_profile", @@ -53,7 +50,7 @@ fn assemble_project<'py>( analysis_model_names: arguments.value("analysis_model_names")?.unbind(), }; let collector: Bound<'py, PyAny> = - imported(py, COLLECTOR_MODULE, "collect_compile_diagnostics")?.call0()?; + registry::native(py, NativeAttr::CollectCompileDiagnostics)?.call0()?; let (built, violations): (Bound<'py, PyAny>, Bound<'py, PyAny>) = within(&collector, |violations| { Ok((referenced_project(py, request)?, violations.clone())) @@ -64,7 +61,7 @@ fn assemble_project<'py>( } let diagnostics: Bound<'py, PyTuple> = chained(&built.getattr("diagnostics")?, &collected)?; call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[("project", built), ("diagnostics", diagnostics.into_any())], ) } @@ -86,7 +83,7 @@ pub(crate) fn chained<'py>( /// The undecorated `assemble_project`. fn referenced_project(py: Python<'_>, request: AssemblyRequest) -> PyResult> { let inputs: Bound<'_, PyAny> = request.inputs.bind(py).clone(); - let project: Bound<'_, PyAny> = if imported(py, ACTIVE_BRIDGE_MODULE, "active_macro_bridge")? + let project: Bound<'_, PyAny> = if host::entry(py, UserCodeHost::ActiveMacroBridge)? .call0()? .is_none() { @@ -99,7 +96,7 @@ fn referenced_project(py: Python<'_>, request: AssemblyRequest) -> PyResult, request: AssemblyRequest) -> PyResult = with_native_project(&project, &inputs.getattr("native_project")?)?; let references: Bound<'_, PyAny> = call_with( - &imported(py, EXPLICIT_REFERENCES_MODULE, "explicit_reference_report")?, + ®istry::native(py, NativeAttr::ExplicitReferenceReport)?, &[ ("project", project.clone()), ("discovered_inputs", inputs.getattr("discovered_inputs")?), @@ -129,7 +126,7 @@ fn referenced_project(py: Python<'_>, request: AssemblyRequest) -> PyResult = chained(&project.getattr("diagnostics")?, &reference_diagnostics)?; call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project), ("diagnostics", diagnostics.into_any()), @@ -148,7 +145,7 @@ fn with_native_project<'py>( return Ok(project.clone()); } call_with( - &imported(py, RECORD_ANALYSES_MODULE, "record_model_analyses")?, + ®istry::native(py, NativeAttr::RecordModelAnalyses)?, &[ ("project", native_project.clone()), ("models", project.getattr("models")?), @@ -158,7 +155,7 @@ fn with_native_project<'py>( return Ok(project.clone()); } call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project.clone()), ("native_project", native_project.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs index fbe498fcd0..6b3728c6e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs @@ -12,14 +12,12 @@ use crate::bindings::_helpers::compile_driver::compile_timings::{ record_compile_cpu_timing, record_compile_timing, }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const UDF_PREFIX: &str = "__sqlbuild_udf_"; @@ -78,7 +76,7 @@ pub(crate) fn analysis_context<'py>( request.skip_column_inference.bind(py), )?; let relations: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "declared_relations")?.call1((inputs,))?; + registry::native(py, NativeAttr::DeclaredRelations)?.call1((inputs,))?; let column_types: Bound<'py, PyAny> = relations.get_item(0)?; let nullability: Bound<'py, PyAny> = relations.get_item(1)?; let complete_schemas: Bound<'py, PyAny> = relations.get_item(2)?; @@ -112,7 +110,7 @@ pub(crate) fn analysis_context<'py>( fn dynamic_families_by_table<'py>(inputs: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = inputs.py(); let enforced: Bound<'py, PyAny> = - imported(py, PLANNER_TYPES_MODULE, "ContractPolicy")?.getattr("ENFORCED")?; + registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; let families: Bound<'py, PyDict> = PyDict::new(py); for model_input in inputs.getattr("model_inputs")?.try_iter()? { let model_input: Bound<'py, PyAny> = model_input?; @@ -143,9 +141,9 @@ pub(crate) fn declared_names<'py>( let py: Python<'py> = inputs.py(); let functions: Bound<'py, PyAny> = inputs.getattr("sql_function_inputs")?; let names: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "known_function_names")?.call1((functions.clone(),))?; + registry::native(py, NativeAttr::KnownFunctionNames)?.call1((functions.clone(),))?; let types: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "known_declared_types")?, + ®istry::native(py, NativeAttr::KnownDeclaredTypes)?, &[ ("functions", functions), ("column_types", column_types.clone()), @@ -165,7 +163,7 @@ fn profile_with_catalog<'py>( let profile: Bound<'py, PyAny> = if inference_profile.is_truthy()? { inference_profile.clone() } else { - imported(py, ADAPTER_MODELS_MODULE, "ExpressionInferenceProfile")?.call0()? + host::entry(py, AdapterHost::ExpressionInferenceProfile)?.call0()? }; let (known_functions, known_types): (Bound<'py, PyAny>, Bound<'py, PyAny>) = declared_names(inputs, column_types)?; @@ -273,7 +271,7 @@ fn record_expression_shapes<'py>( } } let shapes: Vec> = call_with( - &imported(py, NATIVE_MODULE, "infer_native_expression_source_shapes")?, + ®istry::native(py, NativeAttr::InferNativeExpressionSourceShapes)?, &[ ("expressions", PyTuple::new(py, expressions)?.into_any()), ("profile", context.profile.clone()), @@ -336,7 +334,7 @@ fn model_analyses<'py>( let inputs: &Bound<'py, PyAny> = request.inputs.bind(py); let names: &Bound<'py, PyAny> = request.analysis_model_names.bind(py); let mode: &Bound<'py, PyAny> = request.column_lineage_mode.bind(py); - let lineage_mode: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "ColumnLineageMode")?; + let lineage_mode: Bound<'py, PyAny> = registry::class(py, RecordId::ColumnLineageMode)?; let rich: bool = mode.eq(lineage_mode.getattr("RICH")?)?; let allow_compact: bool = mode.eq(lineage_mode.getattr("FAST")?)? || rich; let flags: (Bound<'py, PyAny>, Bound<'py, PyAny>) = ( @@ -362,7 +360,7 @@ fn model_analyses<'py>( } } let analysis_request: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_MODELS_MODULE, "NativeModelAnalysisRequest")?, + ®istry::class(py, RecordId::NativeModelAnalysisRequest)?, &[ ("known_functions", known_functions), ("known_types", known_types), @@ -381,7 +379,7 @@ fn model_analyses<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "analyze_native_model_sql")?, + ®istry::native(py, NativeAttr::AnalyzeNativeModelSql)?, &[("request", analysis_request)], ) }) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs index 7f9878328f..05c6331f5d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs @@ -4,15 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::attachments::cursor_intrinsics::validated_model_cursor_sql; -use crate::bindings::_helpers::compile_driver::assembly_project::ADAPTER_NAME_MODULE; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::declaration_scope_resolver; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The `CompiledSqlScenario` fields copied from its input, in Python's keyword order. const SCENARIO_FIELDS: [&str; 12] = [ "sql_body", @@ -37,10 +34,9 @@ pub(crate) fn test_model_query_overrides<'py>( let py: Python<'py> = inputs.py(); let overrides: Bound<'py, PyDict> = PyDict::new(py); let payload: Bound<'py, PyAny> = test_input.getattr("payload")?; - if !payload.is_instance(&imported( + if !payload.is_instance(®istry::class( py, - NATIVE_RECORDS_MODULE, - "CompileModelSqlTestInputPayload", + RecordId::CompileModelSqlTestInputPayload, )?)? { return Ok(overrides); } @@ -70,7 +66,7 @@ pub(crate) fn test_model_query_overrides<'py>( &inputs.getattr("scope_index")?, (&loaded_macros, &lookup), )?; - let expand: Bound<'py, PyAny> = py.import(NATIVE_MODULE)?.getattr("expand_sql_macros")?; + let expand: Bound<'py, PyAny> = registry::native(py, NativeAttr::ExpandSqlMacros)?; for model_input in inputs.getattr("model_inputs")?.try_iter()? { let model_input: Bound<'py, PyAny> = model_input?; let file_path: Bound<'py, PyAny> = @@ -115,14 +111,14 @@ fn model_macro_context<'py>(inputs: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", inputs.getattr("project_config")?), ("local_config", inputs.getattr("local_config")?), ], )?; call_with( - &imported(py, COMPILE_MODELS_MODULE, "MacroContext")?, + ®istry::class(py, RecordId::MacroContext)?, &[ ("adapter_name", adapter_name), ( @@ -142,9 +138,8 @@ pub(crate) fn compiled_sql_scenarios<'py>( inputs: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = inputs.py(); - let scenario_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompiledSqlScenario")?; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?; + let scenario_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledSqlScenario)?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let resource_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.getattr("SQL_SCENARIO")?; let mut scenarios: Vec> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs index 9a89d802c2..81d7e22eac 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs @@ -5,11 +5,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::assembly_analysis::AnalysisContext; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::project_assembly::models::{NativeModelFacts, NativeProjectFacts}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// `NativeProjectFacts` of `inputs`: resources, syntax validity, deps and standalone pivot proofs. pub(crate) fn native_project_facts<'py>( @@ -33,7 +32,7 @@ pub(crate) fn native_project_facts<'py>( PyDict::new(py).into_any() }; let syntax_checks: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "model_syntax_checks")?, + ®istry::native(py, NativeAttr::ModelSyntaxChecks)?, &[ ("model_inputs", model_inputs.clone()), ("analysis_model_names", names.clone()), @@ -41,7 +40,7 @@ pub(crate) fn native_project_facts<'py>( ], )?; let resources: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "assemble_native_project_resources")?, + ®istry::native(py, NativeAttr::AssembleNativeProjectResources)?, &[ ("inputs", inputs.clone()), ("dialect", dialect.clone()), @@ -50,7 +49,7 @@ pub(crate) fn native_project_facts<'py>( )?; let proofs: Bound<'py, PyDict> = pivot_proofs( &call_with( - &imported(py, NATIVE_MODULE, "standalone_pivot_models")?, + ®istry::native(py, NativeAttr::StandalonePivotModels)?, &[ ("model_inputs", model_inputs.clone()), ("analysed", positions), @@ -98,7 +97,7 @@ fn pivot_proofs<'py>( ) -> PyResult> { let py: Python<'py> = pivots.py(); let tables: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_MODELS_MODULE, "NativePivotTables")?, + ®istry::class(py, RecordId::NativePivotTables)?, &[ ("dialect", context.profile.getattr("sql_analysis_dialect")?), ("column_types_by_table", context.column_types.clone()), @@ -114,7 +113,7 @@ fn pivot_proofs<'py>( ], )?; let proven: Vec> = call_with( - &imported(py, NATIVE_MODULE, "prove_native_dynamic_contracts")?, + ®istry::native(py, NativeAttr::ProveNativeDynamicContracts)?, &[ ("session", session.clone()), ("tables", tables), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs index 6069d8cbeb..7975846def 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs @@ -6,15 +6,14 @@ use pyo3::types::{PyDict, PyList, PyString, PyTuple}; use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_sql; use crate::bindings::_helpers::boundary::panics::NativeCompilerError; use crate::bindings::_helpers::compile_driver::assembly_analysis::{ - ANALYSIS_MODELS_MODULE, AnalysisContext, model_stem, selected_for_analysis, + AnalysisContext, model_stem, selected_for_analysis, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::hand_built::compiled_model::compiled_model as hand_built_model; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::macro_calls::top_level_macro_call_names; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const NATIVE_FAILURE_PREFIX: &str = "NativeCompilerError: "; const HOOK_NAMES: [&str; 2] = ["pre_hooks", "post_hooks"]; @@ -41,12 +40,11 @@ pub(crate) fn compiled_models<'py>( (native_models, names): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = inputs.py(); - let native: Bound<'py, PyAny> = py.import(NATIVE_MODULE)?.into_any(); let stages: ModelStages<'py> = ModelStages { - placeholders: native.getattr("model_placeholders")?, - validate_hooks: native.getattr("validate_hook_sql_syntax")?, - validate_sql: native.getattr("validate_sql_syntax")?, - native_input: native.getattr("NativeModelInput")?, + placeholders: registry::native(py, NativeAttr::ModelPlaceholders)?, + validate_hooks: registry::native(py, NativeAttr::ValidateHookSqlSyntax)?, + validate_sql: registry::native(py, NativeAttr::ValidateSqlSyntax)?, + native_input: registry::native(py, NativeAttr::NativeModelInput)?, }; let analysis_enabled: bool = context.sql_analysis_enabled.is_truthy()?; let mut models: Vec> = Vec::new(); @@ -173,7 +171,7 @@ fn compiled_model<'py>( ); } let analysis: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "HandBuiltAnalysis")?, + ®istry::class(py, RecordId::HandBuiltAnalysis)?, &[ ("session", session_analysis), ("fallback", fallback), @@ -199,7 +197,7 @@ fn fallback_columns<'py>( ) -> PyResult> { let py: Python<'py> = analysis_query_sql.py(); let query: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_MODELS_MODULE, "NativeColumnQuery")?, + ®istry::class(py, RecordId::NativeColumnQuery)?, &[ ("sql", analysis_query_sql.clone()), ("mode", PyString::new(py, "parse").into_any()), @@ -207,7 +205,7 @@ fn fallback_columns<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "infer_native_query_column_rows")?, + ®istry::native(py, NativeAttr::InferNativeQueryColumnRows)?, &[ ("queries", PyTuple::new(py, [query])?.into_any()), ("profile", context.profile.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs index e848b2a2cf..1a57642e0f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs @@ -15,16 +15,15 @@ use crate::bindings::_helpers::compile_driver::assembly_models::{ binding_rows, compiled_models, project_binding_diagnostics, }; use crate::bindings::_helpers::compile_driver::assembly_sql_tests::assembled_sql_tests; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; use crate::bindings::_helpers::model_config::template_data::early_model_templates; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_errors::report_scope_index_errors; use crate::bindings::_helpers::scopes::scope_validation::scope_index_with_compile_usages; -const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const ADAPTER_TYPES_MODULE: &str = "sqlbuild.adapter.contract.types"; -const SCENARIO_CONFIG_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const SOURCE_SLOT: &str = "source"; const SEED_SLOT: &str = "seed"; const AUDIT_SLOT: &str = "audit"; @@ -96,7 +95,7 @@ pub(crate) fn assembled_project<'py>( let project: Bound<'py, PyAny> = project_view(inputs, &fields, &models)?; let binding_results: Bound<'py, PyDict> = binding_rows(&context.analyses)?; call_with( - &imported(py, NATIVE_MODULE, "complete_semantic_project")?, + ®istry::native(py, NativeAttr::CompleteSemanticProject)?, &[ ("project", project), ("profile", context.profile.clone()), @@ -166,13 +165,13 @@ fn connection_database_fallback<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", inputs.getattr("project_config")?), ("local_config", inputs.getattr("local_config")?), ], )?; - if adapter_name.eq(imported(py, ADAPTER_TYPES_MODULE, "BuiltinAdapter")?.getattr("DUCKDB")?)? { + if adapter_name.eq(host::entry(py, AdapterHost::BuiltinAdapter)?.getattr("DUCKDB")?)? { return Ok(py.None().into_bound(py)); } str_or_none(&connection.call_method1("get", ("database",))?) @@ -227,11 +226,7 @@ fn project_fields<'py>( ( "scenario", call_with( - &imported( - py, - SCENARIO_CONFIG_MODULE, - "resolve_effective_scenario_config", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveScenarioConfig)?, &[ ("project_config", project_config.clone()), ("local_config", inputs.getattr("local_config")?), @@ -334,7 +329,7 @@ fn project_view<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let owner: Bound<'py, PyAny> = inputs.getattr("native_project")?; - let native_model: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeCompiledModel")?; + let native_model: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeCompiledModel)?; let mut all_native: bool = true; for model in models.as_any().try_iter()? { if !model?.is_instance(&native_model)? { @@ -361,5 +356,5 @@ fn project_view<'py>( } let arguments: Bound<'py, PyDict> = fields.copy()?; arguments.set_item("sql_expansions", sql_expansions)?; - imported(py, COMPILE_MODELS_MODULE, "CompiledProject")?.call((), Some(&arguments)) + registry::class(py, RecordId::CompiledProject)?.call((), Some(&arguments)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs index e085317b33..9e768fec7e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs @@ -6,12 +6,9 @@ use pyo3::types::{PyList, PyTuple}; use crate::bindings::_helpers::boundary::panics::NativeCompilerError; use crate::bindings::_helpers::compile_driver::assembly_checks::test_model_query_overrides; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; const DECIMAL_MODULE: &str = "decimal"; const DATACLASSES_MODULE: &str = "dataclasses"; const SQL_TEST_INPUT_FAILURE: &str = "input"; @@ -23,7 +20,7 @@ pub(crate) fn assembled_sql_tests<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let assemblies: Vec> = call_with( - &imported(py, NATIVE_MODULE, "assemble_native_sql_tests")?, + ®istry::native(py, NativeAttr::AssembleNativeSqlTests)?, &[("inputs", inputs.clone())], )? .try_iter()? @@ -37,9 +34,9 @@ pub(crate) fn assembled_sql_tests<'py>( "native SQL test assembly returned a different number of tests", )); } - let report: Bound<'py, PyAny> = imported(py, COLLECTOR_MODULE, "report_compile_diagnostic")?; + let report: Bound<'py, PyAny> = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; let mocked_input: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompileModelSqlTestInputPayload")?; + registry::class(py, RecordId::CompileModelSqlTestInputPayload)?; let mut tests: Vec> = Vec::with_capacity(assemblies.len()); for (test_input, assembly) in test_inputs.iter().zip(assemblies) { let failure: Bound<'py, PyAny> = assembly.getattr("failure")?; @@ -86,7 +83,7 @@ fn assembly_error( }; if kind == Some(SQL_TEST_INPUT_FAILURE) { let error: Bound<'_, PyAny> = - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?; + registry::error(py, ErrorId::CompileInput)?.call1((message,))?; return Ok(PyErr::from_value(error)); } Ok(NativeCompilerError::new_err(message)) @@ -99,11 +96,7 @@ fn with_model_queries<'py>( ) -> PyResult> { let py: Python<'py> = test.py(); let payload: Bound<'py, PyAny> = test.getattr("payload")?; - if !payload.is_instance(&imported( - py, - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", - )?)? { + if !payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { return Ok(test.clone()); } let replace: Bound<'py, PyAny> = imported(py, DATACLASSES_MODULE, "replace")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs index d7ba67d013..7e1e5c63e4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs @@ -25,12 +25,11 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ Status, call_with, imported, keywords, within, }; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::format_skipped_type_proof_rules; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - const BYPASS_OUTCOME: &str = "bypass"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// What one full compile reports for reuse recording. struct CommandOutcome<'py> { @@ -152,11 +151,11 @@ fn recorded_compile<'py>( let compiled: PyResult<(CommandOutcome<'py>, Bound<'py, PyAny>, Bound<'py, PyAny>)> = collect_compile_timings(|detailed_timings| { within( - &imported(py, NATIVE_MODULE, "record_compile_input_reads")?.call0()?, + ®istry::native(py, NativeAttr::RecordCompileInputReads)?.call0()?, |input_reads| { let artifact_writes: Py = new_artifact_recorder(py)?; let manager: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PreparedCompileArtifacts")?, + ®istry::native(py, NativeAttr::PreparedCompileArtifacts)?, &[ ( "enabled", @@ -194,7 +193,7 @@ fn recorded_compile<'py>( close_status(status)?; let (outcome, input_reads, artifact_writes) = compiled?; let stored: PyResult> = call_with( - &imported(py, NATIVE_MODULE, "write_reusable_compile")?, + ®istry::native(py, NativeAttr::WriteReusableCompile)?, &[ ("attempt", attempt.clone()), ("output", output.clone()), @@ -331,7 +330,7 @@ fn analysis_options<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -358,7 +357,7 @@ fn analysis_options<'py>( fn rules_failed(analysis: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = analysis.py(); let rule_phase: Bound<'_, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticPhase")?.getattr("RULE")?; + registry::class(py, RecordId::DiagnosticPhase)?.getattr("RULE")?; for diagnostic in analysis.getattr("diagnostics")?.try_iter()? { let diagnostic: Bound<'_, PyAny> = diagnostic?; if diagnostic.getattr("is_error")?.is_truthy()? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs index 35a99e92ff..4eba2d7bfc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs @@ -4,14 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyString, PyTuple}; use sqlbuild_config::project::main::unsupported_target_warehouse::unsupported_target_warehouse; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const SPEC_ERRORS_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const SESSION_WAREHOUSE_MODULE: &str = "sqlbuild.adapter.discovery.main._session_warehouse_support"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; const LOCAL_CONFIG_FILENAME: &str = "sqlbuild_local.toml"; const SQL_ANALYSIS_KEY: &str = "sql_analysis"; @@ -29,8 +27,7 @@ fn project_config_error(py: Python<'_>, message: &str, help: Option<&str>) -> Py let raised = (|| -> PyResult> { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; - imported(py, DISCOVERY_ERRORS_MODULE, "ProjectConfigError")? - .call((message,), Some(&keywords)) + registry::error(py, ErrorId::ProjectConfig)?.call((message,), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), @@ -66,7 +63,7 @@ fn reject_local_sql_analysis_opt_out(config: &ProjectConfiguration<'_, '_>) -> P local_settings.getattr(SQL_ANALYSIS_KEY)?, ], )?; - imported(py, NATIVE_MODULE, "validate_local_sql_analysis")? + registry::native(py, NativeAttr::ValidateLocalSqlAnalysis)? .call1((config.project_dir, flags))?; Ok(()) } @@ -94,11 +91,7 @@ fn session_warehouse_support( project_dir: &Bound<'_, PyAny>, ) -> PyResult { call_with( - &imported( - py, - SESSION_WAREHOUSE_MODULE, - "adapter_supports_session_warehouse", - )?, + &host::entry(py, AdapterHost::AdapterSupportsSessionWarehouse)?, &[ ("adapter_name", adapter_name.clone()), ("project_dir", project_dir.clone()), @@ -114,7 +107,7 @@ fn reject_group( (target, group, value): (String, String, String), ) -> PyErr { let raised = (|| -> PyResult { - let file_path: String = imported(py, NATIVE_MODULE, "resolve_config_file_path")? + let file_path: String = registry::native(py, NativeAttr::ResolveConfigFilePath)? .call1((project_dir, local))? .str()? .extract()?; @@ -151,7 +144,7 @@ fn reject_unsupported_target_warehouses(config: &ProjectConfiguration<'_, '_>) - return Ok(()); }; let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", config.project_config.clone()), ("local_config", config.local_config.clone()), @@ -175,8 +168,8 @@ fn reject_invalid_target_connections(config: &ProjectConfiguration<'_, '_>) -> P names.extend(target_names(config.local_config)?); names.sort(); names.dedup(); - let resolve: Bound<'_, PyAny> = imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?; - let spec_error: Bound<'_, PyAny> = imported(py, SPEC_ERRORS_MODULE, "SpecConfigError")?; + let resolve: Bound<'_, PyAny> = registry::native(py, NativeAttr::ResolveTargetConfig)?; + let spec_error: Bound<'_, PyAny> = registry::error(py, ErrorId::SpecConfig)?; for name in names { let resolved: PyResult> = call_with( &resolve, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs index a07e375fd3..3f5f2f5bd3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs @@ -9,15 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_model_config::templates::main::deferred_target_segments::deferred_target_segments; use sqlbuild_model_config::templates::models::DeferredSegment; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const PROJECT_VARS_MODULE: &str = "sqlbuild._native"; const PRESERVE_TARGET_VALUE: &str = "preserve"; /// `sorted(DEFERRED_TARGET_CONTEXT_KEYS)`. const DEFERRED_TARGET_CONTEXT_KEYS: [&str; 2] = ["database", "schema"]; @@ -117,7 +112,7 @@ fn deferred_location<'py>( ], )?; call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ("database", database), ("schema", schema), @@ -155,7 +150,7 @@ fn target_field<'py>( .iter() .map(|key| (*key).to_owned()) .collect(); - let render: Bound<'py, PyAny> = imported(py, PROJECT_VARS_MODULE, "render_project_var_text")?; + let render: Bound<'py, PyAny> = registry::native(py, NativeAttr::RenderProjectVarText)?; let mut resolved: String = String::new(); for segment in deferred_target_segments(&template, &logical, &context_keys, &variables) { match segment { @@ -182,9 +177,7 @@ fn target_field<'py>( /// A `PlannerInputError` with `message` and the default code. fn planner_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::PlannerInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -322,7 +315,7 @@ fn resolve_model_deferral_inputs<'py>( &[], ), )?; - let inputs: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "DeferralInputs")?; + let inputs: Bound<'py, PyAny> = registry::class(py, RecordId::DeferralInputs)?; let defer_to: Bound<'py, PyAny> = arguments.value("defer_to")?; if defer_to.is_none() { return inputs.call0(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs index f2b8f5325c..b5fbf09e5b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs @@ -8,8 +8,9 @@ use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{PyErr, pyclass, pyfunction, pymethods, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{context_variable, imported}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const CONTEXT_VARIABLE_NAME: &str = "sqlbuild_collected_compile_diagnostics"; static ACTIVE_COLLECTOR: PyOnceLock> = PyOnceLock::new(); @@ -122,8 +123,7 @@ pub(crate) fn report_compile_diagnostic( keywords.set_item("help", diagnostic.getattr("help")?)?; let message: String = format!("{}{pointer}", diagnostic.getattr("message")?.str()?); Err(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call((message,), Some(&keywords))?, + registry::error(py, ErrorId::CompileInput)?.call((message,), Some(&keywords))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs index 0787d960c5..19874d97ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs @@ -16,9 +16,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::graph::views::project_lineage_views; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; /// Indexes of the `CompiledObjectKey`s a diff graph names, assigned on first sight. @@ -111,7 +111,7 @@ fn diff_graph<'py>(project: &Bound<'py, PyAny>, keys: &KeyIndexes<'py>) -> PyRes /// The `PlannerInputError` a rejected selector raises. fn selector_error(py: Python<'_>, error: SelectorError) -> PyErr { let raised: PyResult> = (|| { - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")?.call( + registry::error(py, ErrorId::PlannerInput)?.call( (error.message,), Some(&keywords( py, @@ -202,7 +202,7 @@ fn run_diff_pipeline<'py>( &["cli_vars", "external_sql_reference_resolver"], ), )?; - let build: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "build_compiled_project")?; + let build: Bound<'py, PyAny> = registry::native(py, NativeAttr::BuildCompiledProject)?; let compile = |target: &str| -> PyResult> { call_with( &build, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs index 36d097f030..09b049f239 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs @@ -9,7 +9,7 @@ use crate::bindings::_helpers::compile_driver::config_validation::{ }; use crate::bindings::_helpers::compile_driver::integration_loaders::integration_loader_functions; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, within, + KeywordArguments, call_with, keywords, within, }; use crate::bindings::_helpers::discovery::declaration_records::{ discover_adapter_file, discover_audit_files, discover_constant_files, discover_enum_files, @@ -29,16 +29,13 @@ use crate::bindings::_helpers::discovery::yaml_records::{ discover_schema_files, discover_source_files, }; use crate::bindings::_helpers::event_exporting::scope::configure_discovered_event_exporters; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const FRONTIER_MODULE: &str = "sqlbuild._native"; -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const VALIDATION_MODULE: &str = "sqlbuild.compiler.discovery._helpers.validation.discovery"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const SQL_ANALYSIS_SETTING_KEY: &str = "sql_analysis"; -const FILESYSTEM_MODULE: &str = "sqlbuild._native"; -const SINKS_MODULE: &str = "sqlbuild.compiler.discovery._helpers.filesystem.command_output_sinks"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; /// Declaration file collections counted into the declaration-parse lifecycle, in field order. const DECLARATION_COLLECTIONS: [&str; 14] = [ @@ -102,41 +99,52 @@ const FILE_DISCOVERERS: [(&str, FileDiscoverer); 14] = [ ]; /// Discoverers of imported project Python that take `project_dir` and `providers`. -const PYTHON_DISCOVERERS: [(&str, &str, &str); 5] = [ +const PYTHON_DISCOVERERS: [(&str, Discoverer); 5] = [ ( "python_nodes", - FILESYSTEM_MODULE, - "discover_python_node_functions", + Discoverer::Native(NativeAttr::DiscoverPythonNodeFunctions), ), ( "materialization_files", - FILESYSTEM_MODULE, - "discover_materialization_files", + Discoverer::Native(NativeAttr::DiscoverMaterializationFiles), ), ( "hook_functions", - FILESYSTEM_MODULE, - "discover_hook_functions", + Discoverer::Native(NativeAttr::DiscoverHookFunctions), ), ( "event_exporters", - FILESYSTEM_MODULE, - "discover_event_exporter_functions", + Discoverer::Native(NativeAttr::DiscoverEventExporterFunctions), ), ( "command_output_sinks", - SINKS_MODULE, - "discover_command_output_sink_functions", + Discoverer::Host(UserCodeHost::DiscoverCommandOutputSinkFunctions), ), ]; +/// Where a project-Python discoverer lives. +#[derive(Clone, Copy)] +enum Discoverer { + Native(NativeAttr), + Host(UserCodeHost), +} + +impl Discoverer { + fn function<'py>(self, py: Python<'py>) -> PyResult> { + match self { + Self::Native(id) => registry::native(py, id), + Self::Host(entry) => host::entry(py, entry), + } + } +} + /// Python node groups whose relative paths count as imported project Python. const PYTHON_NODE_GROUPS: [&str; 5] = ["loaders", "tasks", "assets", "checks", "audit_factories"]; /// `OperationLifecycle(operation_kind="project", operation_name=name)`. fn lifecycle<'py>(py: Python<'py>, name: &str) -> PyResult> { call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", "project".into_pyobject(py)?.into_any()), ("operation_name", name.into_pyobject(py)?.into_any()), @@ -190,11 +198,11 @@ fn discover_project_inputs<'py>( }, )?; call_with( - &imported(py, FRONTIER_MODULE, "compile_frontier")?, + ®istry::native(py, NativeAttr::CompileFrontier)?, &[ ( "until", - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")? + registry::class(py, RecordId::CompilerStage)? .getattr("DISCOVERED_PROJECT_INPUTS")?, ), ("python_stage", stage.into_any()), @@ -210,9 +218,9 @@ fn assembled_inputs<'py>( ) -> PyResult> { let project_dir: &Bound<'py, PyAny> = request.project_dir.bind(py); let project_config: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "load_project_config_file")?.call1((project_dir,))?; + registry::native(py, NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; let local_config: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "load_local_config_file")?.call1((project_dir,))?; + registry::native(py, NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?; reject_invalid_project_configuration(&ProjectConfiguration { project_dir, project_config: &project_config, @@ -240,7 +248,7 @@ fn assembled_inputs<'py>( request.extract_output_column_locations, ), )?; - imported(py, VALIDATION_MODULE, "validate_discovered_inputs")?.call1((&discovered,))?; + host::entry(py, UserCodeHost::ValidateDiscoveredInputs)?.call1((&discovered,))?; configure_discovered_event_exporters(&discovered)?; Ok(discovered) } @@ -364,16 +372,16 @@ fn declaration_files<'py>( fn imported_python<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = project_dir.py(); let providers: Bound<'py, PyAny> = call_with( - &imported(py, FILESYSTEM_MODULE, "discover_provider_classes")?, + ®istry::native(py, NativeAttr::DiscoverProviderClasses)?, &[("project_dir", project_dir.clone())], )?; let python: Bound<'py, PyDict> = PyDict::new(py); python.set_item("providers", &providers)?; - for (field, module, name) in PYTHON_DISCOVERERS { + for (field, discoverer) in PYTHON_DISCOVERERS { python.set_item( field, call_with( - &imported(py, module, name)?, + &discoverer.function(py)?, &[ ("project_dir", project_dir.clone()), ("providers", providers.clone()), @@ -480,7 +488,7 @@ fn project_inputs<'py>( fields.push(("adapter_file", declarations.get_item("adapter_file")?)); fields.push(("native_session", declarations.get_item("native_session")?)); call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &fields, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs index 95088a29f3..9b41b1d58b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs @@ -8,19 +8,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, within, }; use crate::bindings::_helpers::model_config::template_data::run_id; - -const CALL_STORE_MODULE: &str = "sqlbuild.compiler.compile._helpers.macro_bridge.call_store"; -const ATTACHMENT_MODULE: &str = "sqlbuild._native"; -const DECLARATION_SCOPE_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const MACROS_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const ATTACH_CHECKS_MODULE: &str = "sqlbuild._native"; -const ATTACH_RESOURCES_MODULE: &str = "sqlbuild._native"; -const INPUTS_VIEW_MODULE: &str = "sqlbuild._native"; -const INPUTS_WITH_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; const REQUIRED: [&str; 2] = ["discovered_inputs", "adapter_context"]; const OPTIONAL: [&str; 8] = [ @@ -71,7 +62,7 @@ fn build_compile_inputs<'py>( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "build_compile_inputs", options, (&REQUIRED, &OPTIONAL))?; let collector: Bound<'py, PyAny> = - imported(py, COLLECTOR_MODULE, "collect_compile_diagnostics")?.call0()?; + registry::native(py, NativeAttr::CollectCompileDiagnostics)?.call0()?; let (built, violations): (Bound<'py, PyAny>, Bound<'py, PyAny>) = within(&collector, |violations| { Ok((compile_inputs(py, &arguments)?, violations.clone())) @@ -89,7 +80,7 @@ fn build_compile_inputs<'py>( .collect::>>()?, )?; call_with( - &imported(py, INPUTS_WITH_MODULE, "inputs_with")?, + ®istry::native(py, NativeAttr::InputsWith)?, &[("inputs", built), ("diagnostics", diagnostics.into_any())], ) } @@ -103,11 +94,11 @@ fn compile_inputs<'py>( let adapter_context: Bound<'py, PyAny> = arguments.value("adapter_context")?; let target: TargetContext<'py> = target_context(arguments, &discovered, &adapter_context)?; let run_id: Bound<'py, PyAny> = run_id(&arguments.value("run_id")?)?; - let loaded_macros: Bound<'py, PyAny> = imported(py, MACROS_MODULE, "load_project_macros")? + let loaded_macros: Bound<'py, PyAny> = host::entry(py, UserCodeHost::LoadProjectMacros)? .call1((discovered.getattr("macro_files")?,))?; let syntax: Bound<'py, PyAny> = adapter_context.getattr("sql_lexical_syntax")?; let declaration_scope: Bound<'py, PyAny> = call_with( - &imported(py, DECLARATION_SCOPE_MODULE, "build_declaration_scope")?, + ®istry::native(py, NativeAttr::BuildDeclarationScope)?, &[ ("discovered_inputs", discovered.clone()), ("loaded_macros", loaded_macros), @@ -117,7 +108,7 @@ fn compile_inputs<'py>( )?; let value_renderer: Bound<'py, PyAny> = adapter_context.getattr("value_renderer")?; let model_context: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "ModelInputBuildContext")?, + ®istry::class(py, RecordId::ModelInputBuildContext)?, &[ ("effective_vars", target.vars.clone()), ("effective_settings", target.settings.clone()), @@ -187,7 +178,7 @@ fn compile_inputs<'py>( ], )?; call_with( - &imported(py, INPUTS_VIEW_MODULE, "compile_inputs_view")?, + ®istry::native(py, NativeAttr::CompileInputsView)?, &[ ("fields", fields.into_any()), ("native_project", models.native_project), @@ -203,11 +194,7 @@ fn target_context<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let selected: Bound<'py, PyAny> = call_with( - &imported( - py, - CALL_STORE_MODULE, - "target_context_with_macro_call_store", - )?, + &host::entry(py, UserCodeHost::TargetContextWithMacroCallStore)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", arguments.value("selected_target")?), @@ -225,7 +212,7 @@ fn target_context<'py>( cli_vars }; let vars: Bound<'py, PyAny> = call_with( - &imported(py, ATTACHMENT_MODULE, "build_effective_vars")?, + ®istry::native(py, NativeAttr::BuildEffectiveVars)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -234,21 +221,21 @@ fn target_context<'py>( ], )?; let settings: Bound<'py, PyAny> = call_with( - &imported(py, ATTACHMENT_MODULE, "build_effective_settings")?, + ®istry::native(py, NativeAttr::BuildEffectiveSettings)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), ], )?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config), ], )?; let macro_context: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "MacroContext")?, + ®istry::class(py, RecordId::MacroContext)?, &[ ("adapter_name", adapter_name), ("sql_analysis_enabled", settings.getattr("sql_analysis")?), @@ -287,16 +274,14 @@ fn model_build<'py>( context: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = discovered.py(); - let native: Bound<'py, PyModule> = py.import(NATIVE_MODULE)?; - let declarations: Bound<'py, PyAny> = native - .getattr("public_declaration_indexes")? - .call1((discovered,))?; + let declarations: Bound<'py, PyAny> = + registry::native(py, NativeAttr::PublicDeclarationIndexes)?.call1((discovered,))?; let enums: Bound<'py, PyAny> = declarations.get_item(0)?; let constants: Bound<'py, PyAny> = declarations.get_item(1)?; - let schemas: Bound<'py, PyAny> = native - .getattr("public_model_schema_index")? - .call1((discovered,))?; - let native_project: Bound<'py, PyAny> = native.getattr("NativeCompiledProject")?.call0()?; + let schemas: Bound<'py, PyAny> = + registry::native(py, NativeAttr::PublicModelSchemaIndex)?.call1((discovered,))?; + let native_project: Bound<'py, PyAny> = + registry::native(py, NativeAttr::NativeCompiledProject)?.call0()?; let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let model_context: Bound<'py, PyAny> = replace.call( (context,), @@ -310,7 +295,7 @@ fn model_build<'py>( )?), )?; let inputs: Bound<'py, PyAny> = call_with( - &imported(py, ATTACHMENT_MODULE, "build_model_inputs")?, + ®istry::native(py, NativeAttr::BuildModelInputs)?, &[ ("discovered_inputs", discovered.clone()), ("context", model_context), @@ -359,7 +344,7 @@ fn attached_resources<'py>( let context: &Bound<'py, PyAny> = &models.context; let settings: Bound<'py, PyAny> = context.getattr("effective_settings")?; let resources: Bound<'py, PyAny> = call_with( - &imported(py, ATTACH_RESOURCES_MODULE, "attach_resources")?, + ®istry::native(py, NativeAttr::AttachResources)?, &[ ("discovered_inputs", discovered.clone()), ("native_project", models.native_project.clone()), @@ -387,7 +372,7 @@ fn attached_resources<'py>( )?; let sources: Bound<'py, PyAny> = resources.get_item(2)?; let checks: Bound<'py, PyAny> = call_with( - &imported(py, ATTACH_CHECKS_MODULE, "attach_checks")?, + ®istry::native(py, NativeAttr::AttachChecks)?, &[ ("discovered_inputs", discovered.clone()), ("native_project", models.native_project.clone()), @@ -426,11 +411,7 @@ fn effective_connection<'py>( return Ok(resolved); } call_with( - &imported( - discovered.py(), - ATTACHMENT_MODULE, - "build_effective_connection", - )?, + ®istry::native(discovered.py(), NativeAttr::BuildEffectiveConnection)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs index 1ac19d99e2..77adbbe5b2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs @@ -5,32 +5,27 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; use sqlbuild_discovery::yaml_entries::main::integration_loader_name::integration_loader_name; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_TYPES_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// One integration: its kind constant and the runtime module building its loader functions. struct Integration { - constants_module: &'static str, - kind_constant: &'static str, - loaders_module: &'static str, - loader_factory: &'static str, + kind_constant: ThirdPartyHost, + loader_factory: ThirdPartyHost, } /// Integrations in loader order: every ingestr loader precedes every dlt loader. const INTEGRATIONS: [Integration; 2] = [ Integration { - constants_module: "sqlbuild.integrations.ingestr.constants", - kind_constant: "INGESTR_INTEGRATION_KIND", - loaders_module: "sqlbuild.integrations.ingestr.main._loaders", - loader_factory: "ingestr_loader_function", + kind_constant: ThirdPartyHost::IngestrIntegrationKind, + loader_factory: ThirdPartyHost::IngestrLoaderFunction, }, Integration { - constants_module: "sqlbuild.integrations.dlt.constants", - kind_constant: "DLT_INTEGRATION_KIND", - loaders_module: "sqlbuild.integrations.dlt.main._loaders", - loader_factory: "dlt_loader_function", + kind_constant: ThirdPartyHost::DltIntegrationKind, + loader_factory: ThirdPartyHost::DltLoaderFunction, }, ]; @@ -39,16 +34,13 @@ pub(crate) fn integration_loader_functions<'py>( py: Python<'py>, source_files: &Bound<'py, PyAny>, ) -> PyResult> { - let record_class: Bound<'py, PyAny> = - imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredLoaderFunction")?; + let record_class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredLoaderFunction)?; let external: Bound<'py, PyAny> = - imported(py, DISCOVERY_TYPES_MODULE, "LoaderConnectionMode")?.getattr("EXTERNAL")?; + registry::class(py, RecordId::LoaderConnectionMode)?.getattr("EXTERNAL")?; let mut loaders: Vec> = Vec::new(); for integration in &INTEGRATIONS { - let kind: String = - imported(py, integration.constants_module, integration.kind_constant)?.extract()?; - let factory: Bound<'py, PyAny> = - imported(py, integration.loaders_module, integration.loader_factory)?; + let kind: String = host::entry(py, integration.kind_constant)?.extract()?; + let factory: Bound<'py, PyAny> = host::entry(py, integration.loader_factory)?; for source_file in source_files.try_iter()? { let source_file: Bound<'py, PyAny> = source_file?; for source_entry in source_file.getattr("source_entries")?.try_iter()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs index 3f400630ca..234514b0e5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs @@ -12,15 +12,9 @@ use crate::bindings::_helpers::compile_command::target_writer::{ use crate::bindings::_helpers::compile_driver::python_calls::{Status, call_with, imported}; use crate::bindings::_helpers::dag_artifact::artifact::build_dag_json; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const OUTPUT_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const EMPTY_DAG_PATH: &str = ""; const TARGET_DIRECTORY: &str = "target"; const DEFAULT_DAG_FILE: &str = "sqlbuild_dag.json"; @@ -62,14 +56,14 @@ pub(crate) fn manifest_payload<'py>( let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let graph: Bound<'py, PyAny> = analysis.getattr("graph")?; let adapter_type: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config.clone()), ("local_config", discovered.getattr("local_config")?), ], )?; let payload: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_manifest")?, + ®istry::native(py, NativeAttr::BuildManifest)?, &[ ("project", graph.getattr("project")?), ("plan_output", py.None().into_bound(py)), @@ -166,31 +160,28 @@ pub(crate) fn sql_test_planning_diagnostics<'py>( Ok(diagnostics) => return Ok(diagnostics), Err(error) => error, }; - let compile_input_error: Bound<'py, PyAny> = - imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")?; - let planner_input_error: Bound<'py, PyAny> = - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")?; + let compile_input_error: Bound<'py, PyAny> = registry::error(py, ErrorId::CompileInput)?; + let planner_input_error: Bound<'py, PyAny> = registry::error(py, ErrorId::PlannerInput)?; let value: Bound<'py, PyAny> = error.value(py).clone().into_any(); if !(value.is_instance(&compile_input_error)? || value.is_instance(&planner_input_error)?) { return Err(error); } - let reference_error: Bound<'py, PyAny> = - imported(py, COMPILE_EXCEPTIONS_MODULE, "SqlTestReferenceError")?; + let reference_error: Bound<'py, PyAny> = registry::error(py, ErrorId::SqlTestReference)?; let location: Bound<'py, PyAny> = if value.is_instance(&reference_error)? { value.getattr("location")? } else { py.None().into_bound(py) }; let diagnostic: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ ( "phase", - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticPhase")?.getattr("TEST")?, + registry::class(py, RecordId::DiagnosticPhase)?.getattr("TEST")?, ), ( "severity", - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?, ), ("code", value.getattr("code")?), ("message", value.str()?.into_any()), @@ -214,7 +205,7 @@ pub(crate) fn write_artifacts<'py>( let written: Bound<'py, PyAny> = if skip_write { let zero: Bound<'py, PyAny> = 0_i64.into_pyobject(py)?.into_any(); call_with( - &imported(py, OUTPUT_MODELS_MODULE, "WrittenTarget")?, + ®istry::class(py, RecordId::WrittenTarget)?, &[ ("model_count", zero.clone()), ("seed_count", zero.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs index 4f929f4271..f79c418d9b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs @@ -15,26 +15,19 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::frontier::stage_capture::write_plan_capture; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::{format_rules_result, rules_progress}; use crate::bindings::_helpers::rules::rules_config::load_rules_config; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_CONFIG_MODULE: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const ANALYSIS_SELECTION_MODULE: &str = "sqlbuild._native"; -const TIMING_TRACKER_MODULE: &str = "sqlbuild._native"; -const RULE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const RULE_THREADS_MODULE: &str = "sqlbuild._native"; -const RULE_MODELS_MODULE: &str = "sqlbuild._native"; -const RUNTIME_MODELS_MODULE: &str = "sqlbuild.runtime.contracts.models"; const GENERIC_DIALECT: &str = "generic"; /// The current build's phase timing tracker, when one is recording. fn timing_tracker(py: Python<'_>) -> PyResult> { - imported(py, TIMING_TRACKER_MODULE, "BuildPhaseTimingTracker")?.call_method0("current") + registry::native(py, NativeAttr::BuildPhaseTimingTracker)?.call_method0("current") } /// Report `message` through `on_progress` when a callback is set. @@ -81,16 +74,12 @@ fn compiled_phase<'py>( let options: Bound<'py, PyAny> = if options.is_truthy()? { options.clone() } else { - imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?.call0()? + registry::class(py, RecordId::CompilePipelineOptions)?.call0()? }; let configured: Bound<'py, PyAny> = options.getattr("connection_config")?; let connection_config: Bound<'py, PyAny> = if configured.is_none() { call_with( - &imported( - py, - EFFECTIVE_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", options.getattr("selected_target")?), @@ -105,7 +94,7 @@ fn compiled_phase<'py>( let compiled: PyResult> = (|| { let selection: Bound<'py, PyAny> = compile_analysis_selection(&options, discovered)?; call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -133,7 +122,7 @@ fn compiled_phase<'py>( &format!("Compiled project. ({compile_seconds:.2}s)"), )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompiledProjectPhaseResult")?, + ®istry::class(py, RecordId::CompiledProjectPhaseResult)?, &[ ("project", project), ("connection_config", connection_config), @@ -194,14 +183,13 @@ fn reject_compile_errors( } else { py.None().into_bound(py) }; - let error: Bound<'_, PyAny> = imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")? - .call( - (lines.join("\n"),), - Some(&keywords( - py, - &[("code", first.getattr("code")?), ("help", help)], - )?), - )?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::CompileInput)?.call( + (lines.join("\n"),), + Some(&keywords( + py, + &[("code", first.getattr("code")?), ("help", help)], + )?), + )?; Err(PyErr::from_value(error)) } @@ -221,7 +209,7 @@ fn run_compile_pipeline<'py>( let discovered: Bound<'py, PyAny> = arguments.value("discovered_inputs")?; let adapter: Bound<'py, PyAny> = arguments.value("adapter")?; let options: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_SELECTION_MODULE, "apply_unit_test_selectors")?, + ®istry::native(py, NativeAttr::ApplyUnitTestSelectors)?, &[ ("options", arguments.value("options")?), ("discovered_inputs", discovered.clone()), @@ -229,7 +217,7 @@ fn run_compile_pipeline<'py>( )?; let hooks: Bound<'py, PyAny> = arguments.value("hooks")?; let hooks: Bound<'py, PyAny> = if hooks.is_none() { - imported(py, RUNTIME_MODELS_MODULE, "ConnectionHooks")?.call0()? + host::entry(py, ExecutorHost::ConnectionHooks)?.call0()? } else { hooks }; @@ -253,7 +241,7 @@ fn run_rules_gate<'py>( (project, graph): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult<()> { let py: Python<'py> = discovered.py(); - let rules_error: Bound<'py, PyAny> = imported(py, RULE_EXCEPTIONS_MODULE, "RulesError")?; + let rules_error: Bound<'py, PyAny> = registry::error(py, ErrorId::Rules)?; let project_dir: Bound<'py, PyAny> = discovered.getattr("project_dir")?; if project_dir.is_none() { return Err(PyErr::from_value( @@ -264,7 +252,7 @@ fn run_rules_gate<'py>( load_rules_config(py, project_dir.extract()?)?.into_bound(py); if options.getattr("no_cache")?.is_truthy()? { let cache: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "RulesCacheConfig")?, + ®istry::class(py, RecordId::RulesCacheConfig)?, &[("enabled", false.into_pyobject(py)?.to_owned().into_any())], )?; config = imported(py, "dataclasses", "replace")? @@ -279,11 +267,7 @@ fn run_rules_gate<'py>( GENERIC_DIALECT.into_pyobject(py)?.into_any() }; let selected_keys: Bound<'py, PyAny> = call_with( - &imported( - py, - ANALYSIS_SELECTION_MODULE, - "resolve_configured_rule_selection", - )?, + ®istry::native(py, NativeAttr::ResolveConfiguredRuleSelection)?, &[ ("options", options.clone()), ("discovered_inputs", discovered.clone()), @@ -291,7 +275,7 @@ fn run_rules_gate<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &imported(py, RULE_THREADS_MODULE, "evaluate_rules")?, + ®istry::native(py, NativeAttr::EvaluateRules)?, &[ ("graph", graph.clone()), ("discovered_inputs", discovered.clone()), @@ -312,7 +296,7 @@ fn run_rules_gate<'py>( return Ok(()); } let summary: Bound<'py, PyAny> = call_with( - &imported(py, RULE_MODELS_MODULE, "RulesResult")?, + ®istry::class(py, RecordId::RulesResult)?, &[ ("findings", findings), ("evaluated_models", result.getattr("evaluated_models")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs index 5b964605ec..9ffdda1e23 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs @@ -4,15 +4,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyFloat}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const PIPELINE_MODELS_MODULE: &str = "sqlbuild._native"; -const PLANNER_MODELS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// Discovered Python node functions whose selectors need the Python graph. const PYTHON_NODE_FUNCTIONS: [&str; 4] = [ "loader_functions", @@ -62,8 +57,7 @@ pub(crate) fn compile_analysis_selection<'py>( break; } } - let selection: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?; + let selection: Bound<'py, PyAny> = registry::class(py, RecordId::CompileAnalysisSelection)?; let no_cache: Bound<'py, PyAny> = options.getattr("no_cache")?; if (options .getattr("resolve_python_run_selectors")? @@ -106,7 +100,7 @@ fn build_project_graph_from_compiled_project<'py>( /// The project's natively held graph; dict views are built only when a boundary reads them. pub(crate) fn project_graph<'py>(project: &Bound<'py, PyAny>) -> PyResult> { call_with( - &imported(project.py(), PIPELINE_MODELS_MODULE, "ProjectGraph")?, + ®istry::class(project.py(), RecordId::ProjectGraph)?, &[ ("project", project.clone()), ("native", project.getattr("lineage_graph")?), @@ -154,7 +148,7 @@ fn build_static_pipeline_result<'py>( )?; let context: Bound<'py, PyAny> = arguments.value("context")?; call_with( - &imported(py, PIPELINE_MODELS_MODULE, "CompilePipelineResult")?, + ®istry::class(py, RecordId::CompilePipelineResult)?, &[ ("project", context.getattr("project")?), ("plan_output", arguments.value("plan_output")?), @@ -181,7 +175,7 @@ fn run_compile_only_pipeline<'py>( ), )?; let compiled: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "compile_project_phase")?, + ®istry::native(py, NativeAttr::CompileProjectPhase)?, &[ ("discovered_inputs", arguments.value("discovered_inputs")?), ("adapter", arguments.value("adapter")?), @@ -190,12 +184,12 @@ fn run_compile_only_pipeline<'py>( ], )?; call_with( - &imported(py, PIPELINE_MODELS_MODULE, "CompilePipelineResult")?, + ®istry::class(py, RecordId::CompilePipelineResult)?, &[ ("project", compiled.getattr("project")?), ( "plan_output", - imported(py, PLANNER_MODELS_MODULE, "PlanOutput")?.call0()?, + registry::class(py, RecordId::PlanOutput)?.call0()?, ), ("compile_seconds", compiled.getattr("compile_seconds")?), ("planning_seconds", PyFloat::new(py, 0.0).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs index a2660a48e6..142b6a0df1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs @@ -1,23 +1,18 @@ //! Plan a compiled project over an open warehouse connection, and open that connection. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::exceptions::PyException; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::phases::monotonic; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::python_node_graph::run_selection_from_inputs; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXECUTION_PLAN_MODULE: &str = "sqlbuild._native"; -const MATERIALIZATIONS_MODULE: &str = "sqlbuild.compiler.pipeline._helpers.materializations"; -const PIPELINE_MODELS_MODULE: &str = "sqlbuild._native"; const SINGLE_CONNECTION: i64 = 1; /// The deferred relation locations and the warehouse relations behind them, when deferring. @@ -38,7 +33,6 @@ pub(crate) fn pipeline_result<'py>( on_progress: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project.py(); - let native = py.import(NATIVE_MODULE)?; let discovered: &Bound<'py, PyAny> = inputs.discovered_inputs; let select: Bound<'py, PyAny> = options.getattr("select")?; let exclude: Bound<'py, PyAny> = options.getattr("exclude")?; @@ -57,7 +51,7 @@ pub(crate) fn pipeline_result<'py>( .is_truthy()? { let graph: Bound<'py, PyAny> = call_with( - &native.getattr("build_project_graph_from_compiled_project")?, + ®istry::native(py, NativeAttr::BuildProjectGraphFromCompiledProject)?, &[("project", project.clone())], )?; run_selection = @@ -65,16 +59,15 @@ pub(crate) fn pipeline_result<'py>( selected_sql_keys = run_selection.getattr("sql_keys")?; selected_python_node_names = run_selection.getattr("python_node_names")?; python_read_source_names = call_with( - &native.getattr("python_read_source_names_for")?, + ®istry::native(py, NativeAttr::PythonReadSourceNamesFor)?, &[ ("discovered_inputs", discovered.clone()), ("python_node_names", selected_python_node_names.clone()), ], )?; } - let planner_models = py.import(NATIVE_RECORDS_MODULE)?; let selection: Bound<'py, PyAny> = call_with( - &planner_models.getattr("PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[ ("select", select), ("exclude", exclude), @@ -84,7 +77,7 @@ pub(crate) fn pipeline_result<'py>( ], )?; let overrides: Bound<'py, PyAny> = call_with( - &shape_class(planner_models.as_any(), "PlannerOverrides")?, + ®istry::class(py, RecordId::PlannerOverrides)?, &option_fields( options, &[ @@ -104,11 +97,11 @@ pub(crate) fn pipeline_result<'py>( &["defer_sources_to", "source_deferral_enabled"], )?); let deferral: Bound<'py, PyAny> = call_with( - &shape_class(planner_models.as_any(), "DeferralInputs")?, + ®istry::class(py, RecordId::DeferralInputs)?, &deferral_fields, )?; let policies: Bound<'py, PyAny> = call_with( - &shape_class(planner_models.as_any(), "PlannerPolicies")?, + ®istry::class(py, RecordId::PlannerPolicies)?, &option_fields( options, &[ @@ -120,7 +113,7 @@ pub(crate) fn pipeline_result<'py>( )?, )?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTION_PLAN_MODULE, "build_execution_plan")?, + ®istry::native(py, NativeAttr::BuildExecutionPlan)?, &[ ("project", project.clone()), ("adapter", inputs.adapter.clone()), @@ -135,10 +128,10 @@ pub(crate) fn pipeline_result<'py>( ], )?; let custom_materializations: Bound<'py, PyAny> = - imported(py, MATERIALIZATIONS_MODULE, "load_custom_materializations")? + host::entry(py, UserCodeHost::LoadCustomMaterializations)? .call1((discovered.getattr("materialization_files")?,))?; let python_outputs: Bound<'py, PyAny> = call_with( - &native.getattr("build_python_run_plan_outputs")?, + ®istry::native(py, NativeAttr::BuildPythonRunPlanOutputs)?, &[ ("discovered_inputs", discovered.clone()), ("plan_output", plan_output), @@ -147,7 +140,7 @@ pub(crate) fn pipeline_result<'py>( ], )?; call_with( - &imported(py, PIPELINE_MODELS_MODULE, "CompilePipelineResult")?, + ®istry::class(py, RecordId::CompilePipelineResult)?, &[ ("project", project.clone()), ("plan_output", python_outputs.getattr("plan_output")?), @@ -185,10 +178,9 @@ fn deferral<'py>( if defer_to.is_none() { return Ok((py.None().into_bound(py), py.None().into_bound(py))); } - let native = py.import(NATIVE_MODULE)?; let adapter: &Bound<'py, PyAny> = inputs.adapter; let target_config: Bound<'py, PyAny> = call_with( - &native.getattr("resolve_deferred_target_config")?, + ®istry::native(py, NativeAttr::ResolveDeferredTargetConfig)?, &[ ("discovered_inputs", inputs.discovered_inputs.clone()), ("defer_to", defer_to.clone()), @@ -199,7 +191,7 @@ fn deferral<'py>( ], )?; let locations: Bound<'py, PyAny> = call_with( - &native.getattr("build_deferred_locations")?, + ®istry::native(py, NativeAttr::BuildDeferredLocations)?, &[ ("project", project.clone()), ("deferred_target_config", target_config), @@ -216,7 +208,7 @@ fn deferral<'py>( ], )?; let relations: Bound<'py, PyAny> = call_with( - &native.getattr("gather_deferred_relations")?, + ®istry::native(py, NativeAttr::GatherDeferredRelations)?, &[ ("adapter", adapter.clone()), ("connection", inputs.connection.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs index b82f324eb0..ce6ebd9b9e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs @@ -18,20 +18,11 @@ use crate::bindings::_helpers::compile_driver::targets::{ project_targets, }; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const FRONTIER_MODULE: &str = "sqlbuild._native"; -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const BUILD_INPUTS_MODULE: &str = "sqlbuild._native"; -const ASSEMBLE_MODULE: &str = "sqlbuild._native"; -const PROJECT_WITH_MODULE: &str = "sqlbuild._native"; -const PROMOTION_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; use sqlbuild_core::constants::TABLE_PROMOTION_MODE_SETTING_KEY; const LOCAL_CONFIG_FILENAME: &str = "sqlbuild_local.toml"; -const ANALYSIS_SELECTION_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const COLLECTION_RENDERING_MODULE: &str = "sqlbuild._native"; /// Optional keywords of the project graph builders. const GRAPH_OPTIONAL: [&str; 7] = [ "selected_target", @@ -63,7 +54,7 @@ pub(crate) fn lineage_mode<'py>(arguments: &KeywordArguments<'py>) -> PyResult, request: ProjectRequest) -> PyResult( let inference_profile: Bound<'py, PyAny> = adapter.call_method0("expression_inference_profile")?; let analysis_model_names: Bound<'py, PyAny> = call_with( - &imported( - py, - ANALYSIS_SELECTION_MODULE, - "resolve_analysis_model_names", - )?, + ®istry::native(py, NativeAttr::ResolveAnalysisModelNames)?, &[ ("compile_inputs", compile_inputs.clone()), ("inference_profile", inference_profile.clone()), @@ -177,7 +163,7 @@ fn python_compiled_project<'py>( ], )?; let project: Bound<'py, PyAny> = call_with( - &imported(py, ASSEMBLE_MODULE, "assemble_project")?, + ®istry::native(py, NativeAttr::AssembleProject)?, &[ ("inputs", compile_inputs.clone()), ("inference_profile", inference_profile), @@ -225,7 +211,7 @@ fn targeted_project<'py>( let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let local_config: Bound<'py, PyAny> = discovered.getattr("local_config")?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config), ("local_config", local_config.clone()), @@ -243,7 +229,7 @@ fn targeted_project<'py>( pyo3::types::PyString::new(py, PROJECT_CONFIG_FILENAME).into_any() }; let conflicts: Bound<'py, PyAny> = call_with( - &imported(py, PROMOTION_MODULE, "promotion_conflict_diagnostics")?, + ®istry::native(py, NativeAttr::PromotionConflictDiagnostics)?, &[ ("project", project.clone()), ( @@ -262,7 +248,7 @@ fn targeted_project<'py>( .collect::>>()?, )?; call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project), ("diagnostics", diagnostics.into_any()), @@ -279,18 +265,14 @@ fn frontier_compile_inputs<'py>( let discovered: &Bound<'py, PyAny> = request.discovered_inputs.bind(py); let adapter: &Bound<'py, PyAny> = request.adapter.bind(py); let collection_rendering: Bound<'py, PyAny> = call_with( - &imported( - py, - COLLECTION_RENDERING_MODULE, - "resolve_effective_collection_rendering", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveCollectionRendering)?, &[ ("project_config", discovered.getattr("project_config")?), ("declaration_override", py.None().into_bound(py)), ], )?; let adapter_context: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAdapterContext")?, + ®istry::class(py, RecordId::CompileAdapterContext)?, &[ ("value_renderer", adapter.clone()), ("collection_rendering", collection_rendering), @@ -302,7 +284,7 @@ fn frontier_compile_inputs<'py>( ], )?; let stage: Bound<'py, PyAny> = imported(py, "functools", "partial")?.call( - (imported(py, BUILD_INPUTS_MODULE, "build_compile_inputs")?,), + (registry::native(py, NativeAttr::BuildCompileInputs)?,), Some(&keywords( py, &[ @@ -325,12 +307,11 @@ fn frontier_compile_inputs<'py>( )?), )?; call_with( - &imported(py, FRONTIER_MODULE, "compile_frontier")?, + ®istry::native(py, NativeAttr::CompileFrontier)?, &[ ( "until", - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")? - .getattr("COMPILE_PROJECT_INPUTS")?, + registry::class(py, RecordId::CompilerStage)?.getattr("COMPILE_PROJECT_INPUTS")?, ), ("python_stage", stage), ], @@ -372,7 +353,7 @@ fn build_project_graph<'py>( (&["discovered_inputs", "adapter"], &optional), )?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", py_bool(py, arguments.flag("no_cache")?))], )?; selected_graph(&arguments, selection) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs index 0fa45a689e..387db3dbef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs @@ -13,12 +13,11 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PYTHON_NODE_TYPES_MODULE: &str = "sqlbuild.compiler.python_nodes.types"; -const REF_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; const SKIPPED_UPSTREAM_CODE: &str = "P501"; /// The Python node kinds and plan phases entries are built from. @@ -29,7 +28,7 @@ struct NodeKinds<'py> { impl<'py> NodeKinds<'py> { fn load(py: Python<'py>) -> PyResult { - let kinds: Bound<'py, PyAny> = imported(py, PYTHON_NODE_TYPES_MODULE, "PythonNodeKind")?; + let kinds: Bound<'py, PyAny> = host::entry(py, UserCodeHost::PythonNodeKind)?; Ok(Self { task: kinds.getattr("TASK")?, asset: kinds.getattr("ASSET")?, @@ -86,7 +85,7 @@ fn python_read_source_names_for<'py>( } let graph: Bound<'py, PyAny> = node_graph(&arguments.value("discovered_inputs")?)?; let source: Bound<'py, PyAny> = - imported(py, REF_TYPES_MODULE, "SqlResourceRefKind")?.getattr("SOURCE")?; + host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; let mut names: Vec> = Vec::new(); for reference in call_with( &graph.getattr("selected_sql_refs")?, @@ -123,7 +122,7 @@ fn build_python_run_plan_outputs<'py>( &[], ), )?; - let outputs: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PythonRunPlanOutputs")?; + let outputs: Bound<'py, PyAny> = registry::class(py, RecordId::PythonRunPlanOutputs)?; let plan_output: Bound<'py, PyAny> = arguments.value("plan_output")?; let run_selection: Bound<'py, PyAny> = arguments.value("run_selection")?; let selected: Bound<'py, PyAny> = arguments.value("selected_python_node_names")?; @@ -156,14 +155,14 @@ fn build_python_run_plan_outputs<'py>( )?), )?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ("sql_keys", plan_output.getattr("selected_keys")?), ("python_node_names", selected.clone()), ], )?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_sql_run_lifecycle")?, + ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[("selection", selection), ("python_graph", graph.clone())], )?; let entries: Bound<'py, PyTuple> = plan_entries( @@ -217,8 +216,8 @@ fn plan_entries<'py>( previous_identities: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = lifecycle.py(); - let phases: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PythonRunPhase")?; - let entry_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PythonPlanEntry")?; + let phases: Bound<'py, PyAny> = registry::class(py, RecordId::PythonRunPhase)?; + let entry_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonPlanEntry)?; let upstream: HashMap> = upstream_names(graph)?; let nodes: Bound<'py, PyAny> = graph.getattr("nodes_by_name")?; let mut entries: Vec> = Vec::new(); @@ -304,7 +303,7 @@ fn identity_status<'py>( previous_identities: &Bound<'py, PyAny>, ) -> PyResult> { let statuses: Bound<'py, PyAny> = - imported(identity.py(), NATIVE_RECORDS_MODULE, "PythonIdentityStatus")?; + registry::class(identity.py(), RecordId::PythonIdentityStatus)?; if identity.is_none() { return statuses.getattr("UNKNOWN"); } @@ -339,9 +338,9 @@ fn skipped_ingress_warnings<'py>( let nodes: Bound<'py, PyAny> = graph.getattr("nodes_by_name")?; let selected: Bound<'py, PyAny> = run_selection.getattr("python_node_names")?; let upstream: HashMap> = upstream_names(graph)?; - let warning_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "PlanWarning")?; + let warning_class: Bound<'py, PyAny> = registry::class(py, RecordId::PlanWarning)?; let severity: Bound<'py, PyAny> = - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?; + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?; let mut warnings: Vec> = Vec::new(); for loader in loaders { if !nodes.contains(loader.as_str())? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs index b534c3e314..ff9880d2d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs @@ -9,12 +9,11 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::references::source_relations::render_source_relation; -const REF_MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const REF_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; -const QUALIFIED_NAME_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; const SHARED_EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; /// The `SqlResourceRefKind` members a relation map distinguishes. @@ -26,7 +25,7 @@ struct RefKinds<'py> { impl<'py> RefKinds<'py> { fn load(py: Python<'py>) -> PyResult { - let kinds: Bound<'py, PyAny> = imported(py, REF_TYPES_MODULE, "SqlResourceRefKind")?; + let kinds: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRefKind)?; Ok(Self { model: kinds.getattr("MODEL")?, seed: kinds.getattr("SEED")?, @@ -118,7 +117,7 @@ fn planned_relation_refs<'py>( kinds: &RefKinds<'py>, ) -> PyResult> { let py: Python<'py> = plan_output.py(); - let reference: Bound<'py, PyAny> = imported(py, REF_MODELS_MODULE, "SqlResourceRef")?; + let reference: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let refs: Bound<'py, PySet> = PySet::empty(py)?; for (attribute, kind) in [ ("model_locations", &kinds.model), @@ -189,10 +188,9 @@ fn qualified_name<'py>( location: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported( + &host::entry( adapter.py(), - QUALIFIED_NAME_MODULE, - "resolve_relation_location_qualified_name", + AdapterHost::ResolveRelationLocationQualifiedName, )?, &[("adapter", adapter.clone()), ("location", location.clone())], ) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs index 9ab6883540..65b0c4e43c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs @@ -6,14 +6,9 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const SPLIT_SELECTORS_MODULE: &str = "sqlbuild._native"; -const PLANNER_SCOPES_MODULE: &str = "sqlbuild._native"; -const STATIC_SCOPE_MODULE: &str = "sqlbuild._native"; -const STATIC_RELATIONS_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; /// Whether a selection names nothing to select or exclude. @@ -28,7 +23,7 @@ fn unit_test_selectors_applied<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let resolved: Bound<'py, PyAny> = if options.is_none() { - imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?.call0()? + registry::class(py, RecordId::CompilePipelineOptions)?.call0()? } else { options.clone() }; @@ -36,11 +31,7 @@ fn unit_test_selectors_applied<'py>( return Ok(resolved); } let split: Bound<'py, PyAny> = call_with( - &imported( - py, - SPLIT_SELECTORS_MODULE, - "split_project_unit_test_selectors", - )?, + ®istry::native(py, NativeAttr::SplitProjectUnitTestSelectors)?, &[ ("select", resolved.getattr("select")?), ("exclude", resolved.getattr("exclude")?), @@ -71,13 +62,13 @@ fn planner_scopes<'py>( ) -> PyResult> { let py: Python<'py> = project.py(); call_with( - &imported(py, PLANNER_SCOPES_MODULE, "resolve_planner_scopes")?, + ®istry::native(py, NativeAttr::ResolvePlannerScopes)?, &[ ("project", project.clone()), ( "selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[ ("select", selection.getattr("select")?), ("exclude", selection.getattr("exclude")?), @@ -87,7 +78,7 @@ fn planner_scopes<'py>( ( "policies", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerPolicies")?, + ®istry::class(py, RecordId::PlannerPolicies)?, &[("auto_load_sources", selection.getattr("auto_load_sources")?)], )?, ), @@ -129,7 +120,7 @@ fn resolve_configured_rule_selection<'py>( return Ok(PyFrozenSet::empty(py)?.into_any()); } let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_compile_analysis_selection")?, + ®istry::native(py, NativeAttr::ResolveCompileAnalysisSelection)?, &[ ("options", options.clone()), ("discovered_inputs", discovered_inputs.clone()), @@ -160,7 +151,7 @@ fn resolve_analysis_model_names<'py>( return Ok(py.None().into_bound(py)); } let structural_project: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "assemble_project")?, + ®istry::native(py, NativeAttr::AssembleProject)?, &[ ("inputs", compile_inputs.clone()), ("inference_profile", inference_profile.clone()), @@ -188,7 +179,7 @@ fn deferred_locations<'py>( return Ok(py.None().into_bound(py)); } let deferred_target: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_deferred_target_config")?, + ®istry::native(py, NativeAttr::ResolveDeferredTargetConfig)?, &[ ("discovered_inputs", discovered.clone()), ("defer_to", defer_to), @@ -199,7 +190,7 @@ fn deferred_locations<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_deferred_locations")?, + ®istry::native(py, NativeAttr::BuildDeferredLocations)?, &[ ("project", project.clone()), ("deferred_target_config", deferred_target), @@ -241,7 +232,7 @@ fn compile_static_command_context<'py>( selected_keys = PyFrozenSet::empty(py)?.into_any(); } let compiled: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "compile_project_phase")?, + ®istry::native(py, NativeAttr::CompileProjectPhase)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -256,13 +247,13 @@ fn compile_static_command_context<'py>( let deferred: Bound<'py, PyAny> = deferred_locations((&discovered, &adapter), &options, &project)?; let scope: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_SCOPE_MODULE, "resolve_static_command_scope")?, + ®istry::native(py, NativeAttr::ResolveStaticCommandScope)?, &[ ("project", project.clone()), ( "selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[ ("select", options.getattr("select")?), ("exclude", options.getattr("exclude")?), @@ -275,11 +266,7 @@ fn compile_static_command_context<'py>( ], )?; let relations: Bound<'py, PyAny> = call_with( - &imported( - py, - STATIC_RELATIONS_MODULE, - "resolve_static_relation_context", - )?, + ®istry::native(py, NativeAttr::ResolveStaticRelationContext)?, &[ ("project", project.clone()), ("adapter", adapter), @@ -287,7 +274,7 @@ fn compile_static_command_context<'py>( ( "deferral", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeferralInputs")?, + ®istry::class(py, RecordId::DeferralInputs)?, &[ ("deferred_locations", deferred), ("defer_sources_to", options.getattr("defer_sources_to")?), @@ -307,7 +294,7 @@ fn compile_static_command_context<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "StaticCommandContext")?, + ®istry::class(py, RecordId::StaticCommandContext)?, &[ ("project", project), ("scope", scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs index 18b224e71d..27a3ec9e39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs @@ -4,9 +4,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// The adapter defaults every compiled location falls back to. pub(crate) struct TargetDefaults<'py> { @@ -41,8 +40,8 @@ pub(crate) fn apply_target_defaults<'py>( defaults: &TargetDefaults<'py>, ) -> PyResult> { let py: Python<'py> = project.py(); - let compiled_model: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?; - let native_seed: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeCompiledSeed")?; + let compiled_model: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledModel)?; + let native_seed: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeCompiledSeed)?; let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let mut models: Vec> = Vec::new(); for model in project.getattr("models")?.try_iter()? { @@ -98,7 +97,7 @@ pub(crate) fn apply_target_defaults<'py>( )?); } call_with( - &imported(py, NATIVE_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project.clone()), ( @@ -156,7 +155,7 @@ fn resolved_target<'py>( return Ok(target.clone()); } call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ("database", database), ("schema", schema), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs index 3713daa5ec..3bdd7d99b4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs @@ -21,16 +21,10 @@ use sqlbuild_core::text::main::python_strip::python_strip; use sqlbuild_model_config::templates::models::TemplateOptions; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; use crate::bindings::_helpers::model_config::template_data::expanded_template_data; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_RUNTIME_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_CONFIG_MODULE: &str = "sqlbuild._native"; -const SETTING_HELP_MODULE: &str = "sqlbuild._native"; /// Connection settings that identify the warehouse two targets write to. const CONNECTION_IDENTITY_KEYS: [&str; 7] = [ "account", @@ -45,6 +39,8 @@ const CONNECTION_IDENTITY_KEYS: [&str; 7] = [ const EXPLICIT_TARGET_ADAPTERS: [&str; 3] = ["snowflake", "bigquery", "databricks"]; const LOADER_NAMESPACE_LABEL: &str = "managed loader target namespace"; const DEFAULTS_HELP_PURPOSE: &str = "to set project-wide defaults"; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; const DEFAULTS_SECTION: &str = "defaults"; @@ -56,7 +52,7 @@ type TargetNamespaces = (Vec<(String, String)>, Vec); /// A `PlannerInputError` with `message`, `code` and optional `help`. fn planner_error(py: Python<'_>, message: String, code: &str, help: Option) -> PyErr { let raised: PyResult> = (|| { - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")?.call( + registry::error(py, ErrorId::PlannerInput)?.call( (message,), Some(&keywords( py, @@ -131,7 +127,7 @@ pub(crate) fn named_target_schema_strategy( let project_config: Bound<'_, PyAny> = discovered.getattr("project_config")?; let local_config: Bound<'_, PyAny> = discovered.getattr("local_config")?; let target_name: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -142,7 +138,7 @@ pub(crate) fn named_target_schema_strategy( return Ok(()); } let target_config: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", project_config), ("local_config", local_config), @@ -266,7 +262,7 @@ fn target_namespaces( let py: Python<'_> = discovered.py(); let name: Bound<'_, PyAny> = PyString::new(py, target_name).into_any(); let target_config: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -274,11 +270,7 @@ fn target_namespaces( ], )?; let runtime: Bound<'_, PyAny> = call_with( - &imported( - py, - EFFECTIVE_RUNTIME_MODULE, - "build_effective_runtime_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", name.clone()), @@ -288,11 +280,7 @@ fn target_namespaces( runtime.extract()?; let connection: Bound<'_, PyAny> = if resolved_connection.is_none() { call_with( - &imported( - py, - EFFECTIVE_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", name), @@ -460,7 +448,7 @@ fn required_target_parts( return Ok(()); }; let help: String = call_with( - &imported(py, SETTING_HELP_MODULE, "setting_help")?, + ®istry::native(py, NativeAttr::SettingHelp)?, &[ ( "purpose", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs index 8a6a95628b..4e8502b286 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs @@ -16,8 +16,9 @@ use sqlbuild_core::text::main::python_isspace::python_isspace; use crate::bindings::_helpers::analysis::sql_test_comparisons::single_comparison_sql; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; -const LIFECYCLE_TYPES: &str = "sqlbuild.adapter.contract.types"; const RUN_DIR: &str = "run"; const TESTS_DIR: &str = "tests"; const CHECKS_DIR: &str = "checks"; @@ -97,7 +98,7 @@ fn write_text(path: &Bound<'_, PyAny>, text: &str) -> PyResult<()> { fn sql_statements(result: &Bound<'_, PyAny>) -> PyResult> { let sql_kind: Bound<'_, PyAny> = - imported(result.py(), LIFECYCLE_TYPES, "LifeCycleEventKind")?.getattr("SQL")?; + host::entry(result.py(), AdapterHost::LifeCycleEventKind)?.getattr("SQL")?; let mut statements: Vec = Vec::new(); for event in result.getattr("lifecycle_events")?.try_iter()? { let event: Bound<'_, PyAny> = event?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs index f47fb226b8..2e17eb2a39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs @@ -1,5 +1,7 @@ //! Layouts of the compile and discovery value records hosted natively. +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_RELATED_LOCATION: RecordSpec = RecordSpec { @@ -52,11 +54,11 @@ const R_COMPILE_MODEL_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("logical_database", FieldDefault::None), FieldSpec::new( "time_travel_retention", - FieldDefault::Call("sqlbuild._native", "ResolvedTimeTravelRetention"), + FieldDefault::NativeCall(RecordId::ResolvedTimeTravelRetention), ), FieldSpec::new( "table_type", - FieldDefault::Call("sqlbuild._native", "ResolvedTableType"), + FieldDefault::NativeCall(RecordId::ResolvedTableType), ), ], }; @@ -181,11 +183,11 @@ const R_COMPILED_LINEAGE_COLUMN_FACT: RecordSpec = RecordSpec { FieldSpec::new("upstream_columns", FieldDefault::EmptyTuple), FieldSpec::new( "transform_kind", - FieldDefault::Member("sqlbuild._native", "ColumnTransformKind", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnTransformKind, "UNKNOWN"), ), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), ], }; @@ -299,7 +301,7 @@ const R_COMPILE_PROJECT_INPUTS: RecordSpec = RecordSpec { FieldSpec::new("effective_connection", FieldDefault::EmptyDict), FieldSpec::new( "effective_settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), FieldSpec::new("no_sql_validation", FieldDefault::False), FieldSpec::new("effective_vars", FieldDefault::EmptyDict), @@ -323,7 +325,7 @@ const R_COMPILE_PROJECT_INPUTS: RecordSpec = RecordSpec { FieldSpec::new("external_sql_reference_resolver", FieldDefault::None), FieldSpec::new( "scope_index", - FieldDefault::Call("sqlbuild._native", "ScopeIndex"), + FieldDefault::NativeCall(RecordId::ScopeIndex), ), FieldSpec { name: "declaration_scope", @@ -448,7 +450,7 @@ const R_COMPILE_SQL_TEST_INPUT: RecordSpec = RecordSpec { ), FieldSpec::new( "payload", - FieldDefault::Call("sqlbuild._native", "CompileModelSqlTestInputPayload"), + FieldDefault::NativeCall(RecordId::CompileModelSqlTestInputPayload), ), FieldSpec::new("declaration_usages", FieldDefault::EmptyTuple), FieldSpec::new("parent_name", FieldDefault::None), @@ -599,7 +601,7 @@ const R_DISCOVERED_SQL_HOOK_FILE: RecordSpec = RecordSpec { FieldSpec::new("description", FieldDefault::None), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -621,7 +623,7 @@ const R_DISCOVERED_ENUM_FILE: RecordSpec = RecordSpec { FieldSpec::required("declarations"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -638,7 +640,7 @@ const R_DISCOVERED_CONSTANT_FILE: RecordSpec = RecordSpec { FieldSpec::required("declarations"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -666,7 +668,7 @@ const R_DISCOVERED_MODEL_SCHEMA_FILE: RecordSpec = RecordSpec { FieldSpec::required("declarations"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -742,11 +744,11 @@ const R_DISCOVERED_AUDIT_FILE: RecordSpec = RecordSpec { FieldSpec::required("blocks"), FieldSpec::new( "declaration_kind", - FieldDefault::Member("sqlbuild._native", "DeclarationKind", "AUDIT"), + FieldDefault::NativeMember(RecordId::DeclarationKind, "AUDIT"), ), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -897,7 +899,7 @@ const R_DISCOVERED_MACRO_FILE: RecordSpec = RecordSpec { FieldSpec::required("contents"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -942,7 +944,7 @@ const R_DISCOVERED_LOADER_FUNCTION: RecordSpec = RecordSpec { FieldSpec::new("description", FieldDefault::None), FieldSpec::new( "connection_mode", - FieldDefault::Member("sqlbuild._native", "LoaderConnectionMode", "SQLBUILD"), + FieldDefault::NativeMember(RecordId::LoaderConnectionMode, "SQLBUILD"), ), FieldSpec::new("provider_usages", FieldDefault::EmptyTuple), ], @@ -994,11 +996,7 @@ const R_DISCOVERED_CHECK_FUNCTION: RecordSpec = RecordSpec { FieldSpec::required("depends_on"), FieldSpec::new( "severity", - FieldDefault::Member( - "sqlbuild.python_nodes.types", - "PythonCheckSeverity", - "ERROR", - ), + FieldDefault::HostMember(UserCodeHost::PythonCheckSeverity.id(), "ERROR"), ), FieldSpec::new("tags", FieldDefault::EmptyTuple), FieldSpec::new("group", FieldDefault::None), @@ -1020,7 +1018,7 @@ const R_DISCOVERED_HOOK_FUNCTION: RecordSpec = RecordSpec { FieldSpec::new("provider_usages", FieldDefault::EmptyTuple), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -1074,7 +1072,7 @@ const R_DISCOVERED_EVENT_EXPORTER: RecordSpec = RecordSpec { ), FieldSpec::new( "min_severity", - FieldDefault::Member("sqlbuild._native", "EventExportSeverity", "DEBUG"), + FieldDefault::NativeMember(RecordId::EventExportSeverity, "DEBUG"), ), FieldSpec::new("provider_usages", FieldDefault::EmptyTuple), ], @@ -1269,7 +1267,7 @@ const R_MACRO_CONTEXT: RecordSpec = RecordSpec { }, FieldSpec { name: "_collection_rendering", - default: FieldDefault::Member("sqlbuild._native", "CollectionRendering", "VALUE_LIST"), + default: FieldDefault::NativeMember(RecordId::CollectionRendering, "VALUE_LIST"), compared: false, shown: false, }, @@ -1368,7 +1366,7 @@ const R_COMPILE_MODEL_INPUT: RecordSpec = RecordSpec { FieldSpec::required("model_file"), FieldSpec::new( "config", - FieldDefault::Call("sqlbuild._native", "CompileModelConfig"), + FieldDefault::NativeCall(RecordId::CompileModelConfig), ), FieldSpec::new("query_sql", FieldDefault::Text("")), FieldSpec::new("macro_source_sql", FieldDefault::Text("")), @@ -1518,16 +1516,16 @@ const R_COMPILED_PROJECT: RecordSpec = RecordSpec { FieldSpec::new("sql_analysis_dialect", FieldDefault::None), FieldSpec::new( "sql_lexical_syntax", - FieldDefault::Call("sqlbuild._native", "SqlLexicalSyntax"), + FieldDefault::NativeCall(RecordId::SqlLexicalSyntax), ), FieldSpec::new("compile_cache_dir", FieldDefault::None), FieldSpec::new( "settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), FieldSpec::new( "scenario", - FieldDefault::Call("sqlbuild._native", "ScenarioConfig"), + FieldDefault::NativeCall(RecordId::ScenarioConfig), ), FieldSpec::new("models", FieldDefault::EmptyTuple), FieldSpec::new("sources", FieldDefault::EmptyTuple), @@ -1549,7 +1547,7 @@ const R_COMPILED_PROJECT: RecordSpec = RecordSpec { FieldSpec::new("external_sql_reference_resolver", FieldDefault::None), FieldSpec::new( "scope_index", - FieldDefault::Call("sqlbuild._native", "ScopeIndex"), + FieldDefault::NativeCall(RecordId::ScopeIndex), ), FieldSpec { name: "sql_expansions", @@ -1587,7 +1585,7 @@ const R_COMPILED_SQL_TEST: RecordSpec = RecordSpec { ), FieldSpec::new( "payload", - FieldDefault::Call("sqlbuild._native", "CompiledModelSqlTestPayload"), + FieldDefault::NativeCall(RecordId::CompiledModelSqlTestPayload), ), FieldSpec::new("source_path", FieldDefault::None), FieldSpec::new("ownership_root", FieldDefault::None), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs index ec2abe5bcb..c1da8fb1c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs @@ -1,6 +1,5 @@ //! Seeds, functions, sources, SQL tests, scenarios and audits attached from discovered inputs. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyCFunction, PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; @@ -23,22 +22,16 @@ use crate::bindings::_helpers::compiled_project::source_loop::SourceRow; use crate::bindings::_helpers::compiled_project::sql_test_loop::{ TestBlockRow, TestCaseRow, TestFileRow, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_CTE_SCAN_ALGORITHM; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SQL_VALUE_ERRORS_MODULE: &str = "sqlbuild.sql_values.exceptions"; const YAML_FILE_SUFFIXES: [&str; 2] = [".yml", ".yaml"]; -const PYTHON_RESOURCES_MODULE: &str = - "sqlbuild.compiler.compile._helpers.diagnostics.python_resources"; const PATHLIB_MODULE: &str = "pathlib"; const TEST_TIMING_KEY: &str = "test_input_compile_ms"; const DESCRIPTION_KEY: &str = "description"; @@ -73,8 +66,17 @@ fn enum_member<'py>( /// `{member.value: member for member in enum_class}`. fn by_value<'py>(py: Python<'py>, module: &str, class: &str) -> PyResult> { - let members: Bound<'py, PyDict> = PyDict::new(py); - for member in imported(py, module, class)?.try_iter()? { + members_by_value(&imported(py, module, class)?) +} + +/// `{member.value: member for member in enum_class}` of a native string enum. +fn native_by_value(py: Python<'_>, class: RecordId) -> PyResult> { + members_by_value(®istry::class(py, class)?) +} + +fn members_by_value<'py>(enum_class: &Bound<'py, PyAny>) -> PyResult> { + let members: Bound<'py, PyDict> = PyDict::new(enum_class.py()); + for member in enum_class.try_iter()? { let member: Bound<'py, PyAny> = member?; members.set_item(member.getattr("value")?, member)?; } @@ -180,13 +182,13 @@ pub(crate) fn attach_resources<'py>( .into_iter() .map(Bound::unbind) .collect(), - &imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, + ®istry::error(py, ErrorId::CompileInput)?, )?; let defaults: Bound<'py, PyAny> = discovered_inputs .getattr("project_config")? .getattr("defaults")?; let function_kind: Bound<'py, PyAny> = - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "FUNCTION"))?; + registry::member(py, RecordId::ResourceKind, "FUNCTION")?; let functions = NativeCompiledProject::attach_functions( native_project, ( @@ -226,7 +228,7 @@ pub(crate) fn attach_resources<'py>( ( attr(&defaults, "contract")?, validate, - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "SOURCE"))?.unbind(), + registry::member(py, RecordId::ResourceKind, "SOURCE")?.unbind(), ), )?; PyTuple::new( @@ -300,8 +302,8 @@ fn parameter_renderer<'py>(value_renderer: &Bound<'py, PyAny>) -> PyResult = PyTuple::new( py, [ - imported(py, SQL_VALUE_ERRORS_MODULE, "SqlValueRenderingError")?, - imported(py, NATIVE_RECORDS, "SqlValueValidationError")?, + host::entry(py, ExecutorHost::SqlValueRenderingError)?, + registry::native(py, NativeAttr::SqlValueValidationError)?, ], )? .into_any() @@ -336,33 +338,36 @@ fn sql_test_settings<'py>( ) -> PyResult> { let settings: Bound<'py, PyDict> = PyDict::new(py); for (key, class) in [ - ("test_input", "CompileSqlTestInput"), - ("model_payload", "CompileModelSqlTestInputPayload"), - ("direct_payload", "CompileDirectLogicSqlTestInputPayload"), - ("test_cte", "CompileSqlTestCte"), - ("scenario_input", "CompileSqlScenarioInput"), - ("scenario_cte", "CompileSqlScenarioCte"), + ("test_input", RecordId::CompileSqlTestInput), + ("model_payload", RecordId::CompileModelSqlTestInputPayload), + ( + "direct_payload", + RecordId::CompileDirectLogicSqlTestInputPayload, + ), + ("test_cte", RecordId::CompileSqlTestCte), + ("scenario_input", RecordId::CompileSqlScenarioInput), + ("scenario_cte", RecordId::CompileSqlScenarioCte), ] { - settings.set_item(key, shape_class_at(py, COMPILE_MODELS_MODULE, class)?)?; + settings.set_item(key, registry::class(py, class)?)?; } settings.set_item("modes", by_value(py, COMPILE_TYPES_MODULE, "SqlTestMode")?)?; settings.set_item( "test_kind", - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "TEST"))?, + registry::member(py, RecordId::ResourceKind, "TEST")?, )?; settings.set_item( "scenario_kind", - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "SCENARIO"))?, + registry::member(py, RecordId::ResourceKind, "SCENARIO")?, )?; settings.set_item( "runtime_usage", - enum_member(py, (SCOPE_TYPES_MODULE, "UsageKind", "RUNTIME"))?, + registry::member(py, RecordId::UsageKind, "RUNTIME")?, )?; settings.set_item("render_parameter", parameter_renderer(value_renderer)?)?; settings.set_item("adapter_name", value_renderer.getattr("adapter_name")?)?; settings.set_item( "source_location", - imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, )?; settings.set_item("scan_store", scan_cache.call_method0("opened")?)?; settings.set_item( @@ -394,8 +399,7 @@ fn audit_block_row(block: Bound<'_, PyAny>) -> PyResult { fn audit_file_row(file: Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = file.py(); - let generic: Bound<'_, PyAny> = - enum_member(py, (SCOPE_TYPES_MODULE, "DeclarationKind", "AUDIT"))?; + let generic: Bound<'_, PyAny> = registry::member(py, RecordId::DeclarationKind, "AUDIT")?; Ok(( text(&file, "file_path")?, text(&file, "relative_path")?, @@ -419,7 +423,7 @@ fn builtin_audit_file<'py>( let relative_path: Bound<'py, PyAny> = path_class.call1((format!("audits/generic/{name}.sql"),))?; let block: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredAuditBlock")?, + ®istry::class(py, RecordId::DiscoveredAuditBlock)?, &[ ("audit_index", 0.into_bound_py_any(py)?), ("header_values", PyDict::new(py).into_any()), @@ -427,7 +431,7 @@ fn builtin_audit_file<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredAuditFile")?, + ®istry::class(py, RecordId::DiscoveredAuditFile)?, &[ ( "file_path", @@ -468,22 +472,19 @@ fn audit_settings<'py>( settings.set_item("builtins", builtins)?; settings.set_item( "declaration_identity", - imported(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, )?; settings.set_item( "singular_kind", - enum_member( - py, - (SCOPE_TYPES_MODULE, "DeclarationKind", "SINGULAR_AUDIT"), - )?, + registry::member(py, RecordId::DeclarationKind, "SINGULAR_AUDIT")?, )?; settings.set_item( "audit_kind", - enum_member(py, (SCOPE_TYPES_MODULE, "DeclarationKind", "AUDIT"))?, + registry::member(py, RecordId::DeclarationKind, "AUDIT")?, )?; settings.set_item( "resource_kinds", - by_value(py, SCOPE_TYPES_MODULE, "ResourceKind")?, + native_by_value(py, RecordId::ResourceKind)?, )?; Ok(settings) } @@ -518,15 +519,12 @@ fn diagnostic_settings<'py>( let settings: Bound<'py, PyDict> = PyDict::new(py); settings.set_item( "compiler_diagnostic", - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, - )?; - settings.set_item( - "compile_error", - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, + registry::class(py, RecordId::CompilerDiagnostic)?, )?; + settings.set_item("compile_error", registry::error(py, ErrorId::CompileInput)?)?; settings.set_item( "source_location", - imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, )?; settings.set_item("path_class", imported(py, PATHLIB_MODULE, "Path")?)?; settings.set_item("model_files", items(discovered, "model_files")?)?; @@ -577,7 +575,7 @@ fn diagnostic_settings<'py>( settings.set_item( "python_resources", call_with( - &imported(py, PYTHON_RESOURCES_MODULE, "python_resource_rows")?, + &host::entry(py, UserCodeHost::PythonResourceRows)?, &[ ("discovered_inputs", discovered.clone()), ("source_inputs", source_inputs.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs index e626aedc67..e784fe5ac3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs @@ -1,13 +1,13 @@ //! Map offsets in expanded SQL back onto the authored text that produced them. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::compiled_project::main::map_expanded_offset::map_expanded_offset as mapped; use sqlbuild_analysis::compiled_project::models::SplicedSpan; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - /// `map_expanded_offset(*, offset, passes)`: a `MappedOffset` in the first pass's source. #[pyfunction] #[pyo3(signature = (*, offset, passes))] @@ -34,9 +34,7 @@ fn map_expanded_offset<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("offset", offset)?; arguments.set_item("generated", generated)?; - py.import(NATIVE_RECORDS_MODULE)? - .getattr("MappedOffset")? - .call((), Some(&arguments)) + registry::class(py, RecordId::MappedOffset)?.call((), Some(&arguments)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs index 083f4d9d8d..b22859dafd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs @@ -5,23 +5,12 @@ use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyList, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The materializations a model's storage policies are inherited for, besides none. const TABLE_BACKED_MATERIALIZATIONS: [&str; 3] = ["TABLE", "INCREMENTAL", "SNAPSHOT"]; /// The declaration kinds whose project-owned records the loop names. @@ -31,14 +20,6 @@ fn class<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult( - py: Python<'py>, - module: &str, - (enum_class, name): (&str, &str), -) -> PyResult> { - imported(py, module, enum_class)?.getattr(name) -} - fn dict<'py>( py: Python<'py>, entries: Vec<(&str, Bound<'py, PyAny>)>, @@ -54,7 +35,7 @@ fn dict<'py>( pub(crate) fn table_backed<'py>(py: Python<'py>) -> PyResult>> { TABLE_BACKED_MATERIALIZATIONS .iter() - .map(|name| member(py, PLANNER_TYPES_MODULE, ("MaterializationType", name))) + .map(|name| registry::member(py, RecordId::MaterializationType, name)) .collect() } @@ -76,28 +57,24 @@ pub(crate) fn native_config_classes<'py>( ("table_backed", PyTuple::new(py, table_backed)?.into_any()), ( "compile_model_config", - class(py, NATIVE_RECORDS_MODULE, "CompileModelConfig")?, + registry::class(py, RecordId::CompileModelConfig)?, ), ( "resolved_retention", - class(py, CONTRACT_MODELS_MODULE, "ResolvedTimeTravelRetention")?, + registry::class(py, RecordId::ResolvedTimeTravelRetention)?, ), ( "retention_source", - member( - py, - CONTRACT_TYPES_MODULE, - ("TimeTravelRetentionSource", "MODEL"), - )?, + registry::member(py, RecordId::TimeTravelRetentionSource, "MODEL")?, ), ( "resolved_table_type", - class(py, CONTRACT_MODELS_MODULE, "ResolvedTableType")?, + registry::class(py, RecordId::ResolvedTableType)?, ), - ("table_type", class(py, CONTRACT_TYPES_MODULE, "TableType")?), + ("table_type", registry::class(py, RecordId::TableType)?), ( "table_type_source", - member(py, CONTRACT_TYPES_MODULE, ("TableTypeSource", "MODEL"))?, + registry::member(py, RecordId::TableTypeSource, "MODEL")?, ), ("record_template_reads", record_template_reads.clone()), ], @@ -113,7 +90,7 @@ fn named_records<'py>(resolver: &Bound<'py, PyAny>) -> PyResult> = NAMED_KINDS .iter() - .map(|name| member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", name))) + .map(|name| registry::member(py, RecordId::DeclarationKind, name)) .collect::>()?; let declarations: Bound<'py, PyAny> = resolver.getattr("lookup")?.getattr("declarations")?; for pair in declarations.call_method0("items")?.try_iter()? { @@ -139,8 +116,8 @@ fn schema_enums<'py>(resolver: &Bound<'py, PyAny>) -> PyResult = member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", "ENUM"))?; - let enum_class: Bound<'py, PyAny> = class(py, DISCOVERY_MODELS_MODULE, "EnumDeclaration")?; + let enum_kind: Bound<'py, PyAny> = registry::member(py, RecordId::DeclarationKind, "ENUM")?; + let enum_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumDeclaration)?; let declarations: Bound<'py, PyAny> = resolver.getattr("projection")?.getattr("declarations")?; for pair in declarations.call_method0("items")?.try_iter()? { @@ -193,7 +170,7 @@ fn visibility_classes<'py>( } else { let lookup: Bound<'py, PyAny> = resolver.getattr("lookup")?; let declarations: Bound<'py, PyAny> = lookup.getattr("index")?.getattr("declarations")?; - let private: Bound<'py, PyAny> = member(py, SCOPE_TYPES_MODULE, ("ScopeKind", "PRIVATE"))?; + let private: Bound<'py, PyAny> = registry::member(py, RecordId::ScopeKind, "PRIVATE")?; let mut owners: Vec> = Vec::new(); for declaration in declarations.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; @@ -218,35 +195,31 @@ fn visibility_classes<'py>( ("visible_declarations", visible_declarations.clone()), ( "resource_identity", - class(py, SCOPE_MODELS_MODULE, "ResourceIdentity")?, + registry::class(py, RecordId::ResourceIdentity)?, ), ( "declaration_identity", - class(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, ), ( "model_kind", - member(py, SCOPE_TYPES_MODULE, ("ResourceKind", "MODEL"))?, + registry::member(py, RecordId::ResourceKind, "MODEL")?, ), ( "enum_kind", - member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", "ENUM"))?, + registry::member(py, RecordId::DeclarationKind, "ENUM")?, ), ( "constant_kind", - member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", "CONSTANT"))?, + registry::member(py, RecordId::DeclarationKind, "CONSTANT")?, ), ( "private_owner", - member( - py, - SCOPE_TYPES_MODULE, - ("VisibilityReason", "PRIVATE_OWNER"), - )?, + registry::member(py, RecordId::VisibilityReason, "PRIVATE_OWNER")?, ), ( "visibility_record", - class(py, SCOPE_MODELS_MODULE, "VisibilityRecord")?, + registry::class(py, RecordId::VisibilityRecord)?, ), ], ) @@ -256,15 +229,15 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { let compiled: Vec<(&str, Bound<'py, PyAny>)> = vec![ ( "object_key", - class(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?, + registry::class(py, RecordId::CompiledObjectKey)?, ), ( "relation_location", - class(py, COMPILE_MODELS_MODULE, "CompiledRelationLocation")?, + registry::class(py, RecordId::CompiledRelationLocation)?, ), ( "inferred_column", - class(py, COMPILE_MODELS_MODULE, "InferredColumn")?, + registry::class(py, RecordId::InferredColumn)?, ), ( "nullability", @@ -272,31 +245,31 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { ), ( "compiler_diagnostic", - class(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + registry::class(py, RecordId::CompilerDiagnostic)?, ), ( "source_location", - class(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, ), ( "compact_lineage_facts", - class(py, COMPILE_MODELS_MODULE, "CompactLineageFacts")?, + registry::class(py, RecordId::CompactLineageFacts)?, ), ( "lineage_column_fact", - class(py, NATIVE_RECORDS_MODULE, "CompiledLineageColumnFact")?, + registry::class(py, RecordId::CompiledLineageColumnFact)?, ), ( "lineage_source_fact", - class(py, COMPILE_MODELS_MODULE, "CompiledLineageSourceFact")?, + registry::class(py, RecordId::CompiledLineageSourceFact)?, ), ( "contract_proof", - class(py, NATIVE_RECORDS_MODULE, "DynamicColumnContractProof")?, + registry::class(py, RecordId::DynamicColumnContractProof)?, ), ( "family_proof", - class(py, NATIVE_RECORDS_MODULE, "DynamicColumnFamilyProof")?, + registry::class(py, RecordId::DynamicColumnFamilyProof)?, ), ]; dict( @@ -304,23 +277,23 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { vec![ ( "sql_reference", - class(py, COMPILE_MODELS_MODULE, "CompileSqlReference")?, + registry::class(py, RecordId::CompileSqlReference)?, ), ( "reference_kind", - class(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?, + registry::class(py, RecordId::SqlReferenceKind)?, ), ( "schema_model_entry", - class(py, CONTRACT_MODELS_MODULE, "SchemaModelEntry")?, + registry::class(py, RecordId::SchemaModelEntry)?, ), ( "sql_expansion", - class(py, NATIVE_RECORDS_MODULE, "CompiledSqlExpansion")?, + registry::class(py, RecordId::CompiledSqlExpansion)?, ), ( "expansion_span", - class(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?, + registry::class(py, RecordId::ExpansionSpan)?, ), ( "audits", @@ -346,11 +319,11 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { vec![ ( "function_argument", - class(py, NATIVE_RECORDS_MODULE, "FunctionArgument")?, + registry::class(py, RecordId::FunctionArgument)?, ), ( "function_return_column", - class(py, NATIVE_RECORDS_MODULE, "FunctionReturnColumn")?, + registry::class(py, RecordId::FunctionReturnColumn)?, ), ( "function_language", @@ -403,7 +376,7 @@ pub(crate) fn model_loop_classes<'py>( ("provider_names", provider_names.into_bound_py_any(py)?), ( "python_hook_entry", - class(py, DISCOVERY_MODELS_MODULE, "PythonHookEntry")?, + registry::class(py, RecordId::PythonHookEntry)?, ), ( "signature", @@ -417,32 +390,26 @@ pub(crate) fn model_loop_classes<'py>( ("replace", imported(py, "dataclasses", "replace")?), ( "declaration_rows", - imported(py, NATIVE_MODULE, "declaration_rows")?, + registry::native(py, NativeAttr::DeclarationRows)?, ), ], )?; dict( py, vec![ - ( - "usage_record", - class(py, SCOPE_MODELS_MODULE, "UsageRecord")?, - ), - ( - "compile_error", - class(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, - ), + ("usage_record", registry::class(py, RecordId::UsageRecord)?), + ("compile_error", registry::error(py, ErrorId::CompileInput)?), ("syntax_help", host("syntax_help")?), ("source_syntax_help", host("source_syntax_help")?), ("macros", host("macros")?), ("sql_hooks", host("sql_hooks")?), ( "scope_path_error", - class(py, SCOPE_ERRORS_MODULE, "InvalidScopePathError")?, + registry::error(py, ErrorId::InvalidScopePath)?, ), ( "sql_hook_entry", - class(py, DISCOVERY_MODELS_MODULE, "SqlHookEntry")?, + registry::class(py, RecordId::SqlHookEntry)?, ), ( "audit_factories", @@ -450,25 +417,25 @@ pub(crate) fn model_loop_classes<'py>( ), ( "generated_usage", - member(py, SCOPE_TYPES_MODULE, ("UsageKind", "GENERATED"))?, + registry::member(py, RecordId::UsageKind, "GENERATED")?, ), ( "contract_enforced", - member(py, PLANNER_TYPES_MODULE, ("ContractPolicy", "ENFORCED"))?, + registry::member(py, RecordId::ContractPolicy, "ENFORCED")?, ), ("model_schemas", context.getattr("public_model_schemas")?), ("named_records", named_records(&resolver)?.into_any()), ("schema_enums", schema_enums(&resolver)?.into_any()), ( "compiler_diagnostic", - class(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + registry::class(py, RecordId::CompilerDiagnostic)?, ), ("report_diagnostic", host("report_diagnostic")?), ("path_class", imported(py, "pathlib", "Path")?), ("external_resolver", external_resolver.clone()), ( "reference_kind", - class(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?, + registry::class(py, RecordId::SqlReferenceKind)?, ), ("partial", imported(py, "functools", "partial")?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs index 846deceaae..79c7d56990 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs @@ -1,6 +1,5 @@ //! Compiled records and views: projecting native views as records, and recording later stages. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRef, PyResult, Python, }; @@ -13,10 +12,9 @@ use crate::bindings::_helpers::compiled_project::inputs_view::NativeInputsView; use crate::bindings::_helpers::compiled_project::model_input_view::NativeModelInput; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::compiled_project::project_view::NativeProjectView; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const DATACLASSES_MODULE: &str = "dataclasses"; const DECIMAL_KIND: &str = "decimal"; @@ -37,9 +35,9 @@ pub(crate) fn replace<'py>( } /// `value` when it already is a `class` record, else `class(**{field: getattr(value, field)})`. -fn record<'py>(value: &Bound<'py, PyAny>, class: &str) -> PyResult> { +fn record<'py>(value: &Bound<'py, PyAny>, class: RecordId) -> PyResult> { let py: Python<'py> = value.py(); - let class: Bound<'py, PyAny> = shape_class_at(py, COMPILE_MODELS_MODULE, class)?; + let class: Bound<'py, PyAny> = registry::class(py, class)?; if value.is_instance(&class)? { return Ok(value.clone()); } @@ -60,13 +58,13 @@ fn record<'py>(value: &Bound<'py, PyAny>, class: &str) -> PyResult(model: &Bound<'py, PyAny>) -> PyResult> { - record(model, "CompiledModel") + record(model, RecordId::CompiledModel) } /// The audit as a `CompiledAudit`, projected from its owner when it is a native view. #[pyfunction] pub(crate) fn compiled_audit_record<'py>(audit: &Bound<'py, PyAny>) -> PyResult> { - record(audit, "CompiledAudit") + record(audit, RecordId::CompiledAudit) } /// The function as a `CompiledFunction`, projected from its owner when it is a native view. @@ -74,7 +72,7 @@ pub(crate) fn compiled_audit_record<'py>(audit: &Bound<'py, PyAny>) -> PyResult< pub(crate) fn compiled_function_record<'py>( function: &Bound<'py, PyAny>, ) -> PyResult> { - record(function, "CompiledFunction") + record(function, RecordId::CompiledFunction) } /// The source as a `CompiledSource`, projected from its owner when it is a native view. @@ -82,17 +80,17 @@ pub(crate) fn compiled_function_record<'py>( pub(crate) fn compiled_source_record<'py>( source: &Bound<'py, PyAny>, ) -> PyResult> { - record(source, "CompiledSource") + record(source, RecordId::CompiledSource) } /// The seed as a `CompiledSeed`, projected from its owner when it is a native view. #[pyfunction] pub(crate) fn compiled_seed_record<'py>(seed: &Bound<'py, PyAny>) -> PyResult> { - record(seed, "CompiledSeed") + record(seed, RecordId::CompiledSeed) } /// Each item of `fields[name]` as a `class` record. -fn records_of<'py>(fields: &Bound<'py, PyDict>, (name, class): (&str, &str)) -> PyResult<()> { +fn records_of<'py>(fields: &Bound<'py, PyDict>, (name, class): (&str, RecordId)) -> PyResult<()> { let py: Python<'py> = fields.py(); let items: Vec> = fields .as_any() @@ -111,7 +109,7 @@ pub(crate) fn compiled_project_record<'py>( include_sql_expansions: bool, ) -> PyResult> { let py: Python<'py> = project.py(); - let class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledProject")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledProject)?; if project.is_instance(&class)? { if include_sql_expansions { return Ok(project.clone()); @@ -122,10 +120,10 @@ pub(crate) fn compiled_project_record<'py>( let fields: Bound<'py, PyDict> = view.fields(py, include_sql_expansions)?; drop(view); for pair in [ - ("seeds", "CompiledSeed"), - ("sources", "CompiledSource"), - ("functions", "CompiledFunction"), - ("audits", "CompiledAudit"), + ("seeds", RecordId::CompiledSeed), + ("sources", RecordId::CompiledSource), + ("functions", RecordId::CompiledFunction), + ("audits", RecordId::CompiledAudit), ] { records_of(&fields, pair)?; } @@ -140,7 +138,7 @@ pub(crate) fn with_binding_diagnostics<'py>( diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = model.py(); - if model.is_instance(&imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?)? { + if model.is_instance(®istry::class(py, RecordId::CompiledModel)?)? { return replace(model, &[("binding_diagnostics", diagnostics.clone())]); } model.call_method1("with_binding_diagnostics", (diagnostics,)) @@ -163,7 +161,7 @@ pub(crate) fn with_type_recovery<'py>( unchecked.push(column); } } - if !model.is_instance(&imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?)? { + if !model.is_instance(®istry::class(py, RecordId::CompiledModel)?)? { return model.call_method1("with_type_recovery", (diagnostics, unchecked)); } let inferred: Bound<'py, PyAny> = model.getattr("inferred_columns")?; @@ -202,7 +200,7 @@ pub(crate) fn project_with<'py>( project: &Bound<'py, PyAny>, changes: Option>, ) -> PyResult> { - with_changes(project, "CompiledProject", changes) + with_changes(project, RecordId::CompiledProject, changes) } /// The compile inputs with `changes`; a native view shares its owner's model inputs. @@ -212,18 +210,18 @@ pub(crate) fn inputs_with<'py>( inputs: &Bound<'py, PyAny>, changes: Option>, ) -> PyResult> { - with_changes(inputs, "CompileProjectInputs", changes) + with_changes(inputs, RecordId::CompileProjectInputs, changes) } /// `replace(value, **changes)` for a record, else the native view's `with_changes(**changes)`. fn with_changes<'py>( value: &Bound<'py, PyAny>, - class: &str, + class: RecordId, changes: Option>, ) -> PyResult> { let py: Python<'py> = value.py(); let changes: Bound<'py, PyDict> = changes.unwrap_or_else(|| PyDict::new(py)); - if value.is_instance(&shape_class_at(py, COMPILE_MODELS_MODULE, class)?)? { + if value.is_instance(®istry::class(py, class)?)? { return imported(py, DATACLASSES_MODULE, "replace")?.call((value,), Some(&changes)); } value.call_method("with_changes", (), Some(&changes)) @@ -253,7 +251,7 @@ pub(crate) fn compile_inputs_view<'py>( } let keywords: Bound<'py, PyDict> = fields.copy()?; keywords.set_item("native_project", native_project)?; - imported(py, NATIVE_RECORDS_MODULE, "CompileProjectInputs")?.call((), Some(&keywords)) + registry::class(py, RecordId::CompileProjectInputs)?.call((), Some(&keywords)) } /// A typed SQL value as the plain value native consumers read. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs index b34dea553e..63489d0281 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs @@ -1,5 +1,7 @@ //! Layouts of the compiler, lint and config value records hosted natively. +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_NATIVE_MODEL_ANALYSIS_REQUEST: RecordSpec = RecordSpec { @@ -132,7 +134,7 @@ const R_HOOK_READ_EDGE: RecordSpec = RecordSpec { FieldSpec::required("read"), FieldSpec::new( "hook_type", - FieldDefault::Member("sqlbuild._native", "HookReadType", "PYTHON"), + FieldDefault::NativeMember(RecordId::HookReadType, "PYTHON"), ), FieldSpec::new("hook_path", FieldDefault::None), ], @@ -163,11 +165,11 @@ const R_COLUMN_LINEAGE_EDGE: RecordSpec = RecordSpec { FieldSpec::required("target"), FieldSpec::new( "transform_kind", - FieldDefault::Member("sqlbuild._native", "ColumnTransformKind", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnTransformKind, "UNKNOWN"), ), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), ], }; @@ -181,7 +183,7 @@ const R_DIRECT_SEMANTIC_COLUMN_USE: RecordSpec = RecordSpec { FieldSpec::required("source"), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), FieldSpec::new("line", FieldDefault::None), FieldSpec::new("column", FieldDefault::None), @@ -205,7 +207,7 @@ const R_COLUMN_LINEAGE: RecordSpec = RecordSpec { ), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), ], }; @@ -491,7 +493,7 @@ const R_COMPILE_PIPELINE_OPTIONS: RecordSpec = RecordSpec { FieldSpec::new("accepts_unit_test_selectors", FieldDefault::False), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), ], }; @@ -504,7 +506,7 @@ const R_PYTHON_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::required("phase"), FieldSpec::new( "identity_status", - FieldDefault::Member("sqlbuild._native", "PythonIdentityStatus", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::PythonIdentityStatus, "UNKNOWN"), ), FieldSpec::new("current_definition_json", FieldDefault::None), FieldSpec::new("previous_definition_json", FieldDefault::None), @@ -586,7 +588,7 @@ const R_DISCOVERED_PYTHON_LOADER_METADATA: RecordSpec = RecordSpec { FieldSpec::new("contract", FieldDefault::None), FieldSpec::new( "connection_mode", - FieldDefault::Member("sqlbuild._native", "LoaderConnectionMode", "SQLBUILD"), + FieldDefault::NativeMember(RecordId::LoaderConnectionMode, "SQLBUILD"), ), ], }; @@ -609,11 +611,7 @@ const R_DISCOVERED_PYTHON_CHECK_METADATA: RecordSpec = RecordSpec { doc: "Check-specific metadata carried by an internal Python check node.", fields: &[FieldSpec::new( "severity", - FieldDefault::Member( - "sqlbuild.python_nodes.types", - "PythonCheckSeverity", - "ERROR", - ), + FieldDefault::HostMember(UserCodeHost::PythonCheckSeverity.id(), "ERROR"), )], }; const R_PYTHON_NODE_DEPENDENCY_EDGE: RecordSpec = RecordSpec { @@ -727,7 +725,7 @@ const R_DBT_INIT_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("skip_dbt_debug", FieldDefault::False), FieldSpec::new( "progress_callbacks", - FieldDefault::Call("sqlbuild._native", "DbtInitProgressCallbacks"), + FieldDefault::NativeCall(RecordId::DbtInitProgressCallbacks), ), ], }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs index 16fd0baad8..61d449b063 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs @@ -1,2 +1 @@ pub(crate) mod constants; -pub(crate) mod shape_classes; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs deleted file mode 100644 index a9e9ea9d42..0000000000 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Resolve a shape class by its Python module and name: the native class once it is hosted natively. - -use pyo3::exceptions::PyLookupError; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; - -use sqlbuild_cache::hosted_shapes::main::hosted_native_name::hosted_native_name; - -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; - -/// The native class name hosting `module:class`, failing loudly when the name is ambiguous. -fn hosted_name(module: &str, class: &str) -> PyResult> { - hosted_native_name(module, class).map_err(|_| { - PyLookupError::new_err(format!( - "{module}:{class} names more than one natively hosted shape" - )) - }) -} - -/// `class` of the Python module object `module`, or the native class hosting it. -pub(crate) fn shape_class<'py, T>( - module: &Bound<'py, T>, - class: &str, -) -> PyResult> { - let module_path: String = module.as_any().getattr("__name__")?.extract()?; - if let Some(native) = hosted_name(&module_path, class)? { - return imported(module.py(), NATIVE_MODULE, native); - } - module.as_any().getattr(class) -} - -/// `class` of the module at `module_path`, or the native class hosting it. -pub(crate) fn shape_class_at<'py>( - py: Python<'py>, - module_path: &str, - class: &str, -) -> PyResult> { - if let Some(native) = hosted_name(module_path, class)? { - return imported(py, NATIVE_MODULE, native); - } - imported(py, module_path, class) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs index 5b18e88d17..03e5e15825 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs @@ -10,33 +10,30 @@ use sqlbuild_cli::compute_logs::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.compute_logs.exceptions"; -pub(crate) const MODELS_MODULE: &str = "sqlbuild.runtime.compute_logs.models"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild.runtime.compute_logs.types"; const EPOCH_TEXT: &str = "1970-01-01T00:00:00+00:00"; const UTC_OFFSET: &str = "+00:00"; const UTC_SUFFIX: &str = "Z"; const STREAM_VALUES: [&str; 3] = ["stdout", "stderr", "diagnostics"]; -fn exception_name(kind: ComputeLogErrorKind) -> &'static str { +fn exception_class(kind: ComputeLogErrorKind) -> ExecutorHost { match kind { - ComputeLogErrorKind::InvalidCaptureId => "InvalidCaptureIdError", - ComputeLogErrorKind::InvalidCursor => "InvalidComputeLogCursorError", - ComputeLogErrorKind::InvalidLimit => "InvalidComputeLogLimitError", - ComputeLogErrorKind::AlreadyExists => "CaptureAlreadyExistsError", - ComputeLogErrorKind::NotFound => "CaptureNotFoundError", - ComputeLogErrorKind::State => "CaptureStateError", - ComputeLogErrorKind::Path => "ComputeLogPathError", - ComputeLogErrorKind::Metadata => "ComputeLogMetadataError", + ComputeLogErrorKind::InvalidCaptureId => ExecutorHost::InvalidCaptureIdError, + ComputeLogErrorKind::InvalidCursor => ExecutorHost::InvalidComputeLogCursorError, + ComputeLogErrorKind::InvalidLimit => ExecutorHost::InvalidComputeLogLimitError, + ComputeLogErrorKind::AlreadyExists => ExecutorHost::CaptureAlreadyExistsError, + ComputeLogErrorKind::NotFound => ExecutorHost::CaptureNotFoundError, + ComputeLogErrorKind::State => ExecutorHost::CaptureStateError, + ComputeLogErrorKind::Path => ExecutorHost::ComputeLogPathError, + ComputeLogErrorKind::Metadata => ExecutorHost::ComputeLogMetadataError, } } /// The Python exception for a storage rejection of `kind`. pub(crate) fn rejection(py: Python<'_>, kind: ComputeLogErrorKind, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, exception_name(kind)) - .and_then(|class| class.call1((message,))) - { + match host::entry(py, exception_class(kind)).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -70,7 +67,7 @@ pub(crate) fn capture_id(value: &Bound<'_, PyAny>) -> PyResult { /// The stream index of a `ComputeLogStream` member: stdout, stderr or diagnostics. pub(crate) fn stream_index(stream: &Bound<'_, PyAny>) -> PyResult { let py = stream.py(); - if stream.is_instance(&imported(py, TYPES_MODULE, "ComputeLogStream")?)? { + if stream.is_instance(&host::entry(py, ExecutorHost::ComputeLogStream)?)? { let value: String = stream.getattr("value")?.extract()?; if let Some(index) = STREAM_VALUES.iter().position(|name| *name == value) { return Ok(index); @@ -129,7 +126,7 @@ pub(crate) fn document(metadata: &Bound<'_, PyAny>) -> PyResult i64::from(version.eq(1)?) }; let completion = - if metadata.is_instance(&imported(py, MODELS_MODULE, "FinalCaptureMetadata")?)? { + if metadata.is_instance(&host::entry(py, ExecutorHost::FinalCaptureMetadata)?)? { Some(CaptureCompletion { completed_at: instant(&metadata.getattr("completed_at")?)?, exit_code: count(py, &metadata.getattr("exit_code")?)?, @@ -197,7 +194,7 @@ pub(crate) fn metadata_model<'py>( ("run_id", document.run_id.as_deref().into_bound_py_any(py)?), ]; let Some(completion) = &document.completion else { - return call_with(&imported(py, MODELS_MODULE, "CaptureMetadata")?, &fields); + return call_with(&host::entry(py, ExecutorHost::CaptureMetadata)?, &fields); }; fields.extend([ ( @@ -219,7 +216,7 @@ pub(crate) fn metadata_model<'py>( ), ]); call_with( - &imported(py, MODELS_MODULE, "FinalCaptureMetadata")?, + &host::entry(py, ExecutorHost::FinalCaptureMetadata)?, &fields, ) } @@ -230,7 +227,7 @@ pub(crate) fn inventory_item<'py>( entry: &InventoryEntry, ) -> PyResult> { call_with( - &imported(py, MODELS_MODULE, "CaptureInventoryItem")?, + &host::entry(py, ExecutorHost::CaptureInventoryItem)?, &[ ( "invocation_id", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs index a722957f46..407cda8b7e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs @@ -32,12 +32,12 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::compute_logs::conversion::{ - MODELS_MODULE, capture_id, document, inventory_item, is_exact_int, metadata_model, rejection, - storage_error, stream_index, + capture_id, document, inventory_item, is_exact_int, metadata_model, rejection, storage_error, + stream_index, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const DIAGNOSTIC_MODEL_MODULE: &str = "sqlbuild.runtime.observability.models"; -const DIAGNOSTIC_JSON_MODULE: &str = "sqlbuild.runtime.observability.main.diagnostic_log_to_json"; const DIAGNOSTICS_STREAM: usize = 2; /// Open stream files of the captures this instance started, and whether it is closed. @@ -221,7 +221,7 @@ impl LocalFilesystemComputeLogStorage { invocation_id: &Bound<'_, PyAny>, record: &Bound<'_, PyAny>, ) -> PyResult<()> { - if !record.is_instance(&imported(py, DIAGNOSTIC_MODEL_MODULE, "DiagnosticLog")?)? { + if !record.is_instance(&host::entry(py, ExecutorHost::DiagnosticLog)?)? { return Err(rejection( py, ComputeLogErrorKind::State, @@ -229,7 +229,7 @@ impl LocalFilesystemComputeLogStorage { )); } let encoded: Bound<'_, PyAny> = - imported(py, DIAGNOSTIC_JSON_MODULE, "diagnostic_log_to_json")?.call1((record,))?; + host::entry(py, ExecutorHost::DiagnosticLogToJson)?.call1((record,))?; let mut line: Vec = encoded .downcast::()? .to_str()? @@ -276,7 +276,7 @@ impl LocalFilesystemComputeLogStorage { ), )?; call_with( - &imported(py, MODELS_MODULE, "ComputeLogReadChunk")?, + &host::entry(py, ExecutorHost::ComputeLogReadChunk)?, &[ ("data", PyBytes::new(py, &chunk.data).into_any()), ("next_cursor", chunk.next_cursor.into_bound_py_any(py)?), @@ -319,7 +319,7 @@ impl LocalFilesystemComputeLogStorage { let [stdout_bytes, stderr_bytes, diagnostics_bytes] = self.byte_counts(py, invocation_id)?; call_with( - &imported(py, MODELS_MODULE, "CaptureByteCounts")?, + &host::entry(py, ExecutorHost::CaptureByteCounts)?, &[ ("stdout_bytes", stdout_bytes.into_bound_py_any(py)?), ("stderr_bytes", stderr_bytes.into_bound_py_any(py)?), @@ -396,7 +396,7 @@ impl LocalFilesystemComputeLogStorage { py.detach(|| prune_captures(&root, retain, &|id: &str| active.contains(id))), )?; call_with( - &imported(py, MODELS_MODULE, "PruneResult")?, + &host::entry(py, ExecutorHost::PruneResult)?, &[ ( "deleted_invocation_ids", @@ -423,7 +423,7 @@ impl LocalFilesystemComputeLogStorage { } let complete_count = entries.iter().filter(|entry| entry.is_complete).count(); call_with( - &imported(py, MODELS_MODULE, "CaptureInventory")?, + &host::entry(py, ExecutorHost::CaptureInventory)?, &[ ("captures", PyTuple::new(py, items)?.into_any()), ("complete_count", complete_count.into_bound_py_any(py)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs index 606b246d5d..c32675ba79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs @@ -23,14 +23,9 @@ use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_conn use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::contract_command::write::{ContractWrite, write_contracts}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; -const ADOPTION_MODELS: &str = "sqlbuild._native"; const GENERATE_ACTION: &str = "generate"; /// A `CliUserError` with its code. @@ -38,7 +33,7 @@ fn user_error(py: Python<'_>, message: &str, code: &str) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -81,11 +76,11 @@ fn select_resources<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let analysis: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", PyBool::new(py, true).to_owned().into_any())], )?; let project: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", context.getattr("adapter")?), @@ -96,15 +91,11 @@ fn select_resources<'py>( ], )?; let graph: Bound<'py, PyAny> = call_with( - &imported( - py, - NATIVE_MODULE, - "build_project_graph_from_compiled_project", - )?, + ®istry::native(py, NativeAttr::BuildProjectGraphFromCompiledProject)?, &[("project", project)], )?; let selected: Bound<'py, PyAny> = call_with( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", request.getattr("select")?), @@ -169,7 +160,7 @@ fn compare<'py>( let py: Python<'py> = request.py(); let adapter: Bound<'py, PyAny> = context.getattr("adapter")?; let reporter: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.getattr("adapter_name")?), ("stream", progress.stream.clone()), @@ -207,7 +198,7 @@ fn compare<'py>( ),), )?; let evidence: Bound<'py, PyAny> = match call_with( - &imported(py, NATIVE_MODULE, "compare_contracts")?, + ®istry::native(py, NativeAttr::CompareContracts)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -371,11 +362,11 @@ fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< imported(py, "pathlib", "Path")?.call_method0("cwd")? }; let discovered: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -404,7 +395,7 @@ fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< let progress: Progress<'_> = progress_for(request)?; let evidence: Bound<'_, PyAny> = compare(request, &context, &selection, &progress)?; let mut result: Bound<'_, PyAny> = call_with( - &imported(py, ADOPTION_MODELS, "ContractAdoptionResult")?, + ®istry::class(py, RecordId::ContractAdoptionResult)?, &[("from_target", from_target), ("evidence", evidence)], )?; if generate_write { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs index bc88c90805..f563b0dd6a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs @@ -5,10 +5,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const UTF8: &str = "utf-8"; @@ -28,7 +27,6 @@ pub(crate) fn write_contracts<'py>( result: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = graph.py(); - let native = py.import(NATIVE_MODULE)?; let project: Bound<'py, PyAny> = graph.getattr("project")?; let models: Bound<'py, PyDict> = by_name(&project.getattr("models")?)?; let sources: Bound<'py, PyDict> = by_name(&project.getattr("sources")?)?; @@ -49,7 +47,7 @@ pub(crate) fn write_contracts<'py>( if evidence.getattr("resource_type")?.eq(&model_type)? { let model: Bound<'py, PyAny> = models.as_any().get_item(&name)?; let edited: Bound<'py, PyAny> = call_with( - &native.getattr("edit_contract_model")?, + ®istry::native(py, NativeAttr::EditContractModel)?, &[ ("model", model.clone()), ("physical_columns", physical_columns), @@ -65,7 +63,7 @@ pub(crate) fn write_contracts<'py>( let path: Bound<'py, PyAny> = source.getattr("source_file")?.getattr("file_path")?; let edited: Bound<'py, PyAny> = call_with( - &native.getattr("edit_contract_source")?, + ®istry::native(py, NativeAttr::EditContractSource)?, &[ ("source", source), ("physical_columns", physical_columns), @@ -80,7 +78,7 @@ pub(crate) fn write_contracts<'py>( updates.set_item(&path, contents)?; } let remaining: Bound<'py, PyAny> = call_with( - &native.getattr("remaining_contract_findings")?, + ®istry::native(py, NativeAttr::RemainingContractFindings)?, &[ ("findings", findings), ("overwrite", write.overwrite.clone()), @@ -128,10 +126,9 @@ fn applied<'py>( updates: &Bound<'py, PyDict>, ) -> PyResult<()> { let py: Python<'py> = result.py(); - let native = py.import(NATIVE_MODULE)?; write_all(updates)?; let discovered: Bound<'py, PyAny> = call_with( - &native.getattr("discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", write.project_dir.clone())], )?; if write.adapter.is_none() { @@ -140,10 +137,9 @@ fn applied<'py>( let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("no_cache", true)?; let selection: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")? - .call((), Some(&keywords))?; + registry::class(py, RecordId::CompileAnalysisSelection)?.call((), Some(&keywords))?; call_with( - &native.getattr("build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered), ("adapter", write.adapter.clone()), @@ -159,7 +155,7 @@ fn applied<'py>( /// Write each `path: contents` pair atomically, in order. fn write_all(contents_by_path: &Bound<'_, PyDict>) -> PyResult<()> { let py: Python<'_> = contents_by_path.py(); - let write_atomically: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "write_atomically")?; + let write_atomically: Bound<'_, PyAny> = registry::native(py, NativeAttr::WriteAtomically)?; for (path, contents) in contents_by_path.iter() { call_with(&write_atomically, &[("path", path), ("contents", contents)])?; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs index 4b9a90b52a..59205d3677 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs @@ -1,5 +1,6 @@ //! Layouts of the project configuration and contract value records hosted natively. +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_CLONE_POLICY: RecordSpec = RecordSpec { @@ -40,15 +41,15 @@ const R_MATERIALIZATION_DEFAULTS_CONFIG: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "table", - FieldDefault::Call("sqlbuild._native", "MaterializationRetentionDefaults"), + FieldDefault::NativeCall(RecordId::MaterializationRetentionDefaults), ), FieldSpec::new( "incremental", - FieldDefault::Call("sqlbuild._native", "MaterializationRetentionDefaults"), + FieldDefault::NativeCall(RecordId::MaterializationRetentionDefaults), ), FieldSpec::new( "snapshot", - FieldDefault::Call("sqlbuild._native", "MaterializationRetentionDefaults"), + FieldDefault::NativeCall(RecordId::MaterializationRetentionDefaults), ), ], }; @@ -67,11 +68,11 @@ const R_RESOLVED_TABLE_TYPE: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "value", - FieldDefault::Member("sqlbuild._native", "TableType", "TRANSIENT"), + FieldDefault::NativeMember(RecordId::TableType, "TRANSIENT"), ), FieldSpec::new( "source", - FieldDefault::Member("sqlbuild._native", "TableTypeSource", "DEFAULT"), + FieldDefault::NativeMember(RecordId::TableTypeSource, "DEFAULT"), ), FieldSpec::new("declared", FieldDefault::False), ], @@ -111,10 +112,7 @@ const R_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("loader_schema", FieldDefault::None), FieldSpec::new("defer_sources_to", FieldDefault::None), FieldSpec::new("defer_clone_from", FieldDefault::None), - FieldSpec::new( - "clone", - FieldDefault::Call("sqlbuild._native", "ClonePolicy"), - ), + FieldSpec::new("clone", FieldDefault::NativeCall(RecordId::ClonePolicy)), FieldSpec::new("compile_cache", FieldDefault::None), FieldSpec::new("time_travel_retention", FieldDefault::None), FieldSpec::new( @@ -125,31 +123,24 @@ const R_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("default_table_type", FieldDefault::None), FieldSpec::new( "table_type_downgrade", - FieldDefault::Member( - "sqlbuild._native", - "TableTypeDowngradePolicy", - "REQUIRE_CONFIRMATION", - ), + FieldDefault::NativeMember(RecordId::TableTypeDowngradePolicy, "REQUIRE_CONFIRMATION"), ), FieldSpec::new( "time_travel_retention_decrease", - FieldDefault::Member("sqlbuild._native", "RetentionDecreasePolicy", "DENY"), + FieldDefault::NativeMember(RecordId::RetentionDecreasePolicy, "DENY"), ), FieldSpec::new( "missing_migration_origin", - FieldDefault::Member("sqlbuild._native", "MissingMigrationOriginPolicy", "ALLOW"), + FieldDefault::NativeMember(RecordId::MissingMigrationOriginPolicy, "ALLOW"), ), FieldSpec::new( "execution_limits", - FieldDefault::Call("sqlbuild._native", "ExecutionLimitsConfig"), - ), - FieldSpec::new( - "diff", - FieldDefault::Call("sqlbuild._native", "TargetDiffConfig"), + FieldDefault::NativeCall(RecordId::ExecutionLimitsConfig), ), + FieldSpec::new("diff", FieldDefault::NativeCall(RecordId::TargetDiffConfig)), FieldSpec::new( "warehouses", - FieldDefault::Call("sqlbuild._native", "TargetWarehousesConfig"), + FieldDefault::NativeCall(RecordId::TargetWarehousesConfig), ), ], }; @@ -167,7 +158,7 @@ const R_LOCAL_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("defer_clone_from", FieldDefault::None), FieldSpec::new( "clone", - FieldDefault::Call("sqlbuild._native", "LocalClonePolicy"), + FieldDefault::NativeCall(RecordId::LocalClonePolicy), ), FieldSpec::new("compile_cache", FieldDefault::None), FieldSpec::new("time_travel_retention", FieldDefault::None), @@ -182,15 +173,12 @@ const R_LOCAL_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("missing_migration_origin", FieldDefault::None), FieldSpec::new( "execution_limits", - FieldDefault::Call("sqlbuild._native", "ExecutionLimitsConfig"), - ), - FieldSpec::new( - "diff", - FieldDefault::Call("sqlbuild._native", "TargetDiffConfig"), + FieldDefault::NativeCall(RecordId::ExecutionLimitsConfig), ), + FieldSpec::new("diff", FieldDefault::NativeCall(RecordId::TargetDiffConfig)), FieldSpec::new( "warehouses", - FieldDefault::Call("sqlbuild._native", "TargetWarehousesConfig"), + FieldDefault::NativeCall(RecordId::TargetWarehousesConfig), ), ], }; @@ -203,7 +191,7 @@ const R_SETTINGS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("query_change_tracking", FieldDefault::True), FieldSpec::new( "column_contract_mode", - FieldDefault::Member("sqlbuild._native", "ColumnContractMode", "IMPLICIT"), + FieldDefault::NativeMember(RecordId::ColumnContractMode, "IMPLICIT"), ), FieldSpec::new("concurrency", FieldDefault::Int(1)), FieldSpec::new("auto_load_sources", FieldDefault::True), @@ -243,7 +231,7 @@ const R_CONSTANTS_CONFIG: RecordSpec = RecordSpec { doc: "Project-wide constant rendering configuration.", fields: &[FieldSpec::new( "collection_rendering", - FieldDefault::Member("sqlbuild._native", "CollectionRendering", "VALUE_LIST"), + FieldDefault::NativeMember(RecordId::CollectionRendering, "VALUE_LIST"), )], }; const R_FUTURE_CURSORS_CONFIG: RecordSpec = RecordSpec { @@ -253,7 +241,7 @@ const R_FUTURE_CURSORS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("max_distance", FieldDefault::None), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "FutureCursorAction", "ERROR"), + FieldDefault::NativeMember(RecordId::FutureCursorAction, "ERROR"), ), ], }; @@ -264,7 +252,7 @@ const R_START_CURSORS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("max_ahead", FieldDefault::None), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "FutureCursorAction", "ERROR"), + FieldDefault::NativeMember(RecordId::FutureCursorAction, "ERROR"), ), ], }; @@ -274,11 +262,11 @@ const R_CURSORS_CONFIG: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "future", - FieldDefault::Call("sqlbuild._native", "FutureCursorsConfig"), + FieldDefault::NativeCall(RecordId::FutureCursorsConfig), ), FieldSpec::new( "start", - FieldDefault::Call("sqlbuild._native", "StartCursorsConfig"), + FieldDefault::NativeCall(RecordId::StartCursorsConfig), ), ], }; @@ -289,7 +277,7 @@ const R_MICROBATCH_LIMITS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("max_batches", FieldDefault::None), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "MicrobatchLimitAction", "ERROR"), + FieldDefault::NativeMember(RecordId::MicrobatchLimitAction, "ERROR"), ), ], }; @@ -298,7 +286,7 @@ const R_MICROBATCHES_CONFIG: RecordSpec = RecordSpec { doc: "Project-wide microbatch configuration.", fields: &[FieldSpec::new( "limits", - FieldDefault::Call("sqlbuild._native", "MicrobatchLimitsConfig"), + FieldDefault::NativeCall(RecordId::MicrobatchLimitsConfig), )], }; const R_DEFAULTS_CONFIG: RecordSpec = RecordSpec { @@ -400,7 +388,7 @@ const R_SCENARIO_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("local_type_overrides", FieldDefault::EmptyDict), FieldSpec::new( "snapshot_limits", - FieldDefault::Call("sqlbuild._native", "ScenarioSnapshotLimitsConfig"), + FieldDefault::NativeCall(RecordId::ScenarioSnapshotLimitsConfig), ), ], }; @@ -437,7 +425,7 @@ const R_LIFECYCLE_EVENT_SINKS_CONFIG: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "defaults", - FieldDefault::Call("sqlbuild._native", "LifecycleEventSinkFilterConfig"), + FieldDefault::NativeCall(RecordId::LifecycleEventSinkFilterConfig), ), FieldSpec::new("named", FieldDefault::EmptyDict), FieldSpec::new("shutdown_timeout", FieldDefault::None), @@ -449,7 +437,7 @@ const R_SINKS_CONFIG: RecordSpec = RecordSpec { doc: "Typed destination sink configuration.", fields: &[FieldSpec::new( "lifecycle", - FieldDefault::Call("sqlbuild._native", "LifecycleEventSinksConfig"), + FieldDefault::NativeCall(RecordId::LifecycleEventSinksConfig), )], }; const R_PROJECT_CONFIG: RecordSpec = RecordSpec { @@ -463,62 +451,50 @@ const R_PROJECT_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("connections", FieldDefault::EmptyDict), FieldSpec::new( "settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), - ), - FieldSpec::new( - "scopes", - FieldDefault::Call("sqlbuild._native", "ScopesConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), + FieldSpec::new("scopes", FieldDefault::NativeCall(RecordId::ScopesConfig)), FieldSpec::new( "references", - FieldDefault::Call("sqlbuild._native", "ReferencesConfig"), + FieldDefault::NativeCall(RecordId::ReferencesConfig), ), - FieldSpec::new("cost", FieldDefault::Call("sqlbuild._native", "CostConfig")), + FieldSpec::new("cost", FieldDefault::NativeCall(RecordId::CostConfig)), FieldSpec::new( "constants", - FieldDefault::Call("sqlbuild._native", "ConstantsConfig"), - ), - FieldSpec::new( - "cursors", - FieldDefault::Call("sqlbuild._native", "CursorsConfig"), + FieldDefault::NativeCall(RecordId::ConstantsConfig), ), + FieldSpec::new("cursors", FieldDefault::NativeCall(RecordId::CursorsConfig)), FieldSpec::new( "microbatches", - FieldDefault::Call("sqlbuild._native", "MicrobatchesConfig"), + FieldDefault::NativeCall(RecordId::MicrobatchesConfig), ), FieldSpec::new( "defaults", - FieldDefault::Call("sqlbuild._native", "DefaultsConfig"), + FieldDefault::NativeCall(RecordId::DefaultsConfig), ), - FieldSpec::new("diff", FieldDefault::Call("sqlbuild._native", "DiffConfig")), + FieldSpec::new("diff", FieldDefault::NativeCall(RecordId::DiffConfig)), FieldSpec::new( "materialization_defaults", - FieldDefault::Call("sqlbuild._native", "MaterializationDefaultsConfig"), + FieldDefault::NativeCall(RecordId::MaterializationDefaultsConfig), ), FieldSpec::new("path_defaults", FieldDefault::EmptyDict), FieldSpec::new("vars", FieldDefault::EmptyDict), FieldSpec::new("targets", FieldDefault::EmptyDict), - FieldSpec::new( - "janitor", - FieldDefault::Call("sqlbuild._native", "JanitorConfig"), - ), + FieldSpec::new("janitor", FieldDefault::NativeCall(RecordId::JanitorConfig)), FieldSpec::new( "migrations", - FieldDefault::Call("sqlbuild._native", "MigrationsConfig"), + FieldDefault::NativeCall(RecordId::MigrationsConfig), ), FieldSpec::new( "snapshots", - FieldDefault::Call("sqlbuild._native", "SnapshotsConfig"), + FieldDefault::NativeCall(RecordId::SnapshotsConfig), ), FieldSpec::new( "scenario", - FieldDefault::Call("sqlbuild._native", "ScenarioConfig"), - ), - FieldSpec::new("dbt", FieldDefault::Call("sqlbuild._native", "DbtConfig")), - FieldSpec::new( - "sinks", - FieldDefault::Call("sqlbuild._native", "SinksConfig"), + FieldDefault::NativeCall(RecordId::ScenarioConfig), ), + FieldSpec::new("dbt", FieldDefault::NativeCall(RecordId::DbtConfig)), + FieldSpec::new("sinks", FieldDefault::NativeCall(RecordId::SinksConfig)), ], }; const R_LOCAL_CONFIG: RecordSpec = RecordSpec { @@ -532,17 +508,14 @@ const R_LOCAL_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("targets", FieldDefault::EmptyDict), FieldSpec::new( "settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), FieldSpec::new("setting_overrides", FieldDefault::EmptyFrozenset), FieldSpec::new("vars", FieldDefault::EmptyDict), - FieldSpec::new( - "dbt", - FieldDefault::Call("sqlbuild._native", "LocalDbtConfig"), - ), + FieldSpec::new("dbt", FieldDefault::NativeCall(RecordId::LocalDbtConfig)), FieldSpec::new( "scenario", - FieldDefault::Call("sqlbuild._native", "ScenarioConfig"), + FieldDefault::NativeCall(RecordId::ScenarioConfig), ), ], }; @@ -646,7 +619,7 @@ const R_SCHEMA_SEED_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("meta", FieldDefault::EmptyDict), FieldSpec::new( "csv_settings", - FieldDefault::Call("sqlbuild._native", "SeedCsvSettings"), + FieldDefault::NativeCall(RecordId::SeedCsvSettings), ), FieldSpec::new("columns", FieldDefault::EmptyTuple), FieldSpec::new("tags", FieldDefault::EmptyTuple), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs index 5de21e3b7c..79b95cfef9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs @@ -11,15 +11,11 @@ use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::contracts::model_contracts::{ ConflictRow, DiagnosticRow, evaluate_project_model_contracts, project_promotion_conflicts, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::model_headers::{ColumnLocation, location_rows}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SPEC_MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const IMPLICIT_COLUMN_CONTRACTS: &str = "implicit"; const GENERIC_DIALECT: &str = "generic"; @@ -40,11 +36,7 @@ fn keyword_call<'py>( /// The compile's retained native project, read through `_native.compiled_project_facts`. fn native_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> { - project - .py() - .import(NATIVE_MODULE)? - .getattr("compiled_project_facts")? - .call1((project,)) + registry::native(project.py(), NativeAttr::CompiledProjectFacts)?.call1((project,)) } /// Where the model's SQL produces `column_name`, recovered from the authored SQL when needed. @@ -73,7 +65,7 @@ fn output_column_location<'py>( }; let py: Python<'py> = model.py(); let location: Bound<'py, PyAny> = keyword_call( - &py.import(SPEC_MODELS_MODULE)?.getattr("SourceLocation")?, + ®istry::class(py, RecordId::SourceLocation)?, vec![ ("path", model.getattr("relative_path")?), ("line", line.into_pyobject(py)?.into_any()), @@ -120,8 +112,7 @@ fn related_locations<'py>( return Ok(PyTuple::empty(py)); }; let related: Bound<'py, PyAny> = keyword_call( - &py.import(NATIVE_RECORDS_MODULE)? - .getattr("RelatedLocation")?, + ®istry::class(py, RecordId::RelatedLocation)?, vec![ ("label", OUTPUT_LABEL.into_pyobject(py)?.into_any()), ("location", location), @@ -139,8 +130,7 @@ fn contract_diagnostic<'py>( let (code, is_error, message, column_name, declared_index, output_column, related, help) = row; let severity: &str = if is_error { "error" } else { "warning" }; keyword_call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, vec![ ("phase", CONTRACT_PHASE.into_pyobject(py)?.into_any()), ("severity", severity.into_pyobject(py)?.into_any()), @@ -207,8 +197,7 @@ fn evaluate_model_contracts<'py>( } } keyword_call( - &py.import(CONTRACT_MODELS_MODULE)? - .getattr("ContractValidationResult")?, + ®istry::class(py, RecordId::ContractValidationResult)?, vec![("diagnostics", PyTuple::new(py, diagnostics)?.into_any())], ) } @@ -237,9 +226,7 @@ fn promotion_conflict_diagnostics<'py>( ), &native, )?; - let diagnostic_type: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?; + let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let path_type: Bound<'py, PyAny> = py.import(PATHLIB_MODULE)?.getattr("Path")?; let mut diagnostics: Vec> = Vec::with_capacity(conflicts.len()); for (name, relative_path, code, message, help) in conflicts { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs index b2a1074e97..7370c4c364 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs @@ -16,30 +16,19 @@ use sqlbuild_config::project::main::resolve_config_file::resolve_config_file; use sqlbuild_config::project::models::{Built, ConfigFile, ConfigPaths}; use crate::bindings::_helpers::boundary::config_values::config_value_to_python; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const PROJECT_CONFIG_ERROR: &str = "ProjectConfigError"; const FILE_NAMES: [&str; 4] = [ PROJECT_CONFIG_FILENAME, "sqlbuild_project.yml", "sqlbuild_local.toml", "sqlbuild_local.yml", ]; -/// The module of each `StrEnum` a loaded config holds. -const ENUM_MODULES: [(&str, &str); 10] = [ - ("ColumnContractMode", "sqlbuild._native"), - ("FutureCursorAction", "sqlbuild._native"), - ("MicrobatchLimitAction", "sqlbuild._native"), - ("TableType", "sqlbuild._native"), - ("TableTypeDowngradePolicy", "sqlbuild._native"), - ("RetentionDecreasePolicy", "sqlbuild._native"), - ("MissingMigrationOriginPolicy", "sqlbuild._native"), - ("EventExportSeverity", "sqlbuild._native"), - ("AuditSeverity", "sqlbuild.compiler.auditing.types"), - ("CollectionRendering", "sqlbuild._native"), -]; +/// The Python module of each `StrEnum` a loaded config holds that is not native. +const PYTHON_ENUM_MODULES: [(&str, &str); 1] = + [("AuditSeverity", "sqlbuild.compiler.auditing.types")]; /// The four configuration file paths of a project directory, as Python and native paths. struct Files<'py> { @@ -95,10 +84,7 @@ fn config_error(py: Python<'_>, failure: &ConfigFailure, files: &Files<'_>) -> P } let keywords = PyDict::new(py); keywords.set_item("help", failure.help.as_deref())?; - let error = py - .import(ERRORS_MODULE)? - .getattr(PROJECT_CONFIG_ERROR)? - .call((message,), Some(&keywords))?; + let error = registry::error(py, ErrorId::ProjectConfig)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) } @@ -126,11 +112,12 @@ fn load_error(py: Python<'_>, failure: &LoadFailure, files: &Files<'_>) -> PyRes } fn enum_member<'py>(py: Python<'py>, class: &str, value: &str) -> PyResult> { - let module = ENUM_MODULES - .iter() - .find(|(name, _)| *name == class) - .map_or("sqlbuild._native", |(_, module)| *module); - py.import(module)?.getattr(class)?.call1((value,)) + let class_object: Bound<'py, PyAny> = + match PYTHON_ENUM_MODULES.iter().find(|(name, _)| *name == class) { + Some((_, module)) => py.import(*module)?.getattr(class)?, + None => registry::class(py, registry::record_named(class)?)?, + }; + class_object.call1((value,)) } fn object<'py>( @@ -142,9 +129,7 @@ fn object<'py>( for (name, value) in fields { keywords.set_item(name, built(py, value)?)?; } - py.import(MODELS_MODULE)? - .getattr(class)? - .call((), Some(&keywords)) + registry::class(py, registry::record_named(class)?)?.call((), Some(&keywords)) } fn built(py: Python<'_>, value: Built) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs index 0df7568b94..f19c8b5671 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs @@ -1,5 +1,7 @@ //! Canonical snake_case resource identity checks and SQL function node types. +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -7,7 +9,6 @@ use pyo3::types::{PyDict, PyString, PyStringMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_model_config::resource_identities::main::resource_identity_failure::resource_identity_failure; -const RESOURCE_NAME_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const TABLE_FUNCTION_NODE_TYPE: &str = "table_fn"; const UDF_NODE_TYPE: &str = "udf"; @@ -32,10 +33,8 @@ fn validate_resource_identity( ); let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help_text)?; - let error: Bound<'_, PyAny> = py - .import(RESOURCE_NAME_EXCEPTIONS_MODULE)? - .getattr("ResourceIdentityError")? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::ResourceIdentity)?.call((message,), Some(&keywords))?; Err(PyErr::from_value(error)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs index 32feaabe89..dd1c27fc9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs @@ -12,18 +12,18 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_model_config::name_patterns::main::compile_name_pattern::compile_name_pattern; use sqlbuild_model_config::name_patterns::models::NamePattern; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::effective_config::{ effective_config_target_fields, effective_config_target_name, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const TARGET_GROUPS: [(&str, &str); 4] = [ - ("clone", "ClonePolicy"), - ("execution_limits", "ExecutionLimitsConfig"), - ("diff", "TargetDiffConfig"), - ("warehouses", "TargetWarehousesConfig"), +const TARGET_GROUPS: [(&str, RecordId); 4] = [ + ("clone", RecordId::ClonePolicy), + ("execution_limits", RecordId::ExecutionLimitsConfig), + ("diff", RecordId::TargetDiffConfig), + ("warehouses", RecordId::TargetWarehousesConfig), ]; const SNAPSHOT_LIMITS: [&str; 4] = [ "max_rows_per_relation", @@ -42,7 +42,7 @@ thread_local! { fn spec_error<'py>(py: Python<'py>, message: Bound<'py, PyString>) -> PyResult { Ok(PyErr::from_value( - imported(py, ERRORS_MODULE, "SpecConfigError")?.call1((message,))?, + registry::error(py, ErrorId::SpecConfig)?.call1((message,))?, )) } @@ -72,7 +72,7 @@ pub(crate) fn resolve_target_config<'py>( local_config: &Bound<'py, PyAny>, target_name: &Bound<'py, PyAny>, ) -> PyResult> { - let target_class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "TargetConfig")?; + let target_class: Bound<'py, PyAny> = registry::class(py, RecordId::TargetConfig)?; let (rows, error) = effective_config_target_fields( py, (project_config.clone(), local_config.clone()), @@ -99,8 +99,7 @@ pub(crate) fn resolve_target_config<'py>( } } for ((group, class_name), (_, fields)) in TARGET_GROUPS.iter().zip(&grouped) { - let nested: Bound<'py, PyAny> = - imported(py, MODELS_MODULE, class_name)?.call((), Some(fields))?; + let nested: Bound<'py, PyAny> = registry::class(py, *class_name)?.call((), Some(fields))?; values.set_item(*group, nested)?; } target_class.call((), Some(&values)) @@ -184,7 +183,7 @@ fn resolve_effective_scenario_config<'py>( }) .collect::>()?; call_with( - &imported(py, MODELS_MODULE, "ScenarioConfig")?, + ®istry::class(py, RecordId::ScenarioConfig)?, &[ ( "run_namespace", @@ -197,7 +196,7 @@ fn resolve_effective_scenario_config<'py>( ( "snapshot_limits", call_with( - &imported(py, MODELS_MODULE, "ScenarioSnapshotLimitsConfig")?, + ®istry::class(py, RecordId::ScenarioSnapshotLimitsConfig)?, &limits, )?, ), @@ -282,7 +281,7 @@ fn loader_parts<'py>( ), _ => (text(parts[0]), text(parts[1]), text(parts[2])), }; - imported(py, MODELS_MODULE, "LoaderDestinationParts")?.call1((database, schema, table)) + registry::class(py, RecordId::LoaderDestinationParts)?.call1((database, schema, table)) } /// Whether a family's optional `name_pattern` accepts `column_name`. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs index f03670b956..6bde63f1ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs @@ -1,5 +1,7 @@ //! Typed SQL value normalization between Python authored values and native `SqlValue`s. +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -13,10 +15,6 @@ use sqlbuild_model_config::sql_values::errors::SqlValueFailure; use sqlbuild_model_config::sql_values::main::normalize_sql_value::normalize_sql_value as normalized; use sqlbuild_model_config::sql_values::models::{RawKey, RawSqlValue, SqlValueLimits}; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; - /// The Python classes authored values are told apart by and normalized values are built from. pub(crate) struct SqlValueClasses<'py> { decimal: Bound<'py, PyAny>, @@ -29,15 +27,13 @@ pub(crate) struct SqlValueClasses<'py> { impl<'py> SqlValueClasses<'py> { pub(crate) fn load(py: Python<'py>) -> PyResult { - let models = py.import(MODELS_MODULE)?; - let native = py.import(NATIVE_MODULE)?; Ok(Self { decimal: py.import("decimal")?.getattr("Decimal")?, - authored_set: native.getattr("AuthoredSqlSet")?, - value_call: native.getattr("AuthoredSqlValueCall")?, - sql_value: models.getattr("SqlValue")?, - logical_type: models.getattr("SqlLogicalType")?, - kind: py.import(TYPES_MODULE)?.getattr("SqlValueKind")?, + authored_set: registry::class(py, RecordId::AuthoredSqlSet)?, + value_call: registry::class(py, RecordId::AuthoredSqlValueCall)?, + sql_value: registry::class(py, RecordId::SqlValue)?, + logical_type: registry::class(py, RecordId::SqlLogicalType)?, + kind: registry::class(py, RecordId::SqlValueKind)?, }) } } @@ -178,9 +174,7 @@ pub(crate) fn sql_value<'py>( pub(crate) fn sql_value_error(py: Python<'_>, failure: SqlValueFailure) -> PyResult { Ok(match failure { SqlValueFailure::Invalid(message) => PyErr::from_value( - py.import(NATIVE_MODULE)? - .getattr("SqlValueValidationError")? - .call1((message,))?, + registry::native(py, NativeAttr::SqlValueValidationError)?.call1((message,))?, ), SqlValueFailure::DecimalOverflow => { let overflow = py.import("decimal")?.getattr("Overflow")?; @@ -204,10 +198,7 @@ fn normalize_sql_value<'py>( let raw = self::raw_value(raw_value, &classes)?; let applied: Bound<'py, PyAny> = match limits { Some(limits) if !limits.is_none() => limits.clone(), - _ => py - .import(NATIVE_MODULE)? - .getattr("SqlValueLimits")? - .call0()?, + _ => registry::class(py, RecordId::SqlValueLimits)?.call0()?, }; let limits = SqlValueLimits { max_depth: applied.getattr("max_depth")?.extract()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs index e8cc03cc1d..cd02eb1c20 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs @@ -7,13 +7,14 @@ use pyo3::prelude::{ use pyo3::types::{PyBool, PyDict, PyInt, PyString}; use pyo3::{pyfunction, wrap_pyfunction}; -const EXCEPTIONS_MODULE: &str = "sqlbuild.sql_values.exceptions"; const DATETIME_MODULE: &str = "datetime"; const JSON_MODULE: &str = "json"; use sqlbuild_state::freshness::constants::STRING_STATE_VALUE_TYPE as STRING_TYPE; const INTEGER_TYPE: &str = "integer"; const BOOLEAN_TYPE: &str = "boolean"; const TIMESTAMP_TYPE: &str = "timestamp"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use sqlbuild_state::freshness::constants::TEXT_TIMESTAMP_STATE_VALUE_TYPE as TEXT_TIMESTAMP_TYPE; const DATE_TYPE: &str = "date"; const JSON_TYPE: &str = "json"; @@ -144,10 +145,8 @@ fn typed_null(declared_type: &str) -> &'static str { fn rendering_error(py: Python<'_>, message: &str) -> PyErr { let raised = || -> PyResult { - let error: Bound<'_, PyAny> = py - .import(EXCEPTIONS_MODULE)? - .getattr("SqlValueRenderingError")? - .call1((message,))?; + let error: Bound<'_, PyAny> = + host::entry(py, ExecutorHost::SqlValueRenderingError)?.call1((message,))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs index 1e961676eb..5192cf7e9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs @@ -12,9 +12,10 @@ use sqlbuild_core::cost_command::main::is_zero_cost_duration::is_zero_cost_durat use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cost_command::refusal::cost_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const DATETIME_MODULE: &str = "datetime"; -const DURATION_MODELS: &str = "sqlbuild._native"; const MINUTE_TO_DAY_DURATION_UNITS: [&str; 3] = ["m", "h", "d"]; fn utc(py: Python<'_>) -> PyResult> { @@ -101,7 +102,7 @@ pub(crate) fn parse_since<'py>(py: Python<'py>, value: &str) -> PyResult = - imported(py, DURATION_MODELS, "Duration")?.call_method1("parse", (value,))?; + registry::class(py, RecordId::Duration)?.call_method1("parse", (value,))?; if duration.is_none() { return parse_bound(py, value, "--since", false); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs index 64d9d392b6..3d4cacdeb9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs @@ -18,8 +18,8 @@ use crate::bindings::_helpers::cost_command::refresh::refresh_pending_cost_run; use crate::bindings::_helpers::cost_command::refusal::cost_error; use crate::bindings::_helpers::cost_command::sorting::{sort_history, sort_resources}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; - -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// The request fields every cost view reads. struct CostRequest<'py> { @@ -103,7 +103,7 @@ fn refused(py: Python<'_>, result: Result<(), CostCommandError>) -> PyResult<()> fn cost_runs<'py>(request: &CostRequest<'py>) -> PyResult>> { let py: Python<'py> = request.request.py(); call_with( - &imported(py, NATIVE_MODULE, "list_cost_runs")?, + ®istry::native(py, NativeAttr::ListCostRuns)?, &[("project_dir", request.project_dir.clone())], )? .try_iter()? @@ -178,7 +178,7 @@ fn run_history(request: &CostRequest<'_>) -> PyResult { let records: Bound<'_, PyAny> = PyTuple::new(py, records)?.into_any(); let count: Bound<'_, PyAny> = PyInt::new(py, matching_count).into_any(); let payload: String = call_with( - &imported(py, NATIVE_MODULE, "format_cost_history_json")?, + ®istry::native(py, NativeAttr::FormatCostHistoryJson)?, &[ ("records", records.clone()), ("matching_count", count.clone()), @@ -187,7 +187,7 @@ fn run_history(request: &CostRequest<'_>) -> PyResult { .extract()?; request.emit(&payload, || { call_with( - &imported(py, NATIVE_MODULE, "format_cost_history")?, + ®istry::native(py, NativeAttr::FormatCostHistory)?, &[ ("records", records), ("matching_count", count), @@ -208,7 +208,7 @@ fn resolve_run<'py>(request: &CostRequest<'py>) -> PyResult> { let records: Vec> = cost_runs(request)?; if request.selector == LATEST_SELECTOR { let latest: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_cost_run")?, + ®istry::native(py, NativeAttr::ResolveCostRun)?, &[ ("project_dir", request.project_dir.clone()), ("selector", PyString::new(py, &request.selector).into_any()), @@ -279,7 +279,7 @@ fn run_detail(request: &CostRequest<'_>) -> PyResult { } None => record.clone(), }; - let payload: String = imported(py, NATIVE_MODULE, "format_cost_json")? + let payload: String = registry::native(py, NativeAttr::FormatCostJson)? .call1((json_record,))? .extract()?; request.emit(&payload, || { @@ -287,7 +287,7 @@ fn run_detail(request: &CostRequest<'_>) -> PyResult { options.set_item("record", &record)?; options.set_item("use_color", request.use_color()?)?; options.set_item("limit", limit)?; - imported(py, NATIVE_MODULE, "format_cost_breakdown")? + registry::native(py, NativeAttr::FormatCostBreakdown)? .call((), Some(&options))? .extract() })?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs index 0f3bdfdcdd..c4e0174cab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs @@ -12,11 +12,10 @@ use sqlbuild_core::cost_command::models::CostCoverage; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::cost_telemetry::values::cost_aware; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_TARGET_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const COST_TYPES: &str = "sqlbuild.cost.types"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; fn coverage(summary: &Bound<'_, PyAny>) -> PyResult { Ok(CostCoverage { @@ -40,7 +39,7 @@ fn refresh_record<'py>( let py: Python<'py> = project_dir.py(); let resolve = || { call_with( - &imported(py, NATIVE_MODULE, "resolve_cost_run")?, + ®istry::native(py, NativeAttr::ResolveCostRun)?, &[ ("project_dir", project_dir.clone()), ("selector", selector.clone()), @@ -51,7 +50,7 @@ fn refresh_record<'py>( return resolve(); } let exact: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "read_cost_run")?, + ®istry::native(py, NativeAttr::ReadCostRun)?, &[ ("project_dir", project_dir.clone()), ("run_id", selector.clone()), @@ -104,18 +103,18 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { } let target_name: Bound<'_, PyAny> = record.getattr("target_name")?; let discovered: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let target_config: Bound<'_, PyAny> = call_with( - &imported(py, EFFECTIVE_TARGET_MODULE, "build_effective_target_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", target_name.clone()), ], )?; let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -126,7 +125,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { return Ok(()); } let none_capability: Bound<'_, PyAny> = - imported(py, COST_TYPES, "CostCapability")?.getattr("NONE")?; + host::entry(py, ExecutorHost::CostCapability)?.getattr("NONE")?; if adapter .call_method0("cost_capability")? .eq(none_capability)? @@ -134,7 +133,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { return Ok(()); } let connection_config: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered), ("project_dir", project_dir.clone()), @@ -171,7 +170,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { let refreshed: Bound<'_, PyAny> = imported(py, "dataclasses", "replace")?.call((record,), Some(&changes))?; call_with( - &imported(py, NATIVE_MODULE, "write_cost_run")?, + ®istry::native(py, NativeAttr::WriteCostRun)?, &[("project_dir", project_dir), ("record", refreshed)], )?; Ok(()) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs index 13b791cb68..72b2d7bd69 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs @@ -4,16 +4,15 @@ use pyo3::prelude::{Bound, PyAnyMethods, PyErr, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_core::cost_command::errors::CostCommandError; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// The `CliUserError` for a refused cost request. pub(crate) fn cost_error(py: Python<'_>, error: &CostCommandError) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message.as_str(),), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message.as_str(),), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs index b3d7858934..2c5d62f342 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs @@ -11,16 +11,17 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::cost_report::main::format_cost_breakdown::format_cost_breakdown; use sqlbuild_core::cost_report::models::BreakdownOptions; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cost_report::records::cost_run; use crate::bindings::_helpers::cost_report::store; use crate::bindings::_helpers::cost_telemetry::values::cost_aware; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const LOGGER_NAME: &str = "sqlbuild.cli.commands._helpers.cost.collection"; const COLLECTION_LOGGER_NAME: &str = "sqlbuild.cost"; -const COST_TYPES: &str = "sqlbuild.cost.types"; -const COST_MODELS: &str = "sqlbuild.cost.models"; const DEFAULT_RATE_SOURCE: &str = "default"; const CONFIGURED_RATE_SOURCE: &str = "configured"; const STATEMENT_LEDGER_PATH: [&str; 4] = ["target", "executions", "", "statements.jsonl"]; @@ -162,7 +163,7 @@ fn cost_record<'py>( "had_executable_work", finalization.getattr("had_executable_work")?, )); - call_with(&imported(py, NATIVE_RECORDS, "CostRunRecord")?, &fields) + call_with(®istry::class(py, RecordId::CostRunRecord)?, &fields) } fn summary<'py>( @@ -172,7 +173,7 @@ fn summary<'py>( message: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, COST_MODELS, "RunCostSummary")?, + &host::entry(py, ExecutorHost::RunCostSummary)?, &[ ("status", status), ("usd_per_credit", config.getattr("usd_per_credit")?), @@ -216,7 +217,7 @@ fn collection_failure<'py>( COLLECTION_LOGGER_NAME, )?; let status: Bound<'py, PyAny> = - imported(py, COST_TYPES, "CostStatus")?.getattr("COLLECTION_FAILED")?; + host::entry(py, ExecutorHost::CostStatus)?.getattr("COLLECTION_FAILED")?; let message: Bound<'py, PyAny> = PyString::new( py, &format!("Cost telemetry failed during {stage} ({error_type})."), @@ -286,7 +287,7 @@ pub(crate) fn finalize_build_cost<'py>( return collection_failure(finalization, rate, "capability detection", &error); } }; - if capability.eq(imported(py, COST_TYPES, "CostCapability")?.getattr("NONE")?)? { + if capability.eq(host::entry(py, ExecutorHost::CostCapability)?.getattr("NONE")?)? { return Ok(py.None().into_bound(py)); } let cost: Bound<'py, PyAny> = if finalization.getattr("collect")?.is_truthy()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs index 54d46b6fa2..0b276d5592 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs @@ -6,15 +6,14 @@ use sqlbuild_core::cost_report::errors::CostArtifactError; use sqlbuild_core::cost_report::models::{CostResource, CostRun, CostSummary}; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const COST_MODELS: &str = "sqlbuild.cost.models"; -const COST_TYPES: &str = "sqlbuild.cost.types"; -const COST_EXCEPTIONS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// The `CostArtifactError` Python raised for the same artifact. pub(crate) fn artifact_error(py: Python<'_>, error: &CostArtifactError) -> PyErr { - match imported(py, COST_EXCEPTIONS, "CostArtifactError") + match registry::native(py, NativeAttr::CostArtifactError) .and_then(|class| class.call1((error.message.as_str(),))) { Ok(instance) => PyErr::from_value(instance), @@ -155,7 +154,7 @@ fn resource_object<'py>( builders.decimal(&resource.estimated_compute_credits)?, )?; fields.set_item("estimated_usd", builders.decimal(&resource.estimated_usd)?)?; - imported(py, NATIVE_RECORDS, "ResourceCost")?.call((), Some(&fields)) + registry::class(py, RecordId::ResourceCost)?.call((), Some(&fields)) } fn summary_object<'py>( @@ -170,7 +169,7 @@ fn summary_object<'py>( let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item( "status", - imported(py, COST_TYPES, "CostStatus")?.call1((cost.status.as_str(),))?, + host::entry(py, ExecutorHost::CostStatus)?.call1((cost.status.as_str(),))?, )?; fields.set_item("usd_per_credit", builders.decimal(&cost.usd_per_credit)?)?; fields.set_item("rate_source", &cost.rate_source)?; @@ -194,7 +193,7 @@ fn summary_object<'py>( fields.set_item("method", &cost.method)?; fields.set_item("limitations", PyTuple::new(py, &cost.limitations)?)?; fields.set_item("message", &cost.message)?; - imported(py, COST_MODELS, "RunCostSummary")?.call((), Some(&fields)) + host::entry(py, ExecutorHost::RunCostSummary)?.call((), Some(&fields)) } /// A plain run as the `CostRunRecord` it was read from. @@ -216,7 +215,7 @@ pub(crate) fn record_object<'py>(py: Python<'py>, run: &CostRun) -> PyResult( ) -> PyResult> { let decimal = |value: &PyDecimal| python_decimal(py, value); call_with( - &imported(py, NATIVE_RECORDS, "ResourceCost")?, + ®istry::class(py, RecordId::ResourceCost)?, &[ ( "resource_type", @@ -115,7 +116,7 @@ pub(crate) fn summary_object<'py>( let mut fields: Vec<(&str, Bound<'py, PyAny>)> = vec![ ( "status", - imported(py, COST_TYPES, "CostStatus")?.getattr(state.status)?, + host::entry(py, ExecutorHost::CostStatus)?.getattr(state.status)?, ), ("usd_per_credit", usd_per_credit.clone()), ("rate_source", rate_source.clone()), @@ -156,7 +157,7 @@ pub(crate) fn summary_object<'py>( ), ]); } - call_with(&imported(py, COST_MODELS, "RunCostSummary")?, &fields) + call_with(&host::entry(py, ExecutorHost::RunCostSummary)?, &fields) } /// The allocation that prices queries, or `None` while the run's history is pending. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs index 01c70906eb..fbc922604d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs @@ -8,7 +8,9 @@ use pyo3::{IntoPyObjectExt, pyclass, pymethods}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; -use crate::bindings::_helpers::cost_telemetry::values::{COST_MODELS, process_object}; +use crate::bindings::_helpers::cost_telemetry::values::process_object; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const CONTEXT_VARIABLE_NAME: &str = "sqlbuild_cost_resource_context"; const DEFAULT_PHASE: &str = "execute"; @@ -110,7 +112,7 @@ impl CostContext { } }; let context: Bound<'_, PyAny> = call_with( - &imported(py, COST_MODELS, "CostResourceContext")?, + &host::entry(py, ExecutorHost::CostResourceContext)?, &[ ("run_id", arguments.value("run_id")?), ("resource_type", arguments.value("resource_type")?), @@ -157,7 +159,7 @@ impl CostContext { DEFAULT_ATTEMPT.into_bound_py_any(py)? }; let context: Bound<'_, PyAny> = call_with( - &imported(py, COST_MODELS, "CostResourceContext")?, + &host::entry(py, ExecutorHost::CostResourceContext)?, &[ ("run_id", current.getattr("run_id")?), ("resource_type", arguments.value("resource_type")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs index d013a6d08a..04eb73e589 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs @@ -19,10 +19,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; use crate::bindings::_helpers::cost_telemetry::health::mark_ledger_failure; -use crate::bindings::_helpers::cost_telemetry::values::{COST_EXCEPTIONS, process_object}; +use crate::bindings::_helpers::cost_telemetry::values::process_object; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; static LEDGER_LOCK: PyOnceLock> = PyOnceLock::new(); @@ -195,7 +195,7 @@ fn ledger_entry<'py>(line: &Bound<'py, PyAny>) -> PyResult> { ("completed_at", parsed("completed_at")?), ]; call_with( - &imported(py, NATIVE_RECORDS, "StatementLedgerEntry")?, + ®istry::class(py, RecordId::StatementLedgerEntry)?, &entry_fields, ) } @@ -235,7 +235,7 @@ pub(crate) fn read_ledger<'py>( error.get_type(py).name()? ); let artifact_error: Bound<'py, PyAny> = - imported(py, COST_EXCEPTIONS, "CostArtifactError")?.call1((message,))?; + registry::native(py, NativeAttr::CostArtifactError)?.call1((message,))?; mark_failure(run_id, &artifact_error)?; } Err(error) => return Err(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs index ebb850a906..36713b0271 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs @@ -22,8 +22,9 @@ use crate::bindings::_helpers::cost_telemetry::allocation::{ use crate::bindings::_helpers::cost_telemetry::health::consume_ledger_failure; use crate::bindings::_helpers::cost_telemetry::ledger::read_ledger; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const DEADLINE_SECONDS: f64 = 10.0; const QUERY_TIMEOUT_SECONDS: &str = "5"; const RUNNING_STATUS: &str = "RUNNING"; @@ -258,7 +259,7 @@ fn row_observation<'py>( } let tag: QueryTag = tag.unwrap_or_default(); let observation: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS, "QueryCostObservation")?, + ®istry::class(py, RecordId::QueryCostObservation)?, &[ ("query_id", query_id.str()?.into_any()), ("warehouse_name", warehouse_name.str()?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs index f45635d43d..b615bace40 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs @@ -11,9 +11,6 @@ use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; use crate::bindings::_helpers::state_rows::timestamps::offset_aware; -pub(crate) const COST_MODELS: &str = "sqlbuild.cost.models"; -pub(crate) const COST_TYPES: &str = "sqlbuild.cost.types"; -pub(crate) const COST_EXCEPTIONS: &str = "sqlbuild._native"; const DECIMAL_MODULE: &str = "decimal"; /// The methods an adapter with warehouse cost telemetry implements. const COST_AWARE_METHODS: [&str; 2] = ["cost_capability", "collect_run_cost"]; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs index 6d4b909315..1a8778bef5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs @@ -18,10 +18,10 @@ use crate::bindings::_helpers::cursor::values::{ DATETIME_MODULE, civil_date, local_date_time, naive_date_time, plain_date, replaced, timedelta_micros, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -pub(crate) const MODELS_MODULE: &str = "sqlbuild._native"; const EXCEPTIONS_MODULE: &str = "sqlbuild.cursor_algebra.exceptions"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; /// Cursor grain names, finest first. pub(crate) const GRAIN_NAMES: [(&str, Grain); 6] = [ ("second", Grain::Second), @@ -56,12 +56,11 @@ pub(crate) struct CursorSession<'py> { impl<'py> CursorSession<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let models = py.import(MODELS_MODULE)?; Ok(Self { py, - timestamp_class: models.getattr("TimestampValue")?, - date_class: models.getattr("DateValue")?, - integer_class: models.getattr("IntegerValue")?, + timestamp_class: registry::class(py, RecordId::TimestampValue)?, + date_class: registry::class(py, RecordId::DateValue)?, + integer_class: registry::class(py, RecordId::IntegerValue)?, inputs: RefCell::new(Vec::new()), failure: RefCell::new(None), }) @@ -135,7 +134,7 @@ impl<'py> CursorSession<'py> { return Ok(Some(*known)); } } - imported(self.py, PLANNER_TYPES_MODULE, "CursorGrain")?.call1((grain,))?; + registry::class(self.py, RecordId::CursorGrain)?.call1((grain,))?; Err(PyTypeError::new_err("cursor grain must be a CursorGrain")) } @@ -198,13 +197,13 @@ impl<'py> CursorSession<'py> { .iter() .find(|(_, known)| *known == grain) .map_or("", |(name, _)| name); - imported(self.py, PLANNER_TYPES_MODULE, "CursorGrain")?.call1((name,)) + registry::class(self.py, RecordId::CursorGrain)?.call1((name,)) } /// A new `AlignedInterval`, validated as its constructor validates. pub(crate) fn python_interval(&self, interval: &Interval) -> PyResult> { call_with( - &imported(self.py, MODELS_MODULE, "AlignedInterval")?, + ®istry::class(self.py, RecordId::AlignedInterval)?, &[ ("start", self.python_scalar(&interval.start)?), ("end", self.python_scalar(&interval.end)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs index f522059e2b..2a940a21e7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs @@ -17,12 +17,12 @@ use sqlbuild_cursor::algebra::main::split_into_batches::split_into_batches; use sqlbuild_cursor::algebra::main::validate_interval::validate_interval; use sqlbuild_cursor::algebra::models::{Interval, Scalar}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::as_scalar; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const MICROBATCH_MODELS_MODULE: &str = "sqlbuild.microbatches.models"; -const MICROBATCH_EXCEPTIONS_MODULE: &str = "sqlbuild.microbatches.exceptions"; const RAW_INTERVAL_CURSOR_TYPES: [&str; 2] = ["timestamp", "integer"]; /// One stored interval with its parsed bounds. @@ -112,11 +112,9 @@ fn merge_raw_intervals<'py>( let py: Python<'py> = intervals.py(); let kind: String = cursor_type.str()?.extract()?; if !RAW_INTERVAL_CURSOR_TYPES.contains(&kind.as_str()) { - return Err( - imported(py, MICROBATCH_EXCEPTIONS_MODULE, "MicrobatchStateError") - .and_then(|class| class.call1((format!("unsupported cursor type: {kind}"),))) - .map_or_else(|error| error, PyErr::from_value), - ); + return Err(host::entry(py, ExecutorHost::MicrobatchStateError) + .and_then(|class| class.call1((format!("unsupported cursor type: {kind}"),))) + .map_or_else(|error| error, PyErr::from_value)); } let session: CursorSession<'py> = CursorSession::new(py)?; let mut parsed: Vec> = Vec::new(); @@ -140,7 +138,7 @@ fn merge_raw_intervals<'py>( if let Some(error) = failure { return Err(error); } - let class: Bound<'py, PyAny> = imported(py, MICROBATCH_MODELS_MODULE, "MicrobatchInterval")?; + let class: Bound<'py, PyAny> = host::entry(py, ExecutorHost::MicrobatchInterval)?; let mut merged: Vec> = Vec::new(); for next in parsed { let Some(last) = merged.last_mut() else { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs index 268983ed83..a36a0be5fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs @@ -6,15 +6,17 @@ use pyo3::types::{PyInt, PyString, PyStringMethods}; use sqlbuild_cursor::algebra::main::parse_integer_text::parse_integer_text; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::cursor::conversion::{MODELS_MODULE, algebra_error}; +use crate::bindings::_helpers::cursor::conversion::algebra_error; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const DECIMAL_MODULE: &str = "decimal"; const INTEGER_AS_TIMESTAMP: &str = "an integer cursor cannot be used as a timestamp"; const TIMESTAMP_AS_INTEGER: &str = "a timestamp cursor cannot be used as an integer"; -fn instance_of(value: &Bound<'_, PyAny>, class: &str) -> PyResult { - value.is_instance(&imported(value.py(), MODELS_MODULE, class)?) +fn instance_of(value: &Bound<'_, PyAny>, class: RecordId) -> PyResult { + value.is_instance(®istry::class(value.py(), class)?) } /// A timestamp override as a `datetime`; dates start at midnight and text is ISO 8601. @@ -23,15 +25,15 @@ pub(crate) fn cursor_timestamp<'py>(value: &Bound<'py, PyAny>) -> PyResult = imported(py, DATETIME_MODULE, "datetime")?; - if instance_of(value, "DateValue")? { + if instance_of(value, RecordId::DateValue)? { let midnight: Bound<'py, PyAny> = datetime.getattr("min")?.call_method0("time")?; return datetime.call_method1("combine", (value.getattr("value")?, midnight)); } - if instance_of(value, "IntegerValue")? { + if instance_of(value, RecordId::IntegerValue)? { return Err(algebra_error(py, INTEGER_AS_TIMESTAMP)); } datetime.call_method1("fromisoformat", (value,)) @@ -43,10 +45,10 @@ pub(crate) fn cursor_integer<'py>(value: &Bound<'py, PyAny>) -> PyResult() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs index f551d426da..3648587691 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs @@ -25,11 +25,14 @@ use sqlbuild_cursor::algebra::main::render::render; use sqlbuild_cursor::algebra::models::{Extreme, Grain, Scalar, ScalarValue, SortKey, ZoneOffset}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::cursor::conversion::{CursorSession, MODELS_MODULE, algebra_error}; +use crate::bindings::_helpers::cursor::conversion::{CursorSession, algebra_error}; use crate::bindings::_helpers::cursor::values::{DATETIME_MODULE, naive_date_time}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const DECIMAL_MODULE: &str = "decimal"; -const TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; const INTEGER_CURSOR: &str = "integer"; const UTC_OFFSET: i64 = 0; const TIMESTAMP_CURSOR: &str = "timestamp"; @@ -45,7 +48,7 @@ fn chained(py: Python<'_>, message: String, cause: PyErr) -> PyErr { fn parse_integer<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = raw.py(); let text: String = raw.str()?.to_str()?.to_owned(); - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "IntegerValue")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::IntegerValue)?; if let Some(value) = parse_integer_text(&text) { return call_with(&class, &[("value", value.into_pyobject(py)?.into_any())]); } @@ -105,7 +108,7 @@ fn parse_timestamp_text<'py>(raw: &Bound<'py, PyAny>, text: &str) -> PyResult { return call_with( - &imported(py, MODELS_MODULE, "DateValue")?, + ®istry::class(py, RecordId::DateValue)?, &[("value", date)], ); } @@ -117,7 +120,7 @@ fn parse_timestamp_text<'py>(raw: &Bound<'py, PyAny>, text: &str) -> PyResult call_with( - &imported(py, MODELS_MODULE, "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[ ("value", value), ("source_text", PyString::new(py, &normalized).into_any()), @@ -150,13 +153,13 @@ pub(crate) fn parse_value<'py>( if raw.is_instance(&imported(py, DATETIME_MODULE, "datetime")?)? { let value: Bound<'py, PyAny> = utc_normalized(raw)?; return call_with( - &imported(py, MODELS_MODULE, "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", value)], ); } if raw.is_instance(&imported(py, DATETIME_MODULE, "date")?)? { return call_with( - &imported(py, MODELS_MODULE, "DateValue")?, + ®istry::class(py, RecordId::DateValue)?, &[("value", raw.clone())], ); } @@ -224,7 +227,7 @@ pub(crate) fn render_value(value: &Bound<'_, PyAny>) -> PyResult { #[pyfunction(name = "cursor_sentinel_to_token")] #[pyo3(signature = (*, sentinel))] pub(crate) fn sentinel_to_token(sentinel: &Bound<'_, PyAny>) -> PyResult { - if sentinel.is_instance(&imported(sentinel.py(), TYPES_MODULE, "BoundSentinel")?)? { + if sentinel.is_instance(&host::entry(sentinel.py(), ExecutorHost::BoundSentinel)?)? { return sentinel.getattr("value")?.extract(); } render_value(sentinel) @@ -235,7 +238,7 @@ pub(crate) fn sentinel_to_token(sentinel: &Bound<'_, PyAny>) -> PyResult #[pyo3(signature = (*, token))] fn sentinel_from_token<'py>(token: &Bound<'py, PyAny>) -> PyResult>> { let py: Python<'py> = token.py(); - match imported(py, TYPES_MODULE, "BoundSentinel")?.call1((token,)) { + match host::entry(py, ExecutorHost::BoundSentinel)?.call1((token,)) { Ok(sentinel) => Ok(Some(sentinel)), Err(error) if error.is_instance_of::(py) => Ok(None), Err(error) => Err(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs index bc1c66c99a..5a25b40ee2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs @@ -11,14 +11,12 @@ use sqlbuild_core::json::errors::JsonEmitError; use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::dag_artifact::rows::{project_input, python_graph_input}; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; - -const SPEC_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; fn python_error(error: DagArtifactError, py: pyo3::Python<'_>) -> PyErr { match error { - DagArtifactError::LoaderDestination(message) => py - .import(SPEC_EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("SpecConfigError")) + DagArtifactError::LoaderDestination(message) => registry::error(py, ErrorId::SpecConfig) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value), DagArtifactError::MissingParameterType(name) => PyKeyError::new_err(name), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs index d697b98371..9e4c85ea6b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs @@ -14,11 +14,10 @@ use sqlbuild_analysis::dag_artifact::models::{ DagProjectionCheck, DagProjectionGraph, DagProjectionNode, }; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::dag_artifact::selection::selected_node_ids; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; -const DAGSTER_CONSTANTS: &str = "sqlbuild.integrations.dagster.constants"; -const RIVERS_CONSTANTS: &str = "sqlbuild.integrations.rivers.constants"; const PROJECT_NAME_KEY: &str = "project_name"; const DEFAULT_MATERIALIZATION: &str = "table"; @@ -112,15 +111,15 @@ fn translated_nodes<'py>(artifact: &ArtifactGraph<'py>) -> PyResult, (module, name): (&str, &str)) -> PyResult> { - listed(&imported(py, module, name)?)? +fn kind_set(py: Python<'_>, constant: ThirdPartyHost) -> PyResult> { + listed(&host::entry(py, constant)?)? .iter() .map(text) .collect() } -fn constant_text(py: Python<'_>, (module, name): (&str, &str)) -> PyResult { - text(&imported(py, module, name)?) +fn constant_text(py: Python<'_>, constant: ThirdPartyHost) -> PyResult { + text(&host::entry(py, constant)?) } fn picked<'py>(objects: &[Bound<'py, PyAny>], indexes: &[usize]) -> Vec> { @@ -148,13 +147,13 @@ fn dagster_asset_plans<'py>(dag: &Bound<'py, PyAny>) -> PyResult = artifact_graph(dag, false)?; let upstream_nodes: Vec> = translated_nodes(&artifact)?; let policy: AssetProjectionPolicy = AssetProjectionPolicy { - asset_kinds: kind_set(py, (DAGSTER_CONSTANTS, "DAGSTER_ASSET_NODE_KINDS"))?, + asset_kinds: kind_set(py, ThirdPartyHost::DagsterAssetNodeKinds)?, upstream_asset_kinds_only: false, }; let kinds: AssetSpecKindPolicy = AssetSpecKindPolicy { - model_kind: constant_text(py, (DAGSTER_CONSTANTS, "MODEL_NODE_KIND"))?, - view_materialization: constant_text(py, (DAGSTER_CONSTANTS, "VIEW_MATERIALIZATION_TYPE"))?, - direct_kinds: kind_set(py, (DAGSTER_CONSTANTS, "DAGSTER_DIRECT_KIND_NODE_KINDS"))?, + model_kind: constant_text(py, ThirdPartyHost::ModelNodeKind)?, + view_materialization: constant_text(py, ThirdPartyHost::ViewMaterializationType)?, + direct_kinds: kind_set(py, ThirdPartyHost::DagsterDirectKindNodeKinds)?, }; let plans: Vec> = dag_asset_plans(&artifact.graph, &policy) .iter() @@ -187,7 +186,7 @@ fn rivers_asset_plans<'py>(dag: &Bound<'py, PyAny>) -> PyResult = dag.py(); let artifact: ArtifactGraph<'py> = artifact_graph(dag, false)?; let policy: AssetProjectionPolicy = AssetProjectionPolicy { - asset_kinds: kind_set(py, (RIVERS_CONSTANTS, "RIVERS_ASSET_NODE_KINDS"))?, + asset_kinds: kind_set(py, ThirdPartyHost::RiversAssetNodeKinds)?, upstream_asset_kinds_only: true, }; let plans: Vec> = dag_asset_plans(&artifact.graph, &policy) @@ -226,10 +225,8 @@ fn dagster_check_plans<'py>( let py: Python<'py> = dag.py(); let artifact: ArtifactGraph<'py> = artifact_graph(dag, true)?; let checked_nodes: Vec> = translated_nodes(&artifact)?; - let scenario: BTreeSet = BTreeSet::from([constant_text( - py, - (DAGSTER_CONSTANTS, "SCENARIO_CHECK_KIND"), - )?]); + let scenario: BTreeSet = + BTreeSet::from([constant_text(py, ThirdPartyHost::ScenarioCheckKind)?]); let filter: CheckKindFilter = CheckKindFilter { excluded: if include_scenarios || only_scenarios { BTreeSet::new() @@ -273,8 +270,7 @@ fn dagster_selected_asset_nodes<'py>( nodes: nodes.iter().map(selection_node).collect::>()?, ..DagProjectionGraph::default() }; - let asset_kinds: BTreeSet = - kind_set(py, (DAGSTER_CONSTANTS, "DAGSTER_ASSET_NODE_KINDS"))?; + let asset_kinds: BTreeSet = kind_set(py, ThirdPartyHost::DagsterAssetNodeKinds)?; let translated: Vec> = dag_selected_asset_nodes(&graph, &selected, &asset_kinds) .iter() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs index 8fcf88a7ab..041d164ad9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs @@ -16,9 +16,10 @@ use sqlbuild_analysis::dag_artifact::models::{ use sqlbuild_analysis::planner_selection::main::unit_test_selector_rejection::unit_test_selector_rejection; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; use crate::bindings::_helpers::planner_selection::carrier::Carrier; -const DAGSTER_EXCEPTIONS_MODULE: &str = "sqlbuild.integrations.dagster.exceptions"; const PATHLIB_MODULE: &str = "pathlib"; const JSON_MODULE: &str = "json"; const ABC_MODULE: &str = "collections.abc"; @@ -99,11 +100,9 @@ fn selection_error(py: Python<'_>, error: DagSelectionError, raw: &str) -> PyErr DagSelectionError::UnitTest(selector) => { carrier.error(unit_test_selector_rejection(&selector)) } - DagSelectionError::Input(message) => { - imported(py, DAGSTER_EXCEPTIONS_MODULE, "DagsterDagInputError") - .and_then(|class| class.call1((message,))) - .map_or_else(|failure| failure, PyErr::from_value) - } + DagSelectionError::Input(message) => host::entry(py, ThirdPartyHost::DagsterDagInputError) + .and_then(|class| class.call1((message,))) + .map_or_else(|failure| failure, PyErr::from_value), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs index a9adb97882..b30bb28e28 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs @@ -5,9 +5,8 @@ use std::collections::HashSet; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -pub(crate) const CONSTANTS_MODULE: &str = "sqlbuild.integrations.dagster.constants"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; /// An asset path as Dagster spells it. pub(crate) type AssetPath = Vec; @@ -61,17 +60,20 @@ pub(crate) fn asset_path_or_empty(node: &Bound<'_, PyAny>) -> PyResult(py: Python<'py>, name: &str) -> PyResult> { - imported(py, CONSTANTS_MODULE, name) +pub(crate) fn constant<'py>(py: Python<'py>, name: ThirdPartyHost) -> PyResult> { + host::entry(py, name) } /// Whether a constant set holds `value`. -pub(crate) fn constant_contains(py: Python<'_>, (name, value): (&str, &str)) -> PyResult { +pub(crate) fn constant_contains( + py: Python<'_>, + (name, value): (ThirdPartyHost, &str), +) -> PyResult { constant(py, name)?.contains(value) } /// `bool(record.get(, True))`. -pub(crate) fn enabled(record: &Bound<'_, PyAny>, field_constant: &str) -> PyResult { +pub(crate) fn enabled(record: &Bound<'_, PyAny>, field_constant: ThirdPartyHost) -> PyResult { let py: Python<'_> = record.py(); let field: Bound<'_, PyAny> = constant(py, field_constant)?; record.call_method1("get", (field, true))?.is_truthy() @@ -79,12 +81,12 @@ pub(crate) fn enabled(record: &Bound<'_, PyAny>, field_constant: &str) -> PyResu /// Whether a translated DAG node is owned by the SQLBuild asset definition. pub(crate) fn asset_enabled(node: &Bound<'_, PyAny>) -> PyResult { - enabled(node, "DAGSTER_ASSET_ENABLED_FIELD") + enabled(node, ThirdPartyHost::DagsterAssetEnabledField) } /// Whether a translated DAG check is owned by the SQLBuild asset definition. pub(crate) fn check_enabled(check: &Bound<'_, PyAny>) -> PyResult { - enabled(check, "DAGSTER_CHECK_ENABLED_FIELD") + enabled(check, ThirdPartyHost::DagsterCheckEnabledField) } /// `{str(node.get("id")): node for node in dag.get("nodes", ())}`, keeping the last node per id. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs index a01d52fa43..ea438177fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs @@ -19,6 +19,7 @@ use crate::bindings::_helpers::dagster_invocation::access::{ constant_contains, get, items, node_with_id, nodes_by_id, path_set, string_tuple, text_of, }; use crate::bindings::_helpers::dagster_invocation::results::{dagster_check_name, sorted_nodes}; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const SELECT_FILE_FLAG: &str = "--select-file"; const EVENT_OUTPUT_FLAG: &str = "--event-output"; @@ -176,7 +177,7 @@ fn scenario_selection<'py>( selected_ids.insert(text_of(&node, "id")?); } } - let scenario_kind: String = constant(dag.py(), "SCENARIO_CHECK_KIND")?.extract()?; + let scenario_kind: String = constant(dag.py(), ThirdPartyHost::ScenarioCheckKind)?.extract()?; let mut selectors: Vec = Vec::new(); for check in items(dag, "checks")? { if !check_enabled(&check)? || text_of(&check, "kind")? != scenario_kind { @@ -220,10 +221,10 @@ fn asset_selection<'py>( if is_scenario_test(args) { return scenario_selection((args, dag), read_selection); } - if !constant_contains(py, ("ASSET_SELECTION_COMMANDS", &args[0]))? { + if !constant_contains(py, (ThirdPartyHost::AssetSelectionCommands, &args[0]))? { return Ok(unselected(args)); } - let explicit: Bound<'py, PyAny> = constant(py, "EXPLICIT_SELECTION_FLAGS")?; + let explicit: Bound<'py, PyAny> = constant(py, ThirdPartyHost::ExplicitSelectionFlags)?; for argument in args { if explicit.contains(argument)? { return Ok(unselected(args)); @@ -246,9 +247,9 @@ fn asset_selection<'py>( let kinds: Bound<'py, PyAny> = constant( py, if args[0] == LOAD_COMMAND { - "LOAD_SELECTABLE_NODE_KINDS" + ThirdPartyHost::LoadSelectableNodeKinds } else { - "DEFAULT_SELECTABLE_NODE_KINDS" + ThirdPartyHost::DefaultSelectableNodeKinds }, )?; let mut selectors: Vec = Vec::new(); @@ -313,7 +314,7 @@ pub(crate) fn dagster_cli_arguments<'py>( let mut json_path: Option> = None; if managed && !json_output_requested(&resolved) - && (constant_contains(py, ("ASSET_SELECTION_COMMANDS", &resolved[0]))? + && (constant_contains(py, (ThirdPartyHost::AssetSelectionCommands, &resolved[0]))? || is_scenario_test(&resolved)) { let path: Bound<'py, PyAny> = temporary_file(py, EXECUTION_JSON_FILE, &[])?; @@ -328,7 +329,7 @@ pub(crate) fn dagster_cli_arguments<'py>( json_path = Some(imported(py, "pathlib", "Path")?.call1((path,))?); } let event_path: Option> = if managed - && constant_contains(py, ("LIVE_EVENT_COMMANDS", &resolved[0]))? + && constant_contains(py, (ThirdPartyHost::LiveEventCommands, &resolved[0]))? && !resolved .iter() .any(|argument| argument == EVENT_OUTPUT_FLAG) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs index 3cf7b1e638..66b048599c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs @@ -11,6 +11,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::dagster_invocation::access::{ constant_contains, get, items, text_of, }; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const LIVE_FAILURE_MESSAGE: &str = "SQLBuild resource attempt failed; see final execution diagnostics"; @@ -94,7 +95,7 @@ pub(crate) fn dagster_failure<'py>( let limits: Bound<'py, PyDict> = PyDict::new(py); for asset in items(&payload, "assets")? { let status: String = text_of(&asset, "status")?; - if constant_contains(py, ("COMPLETED_EXECUTION_STATUSES", &status))? { + if constant_contains(py, (ThirdPartyHost::CompletedExecutionStatuses, &status))? { continue; } let failure: AssetFailure = asset_failure(&asset)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs index 7dbad46a46..add0bd57a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::dagster_invocation::access::{ constant_contains, get, items, merged, node_with_id, nodes_by_id, path_set, string_list, text_of, value_or, }; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const CHECK_KIND_FALLBACK: &str = "check"; const CHECK_NAME_JOIN: &str = "__"; @@ -47,7 +48,7 @@ pub(crate) fn sorted_nodes<'py>(dag: &Bound<'py, PyAny>) -> PyResult) -> PyResult { let py: Python<'_> = check.py(); - let field: Bound<'_, PyAny> = constant(py, "DAGSTER_CHECK_NAME_FIELD")?; + let field: Bound<'_, PyAny> = constant(py, ThirdPartyHost::DagsterCheckNameField)?; let translated: Bound<'_, PyAny> = check.call_method1("get", (field,))?; if !translated.is_none() { return translated.str()?.extract(); @@ -110,7 +111,7 @@ fn metadata_value<'py>(value: &Bound<'py, PyAny>) -> PyResult> /// A record's non-null metadata, without check internals. fn metadata_from_mapping<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); - let excluded: Bound<'py, PyAny> = constant(py, "CHECK_METADATA_EXCLUDED_KEYS")?; + let excluded: Bound<'py, PyAny> = constant(py, ThirdPartyHost::CheckMetadataExcludedKeys)?; let metadata: Bound<'py, PyDict> = PyDict::new(py); for pair in value.call_method0("items")?.try_iter()? { let (key, item): (Bound<'py, PyAny>, Bound<'py, PyAny>) = pair?.extract()?; @@ -164,7 +165,7 @@ fn loader_result<'py>( ) -> PyResult)>> { let py: Python<'py> = dag.py(); let nodes = nodes_by_id(dag)?; - let loader_kind: String = constant(py, "LOADER_NODE_KIND")?.extract()?; + let loader_kind: String = constant(py, ThirdPartyHost::LoaderNodeKind)?.extract()?; let source_id: String = text_of(source_node, "id")?; for edge in items(dag, "edges")? { if text_of(&edge, "to_id")? != source_id { @@ -218,8 +219,8 @@ struct CheckFilter<'a> { fn warn_severity(check: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = check.py(); - let warning: String = constant(py, "WARNING_CHECK_SEVERITY")?.extract()?; - let insufficient: String = constant(py, "INSUFFICIENT_CHECK_STATUS")?.extract()?; + let warning: String = constant(py, ThirdPartyHost::WarningCheckSeverity)?.extract()?; + let insufficient: String = constant(py, ThirdPartyHost::InsufficientCheckStatus)?.extract()?; let status: String = text_of(check, "status")?; Ok(status == insufficient || status == warning || text_of(check, "severity")? == warning) } @@ -345,7 +346,7 @@ fn keep_checks_only<'py>( } fn completed(py: Python<'_>, status: &str) -> PyResult { - constant_contains(py, ("COMPLETED_EXECUTION_STATUSES", status)) + constant_contains(py, (ThirdPartyHost::CompletedExecutionStatuses, status)) } /// Dagster events for an execution JSON document. @@ -375,9 +376,9 @@ pub(crate) fn dagster_payload_results<'py>( let payload: Bound<'py, PyAny> = arguments.value("payload")?; let command: Vec = arguments.value("command")?.extract()?; let selection = SelectionFacts::from_arguments(&arguments)?; - let clone_command: String = constant(py, "CLONE_COMMAND")?.extract()?; - let success: String = constant(py, "SUCCESS_EXECUTION_STATUS")?.extract()?; - let source_kind: String = constant(py, "SOURCE_NODE_KIND")?.extract()?; + let clone_command: String = constant(py, ThirdPartyHost::CloneCommand)?.extract()?; + let success: String = constant(py, ThirdPartyHost::SuccessExecutionStatus)?.extract()?; + let source_kind: String = constant(py, ThirdPartyHost::SourceNodeKind)?.extract()?; let is_clone: bool = text_of(&payload, "command")? == clone_command; let selected_paths: HashSet = if is_clone { HashSet::new() @@ -502,8 +503,8 @@ pub(crate) fn dagster_integration_results<'py>( let emitted_asset_paths: Bound<'py, PyAny> = arguments.value("emitted_asset_paths")?; let selection = SelectionFacts::from_arguments(&arguments)?; let nodes = nodes_by_id(&dag)?; - let source_kind: String = constant(py, "SOURCE_NODE_KIND")?.extract()?; - let clone_command: String = constant(py, "CLONE_COMMAND")?.extract()?; + let source_kind: String = constant(py, ThirdPartyHost::SourceNodeKind)?.extract()?; + let clone_command: String = constant(py, ThirdPartyHost::CloneCommand)?.extract()?; let envelope_command: String = envelope.getattr("command")?.str()?.extract()?; let mut plans: Vec> = Vec::new(); let asset: Bound<'py, PyAny> = envelope.getattr("asset")?; @@ -576,7 +577,10 @@ pub(crate) fn dagster_integration_results<'py>( )?); } } - let standalone: bool = constant_contains(py, ("STANDALONE_CHECK_COMMANDS", &envelope_command))?; + let standalone: bool = constant_contains( + py, + (ThirdPartyHost::StandaloneCheckCommands, &envelope_command), + )?; let emitted: HashSet = path_set(&emitted_asset_paths)?; let filter = CheckFilter { selection: &selection, @@ -617,7 +621,7 @@ pub(crate) fn dagster_selected_asset_results<'py>( ) -> PyResult>> { let py: Python<'py> = dag.py(); let selected: HashSet = path_set(&selected_asset_paths)?; - let materializable: Bound<'py, PyAny> = constant(py, "MATERIALIZABLE_NODE_KINDS")?; + let materializable: Bound<'py, PyAny> = constant(py, ThirdPartyHost::MaterializableNodeKinds)?; let mut plans: Vec> = Vec::new(); for node in sorted_nodes(&dag)? { if !selected.is_empty() && !selected.contains(&asset_path(&node)?) { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs index 671f34c8e0..c911d37cec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs @@ -11,7 +11,9 @@ use sqlbuild_core::dagster_invocation::main::fallback_asset_description::fallbac use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::dagster_invocation::access::{CONSTANTS_MODULE, get, value_or}; +use crate::bindings::_helpers::dagster_invocation::access::{get, value_or}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const ABC_MODULE: &str = "collections.abc"; const BUILTINS_MODULE: &str = "builtins"; @@ -55,8 +57,7 @@ fn abc_instance(value: &Bound<'_, PyAny>, class: &str) -> PyResult { /// A source node's non-empty, non-text `meta.dagster.asset_key` sequence. fn authored_asset_key<'py>(node: &Bound<'py, PyAny>) -> PyResult>> { - let source_kind: String = - imported(node.py(), CONSTANTS_MODULE, "SOURCE_NODE_KIND")?.extract()?; + let source_kind: String = host::entry(node.py(), ThirdPartyHost::SourceNodeKind)?.extract()?; if text(&get(node, "kind")?)? != source_kind { return Ok(None); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs index 6d67f1d171..eabf813bc3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs @@ -11,14 +11,12 @@ use sqlbuild_core::dbt_command::main::forwarded_dbt_args::forwarded_dbt_args; use sqlbuild_core::dbt_command::main::render_dbt_auto_init::render_dbt_auto_init; use sqlbuild_core::dbt_command::models::ForwardedArg; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; -const PROGRESS_REPORTER_MODULE: &str = "sqlbuild._native"; fn project_config_found(directory: &Bound<'_, PyAny>) -> PyResult { let name: Bound<'_, PyAny> = @@ -82,14 +80,14 @@ pub(crate) fn ensure_twin_project<'py>( !no_color && !args.iter().any(|arg| arg == JSON_OPTION) && color_enabled(py)?; let progress_stream: Bound<'py, PyAny> = stream(py, "stderr")?; let progress: Bound<'py, PyAny> = call_with( - &imported(py, PROGRESS_REPORTER_MODULE, "DbtInitProgressReporter")?, + ®istry::native(py, NativeAttr::DbtInitProgressReporter)?, &[ ("stream", progress_stream.clone()), ("use_color", flag(py, use_color)), ], )?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitProgressCallbacks")?, + ®istry::class(py, RecordId::DbtInitProgressCallbacks)?, &[ ("start", progress.getattr("start")?), ("complete", progress.getattr("complete")?), @@ -105,7 +103,7 @@ pub(crate) fn ensure_twin_project<'py>( None => none.clone(), }; let request: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitRequest")?, + ®istry::class(py, RecordId::DbtInitRequest)?, &[ ("cwd", effective_dir), ("dbt_project_dir", dbt_project_dir), @@ -120,7 +118,7 @@ pub(crate) fn ensure_twin_project<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_dbt_init_project")?, + ®istry::native(py, NativeAttr::BuildDbtInitProject)?, &[("request", request)], )?; let project_file: String = result.getattr("project_file")?.str()?.to_string(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs index 7f1200a679..0e86d93fec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs @@ -6,18 +6,14 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::dbt_command::main::dbt_output_options::dbt_output_options; use sqlbuild_core::dbt_command::models::DbtOutputOptions; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_command::auto_init::{args_tuple, ensure_twin_project}; use crate::bindings::_helpers::dbt_command::debug::run_dbt_debug; use crate::bindings::_helpers::dbt_command::support::{color_enabled, effective_dir, flag, stream}; use crate::bindings::_helpers::dbt_interop::runtime_config::validate_dbt_execution_args; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PLAN_MODULE: &str = "sqlbuild._native"; -const EXECUTE_MODULE: &str = "sqlbuild._native"; -const PRESENTATION_MODELS: &str = "sqlbuild._native"; const PLAN_COMMAND: &str = "plan"; const DEBUG_COMMAND: &str = "debug"; const BOUNDED_SECTION_ENTRIES: i64 = 10; @@ -41,7 +37,7 @@ impl<'py> Progress<'py> { }, )?; let reporter: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", flag(py, use_color)), @@ -70,7 +66,7 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> progress.blank_line()?; } let plan: Bound<'_, PyAny> = call_with( - &imported(py, PLAN_MODULE, "plan_dbt_interop_from_project")?, + ®istry::native(py, NativeAttr::PlanDbtInteropFromProject)?, &[ ("project_dir", project_dir.clone()), ("args", args_tuple(py, &options.routed_args)?), @@ -80,7 +76,7 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> ], )?; let display: Bound<'_, PyAny> = call_with( - &imported(py, PRESENTATION_MODELS, "DisplayOptions")?, + ®istry::class(py, RecordId::DisplayOptions)?, &[( "max_entries_per_section", if options.verbose { @@ -91,7 +87,7 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> )], )?; let rendered: String = call_with( - &imported(py, NATIVE_MODULE, "render_dbt_interop_plan")?, + ®istry::native(py, NativeAttr::RenderDbtInteropPlan)?, &[ ("plan", plan), ("json_output", flag(py, options.json_output)), @@ -116,7 +112,7 @@ fn run_execution( let progress: Progress<'_> = Progress::new(py, &options, no_color)?; progress.blank_line()?; let request: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "DbtInteropExecutionRequest")?, + ®istry::class(py, RecordId::DbtInteropExecutionRequest)?, &[ ("command", command.clone()), ("project_dir", project_dir.clone()), @@ -129,7 +125,7 @@ fn run_execution( ("json_output", flag(py, options.json_output)), ], )?; - imported(py, EXECUTE_MODULE, "execute_dbt_interop_from_project")? + registry::native(py, NativeAttr::ExecuteDbtInteropFromProject)? .call1((request,))? .extract() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs index 5146169ccc..3c63ed14d7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs @@ -3,12 +3,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use sqlbuild_core::dbt_command::constants::NO_CONNECTION_OPTION; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_command::auto_init::args_tuple; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, status, stream}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DEBUG_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// Run `body` under a stderr status line that is always closed; returns its exit code. fn with_status( @@ -35,7 +34,7 @@ pub(crate) fn run_dbt_debug( let use_color: bool = !no_color && color_enabled(py)?; let dbt_code: i64 = with_status(py, ("Running dbt debug...", use_color), || { call_with( - &imported(py, DEBUG_MODULE, "debug_dbt_from_project")?, + ®istry::native(py, NativeAttr::DebugDbtFromProject)?, &[ ("project_dir", project_dir.clone()), ("args", args_tuple(py, args)?), @@ -49,7 +48,7 @@ pub(crate) fn run_dbt_debug( let sqlbuild_code: i64 = with_status(py, ("Running SQLBuild diagnostics...", use_color), || { call_with( - &imported(py, NATIVE_MODULE, "run_debug_command")?, + ®istry::native(py, NativeAttr::RunDebugCommand)?, &[ ("project_dir", project_dir.clone()), ("no_color", flag(py, no_color)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs index 5ead8833fe..ed3ffd5488 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs @@ -13,18 +13,14 @@ use sqlbuild_core::dbt_command::models::DbtInitSummary; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const PROGRESS_REPORTER_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; fn missing_project(py: Python<'_>) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", "C242")?; - imported(py, ERRORS_MODULE, "CliUserError")?.call( + registry::error(py, ErrorId::CliUser)?.call( ("sqb dbt init requires --project-dir pointing at a dbt project",), Some(&options), ) @@ -84,21 +80,21 @@ fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< let project_dir: Bound<'_, PyAny> = dbt_project_dir(request)?; let use_color: bool = color_enabled(py)?; let progress: Bound<'_, PyAny> = call_with( - &imported(py, PROGRESS_REPORTER_MODULE, "DbtInitProgressReporter")?, + ®istry::native(py, NativeAttr::DbtInitProgressReporter)?, &[ ("stream", stream(py, "stdout")?), ("use_color", flag(py, use_color)), ], )?; let callbacks: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitProgressCallbacks")?, + ®istry::class(py, RecordId::DbtInitProgressCallbacks)?, &[ ("start", progress.getattr("start")?), ("complete", progress.getattr("complete")?), ], )?; let init_request: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitRequest")?, + ®istry::class(py, RecordId::DbtInitRequest)?, &[ ("cwd", request.getattr("cwd")?), ("dbt_project_dir", project_dir), @@ -119,11 +115,11 @@ fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< ], )?; call_with( - &imported(py, NATIVE_MODULE, "validate_dbt_init_project")?, + ®istry::native(py, NativeAttr::ValidateDbtInitProject)?, &[("request", init_request.clone())], )?; let result: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_dbt_init_project")?, + ®istry::native(py, NativeAttr::BuildDbtInitProject)?, &[("request", init_request)], )?; let style: CliStyle = CliStyle { use_color }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs index da84c3fb43..435a07c977 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs @@ -20,11 +20,10 @@ use crate::bindings::_helpers::dbt_interop::runner::launch_os_error; use crate::bindings::types::CompilerDetach; use sqlbuild_config::dbt_profile::models::DbtProjectMetadata; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DBT_EXCEPTIONS: &str = "sqlbuild.integrations.dbt.exceptions"; -const PROFILE_RENDER_MODULE: &str = "sqlbuild.integrations.dbt._helpers.profile.render"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME as PROJECT_FILE; const DEFAULT_OUTPUT_DIR: &str = "sqlbuild_project"; const PROFILES_FILE: &str = "profiles.yml"; @@ -82,11 +81,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported( - py, - PROFILE_RENDER_MODULE, - "render_selected_dbt_profile_output", - )?, + &host::entry(py, ThirdPartyHost::RenderSelectedDbtProfileOutput)?, &[ ("selected", selected.clone()), ("project_dir", paths.dbt_project_dir.clone()), @@ -94,7 +89,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "normalize_dbt_profile_connection")?, + ®istry::native(py, NativeAttr::NormalizeDbtProfileConnection)?, &[("resolved", resolved_output)], )?; let target_path: Bound<'py, PyAny> = resolved( @@ -104,7 +99,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = selected.getattr("target_name")?; let toml: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_dbt_init_project_toml")?, + ®istry::native(py, NativeAttr::BuildDbtInitProjectToml)?, &[ ("project_name", project_name.clone().into_bound_py_any(py)?), ("adapter", normalized.getattr("adapter")?), @@ -148,7 +143,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult, error: &PyErr) -> bool { } fn profile_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, DBT_EXCEPTIONS, "DbtProfileError").and_then(|class| class.call1((message,))) + match host::entry(py, ThirdPartyHost::DbtProfileError).and_then(|class| class.call1((message,))) { Ok(instance) => PyErr::from_value(instance), Err(error) => error, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs index 241b31a3cb..537bf6b09d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs @@ -5,11 +5,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyString, PyTuple}; use sqlbuild_core::dbt_events::models::{DbtNodeMessage, DbtNodeResult}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; fn optional<'py, T: pyo3::IntoPyObject<'py>>( py: Python<'py>, @@ -34,7 +32,7 @@ pub(crate) fn python_result<'py>( py: Python<'py>, result: &DbtNodeResult, ) -> PyResult> { - let message_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS, "DbtNodeMessage")?; + let message_class: Bound<'py, PyAny> = registry::class(py, RecordId::DbtNodeMessage)?; let messages: Vec> = result .messages .iter() @@ -49,7 +47,7 @@ pub(crate) fn python_result<'py>( }) .collect::>()?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtNodeExecutionResult")?, + ®istry::class(py, RecordId::DbtNodeExecutionResult)?, &[ ("unique_id", text(py, &result.unique_id)), ("resource_type", text(py, &result.resource_type)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs index e8af0fab78..7584104b63 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs @@ -1,6 +1,5 @@ //! dbt argv building, executable choice, `dbt ls` output, `sqb dbt` argument routing and config. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -22,10 +21,9 @@ use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const DBT_TYPES: &str = "sqlbuild._native"; -const DBT_EXCEPTIONS: &str = "sqlbuild.integrations.dbt.exceptions"; const DBT_EXECUTABLE_ENV_VAR: &str = "DBT_EXECUTABLE"; const COMMAND_ARGV_OPTIONS: (&[&str], &[&str]) = (&["dbt_executable", "command", "options"], &["args"]); @@ -225,7 +223,7 @@ fn parse_dbt_ls_json_lines<'py>(py: Python<'py>, stdout: &str) -> PyResult(py: Python<'py>, stdout: &str) -> PyResult> { - let node_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS, "DbtLsNode")?; + let node_class: Bound<'py, PyAny> = registry::class(py, RecordId::DbtLsNode)?; let mut nodes: Vec> = Vec::new(); for line in python_splitlines(stdout) { let stripped: &str = python_strip(line); @@ -242,7 +240,7 @@ pub(crate) fn ls_nodes<'py>(py: Python<'py>, stdout: &str) -> PyResult, class_name: &str, error: DbtCliError) -> PyErr { +pub(crate) fn raised(py: Python<'_>, class_name: ErrorId, error: DbtCliError) -> PyErr { let created = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); if let Some(code) = error.code { @@ -251,8 +249,8 @@ pub(crate) fn raised(py: Python<'_>, class_name: &str, error: DbtCliError) -> Py if let Some(help) = error.help { keywords.set_item("help", help)?; } - let instance: Bound<'_, PyAny> = shape_class_at(py, DBT_EXCEPTIONS, class_name)? - .call((error.message,), Some(&keywords))?; + let instance: Bound<'_, PyAny> = + registry::error(py, class_name)?.call((error.message,), Some(&keywords))?; Ok(PyErr::from_value(instance)) }; created().unwrap_or_else(|failure| failure) @@ -299,11 +297,11 @@ fn route_dbt_interop_args<'py>( let command: String = command.str()?.extract()?; let flags: DbtParsedFlags = parsed_flags(parsed)?; let (kind, routed): (DbtInteropCommandKind, DbtRoutedArgs) = - route(&command, &flags).map_err(|error| raised(py, "DbtInteropArgumentError", error))?; + route(&command, &flags).map_err(|error| raised(py, ErrorId::DbtInteropArgument, error))?; let command: Bound<'py, PyAny> = - imported(py, DBT_TYPES, "DbtInteropCommand")?.call1((kind.name(),))?; + registry::class(py, RecordId::DbtInteropCommand)?.call1((kind.name(),))?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropRoutedArgs")?, + ®istry::class(py, RecordId::DbtInteropRoutedArgs)?, &[ ("command", command), ("select", PyTuple::new(py, flags.select)?.into_any()), @@ -378,13 +376,13 @@ fn resolve_dbt_config<'py>( )?; let choice: DbtConfigChoice = choose_dbt_config(sources, arguments.flag("require_project_dir")?) - .map_err(|error| raised(py, "DbtInteropConfigError", error))?; + .map_err(|error| raised(py, ErrorId::DbtInteropConfig, error))?; let target: Bound<'py, PyAny> = choice.target.map_or_else( || py.None().into_bound(py), |target| PyString::new(py, &target).into_any(), ); call_with( - &imported(py, NATIVE_RECORDS, "ResolvedDbtConfig")?, + ®istry::class(py, RecordId::ResolvedDbtConfig)?, &[ ( "project_dir", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs index 911be9bf38..fad85edd01 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs @@ -27,9 +27,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, use crate::bindings::_helpers::dbt_interop::runner::{ dbt_command_lifecycle, launch_failure, launch_os_error, report_exit, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_MODULE: &str = "sqlbuild._native"; const STREAM_OPTIONS: (&[&str], &[&str]) = ( &["argv", "cwd", "stream", "use_color", "target_path"], &[ @@ -96,7 +97,7 @@ fn execute_dbt_json_event_stream<'py>( arguments.value("detail_by_unique_id")?, )?; keywords.set_item("use_color", output.use_color)?; - imported(py, NATIVE_MODULE, "DbtEventStream")?.call((), Some(&keywords))? + registry::native(py, NativeAttr::DbtEventStream)?.call((), Some(&keywords))? }; let argv: Vec = arguments.value("argv")?.extract()?; let cwd: Option = arguments.value("cwd")?.extract()?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs index c29ad70a61..61ee6fb002 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs @@ -19,16 +19,11 @@ use sqlbuild_core::dbt_graph::models::{ }; use sqlbuild_core::dbt_graph::types::DbtSelectorResolver; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::manifest::resolve_sqlbuild_model_dbt_refs; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const SELECTOR_EXPANSION_MODULE: &str = "sqlbuild._native"; -const MODEL_SELECTORS_MODULE: &str = "sqlbuild._native"; const MODEL_RESOURCE_TYPE: &str = "model"; const SELECTION_OPTIONS: (&[&str], &[&str]) = ( &["project", "graph", "select"], @@ -189,12 +184,8 @@ pub(crate) struct PlannerResolver<'py> { pub(crate) fn planner_resolver(project: Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = project.py(); Ok(PlannerResolver { - split: imported(py, SELECTOR_EXPANSION_MODULE, "split_selector_expansion")?, - resolve: imported( - py, - MODEL_SELECTORS_MODULE, - "resolve_sqlbuild_model_selector_names", - )?, + split: registry::native(py, NativeAttr::SplitSelectorExpansion)?, + resolve: registry::native(py, NativeAttr::ResolveSqlbuildModelSelectorNames)?, project, }) } @@ -232,9 +223,8 @@ fn selection_error(py: Python<'_>, error: DbtSelectionError) -> PyErr { let raised = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", PATH_SELECTOR_ERROR_CODE)?; - let error: Bound<'_, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DbtInteropArgumentError")? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::DbtInteropArgument)? + .call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|failure| failure) @@ -277,7 +267,7 @@ fn selection_result<'py>(py: Python<'py>, selection: DbtSelection) -> PyResult>()?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropSelectionResult")?, + ®istry::class(py, RecordId::DbtInteropSelectionResult)?, &[ ( "sqlbuild_model_names", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs index da8afaf6f7..666f734777 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs @@ -22,9 +22,9 @@ use sqlbuild_config::dbt_manifest::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const DBT_REF_KIND: &str = "dbt_ref"; const RELATION_OPTIONS: (&[&str], &[&str]) = (&["manifest", "ref_kind", "ref_name", "ref_package"], &[]); @@ -34,8 +34,8 @@ fn raised(py: Python<'_>, error: ManifestError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", error.code)?; keywords.set_item("help", error.help)?; - let instance: Bound<'_, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call((error.message,), Some(&keywords))?; + let instance: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call((error.message,), Some(&keywords))?; Ok(PyErr::from_value(instance)) }; created().unwrap_or_else(|failure| failure) @@ -139,7 +139,7 @@ fn model_object<'py>( ) -> PyResult> { let py: Python<'py> = raw.py(); call_with( - &imported(py, NATIVE_RECORDS, "DbtManifestModel")?, + ®istry::class(py, RecordId::DbtManifestModel)?, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &model.package_name)), @@ -170,7 +170,7 @@ fn seed_object<'py>( ) -> PyResult> { let py: Python<'py> = raw.py(); call_with( - &imported(py, NATIVE_RECORDS, "DbtManifestSeed")?, + ®istry::class(py, RecordId::DbtManifestSeed)?, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &seed.package_name)), @@ -191,7 +191,7 @@ fn source_object<'py>( ) -> PyResult> { let py: Python<'py> = raw.py(); call_with( - &imported(py, NATIVE_RECORDS, "DbtManifestSource")?, + ®istry::class(py, RecordId::DbtManifestSource)?, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &source.package_name)), @@ -337,7 +337,7 @@ fn manifest_index<'py>(raw_data: &Bound<'py, PyAny>) -> PyResult( || py.None().into_bound(py), |detail| PyString::new(py, &detail).into_any(), ); - let error: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "DbtInteropRuntimeError")? + let error: Bound<'py, PyAny> = registry::error(py, ErrorId::DbtInteropRuntime)? .call(("dbt ls failed",), Some(&keywords(py, &[("help", help)])?))?; Err(PyErr::from_value(error)) } @@ -86,7 +87,7 @@ fn command_kind(command: &Bound<'_, PyAny>) -> PyResult { "debug" => Ok(DbtInteropCommandKind::Debug), _ => Err(raised( command.py(), - "DbtInteropArgumentError", + ErrorId::DbtInteropArgument, DbtCliError { message: format!("Unsupported sqb dbt command: {value}"), ..DbtCliError::default() @@ -141,7 +142,7 @@ fn plan_dbt_interop_command<'py>( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "plan_dbt_interop_command", options, PLAN_OPTIONS)?; let command: Bound<'py, PyAny> = - imported(py, DBT_TYPES, "DbtInteropCommand")?.call1((arguments.value("command")?,))?; + registry::class(py, RecordId::DbtInteropCommand)?.call1((arguments.value("command")?,))?; let command_args: Bound<'py, PyAny> = arguments.value("command_args")?; let (select, exclude) = ( command_args.getattr("select")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs index d22fbee4a5..0045d597c7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs @@ -9,15 +9,12 @@ use sqlbuild_core::dbt_cli::main::dbt_debug_args::dbt_debug_args; use std::time::Instant; use crate::bindings::_helpers::build_command::support::{boolean, none, text}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; use crate::bindings::_helpers::dbt_interop::runtime_config::config_overrides; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; const PLAN_OPTIONS: (&[&str], &[&str]) = ( &["project_dir", "args"], &[ @@ -37,10 +34,10 @@ const DEBUG_OPTIONS: (&[&str], &[&str]) = ( fn native<'py>( py: Python<'py>, - name: &str, + name: NativeAttr, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, "sqlbuild._native", name)?, pairs) + call_with(®istry::native(py, name)?, pairs) } fn prepare<'py>( @@ -48,10 +45,10 @@ fn prepare<'py>( ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py = request.py(); let invocation = - imported(py, "sqlbuild._native", "resolve_dbt_execution_invocation")?.call1((request,))?; + registry::native(py, NativeAttr::ResolveDbtExecutionInvocation)?.call1((request,))?; let manifest = native( py, - "load_compiled_dbt_manifest", + NativeAttr::LoadCompiledDbtManifest, &[ ("runner", invocation.getattr("runner")?), ("dbt_options", invocation.getattr("dbt_options")?), @@ -61,7 +58,7 @@ fn prepare<'py>( )?; let compiled = native( py, - "compile_dbt_interop_project", + NativeAttr::CompileDbtInteropProject, &[ ("project_dir", request.getattr("project_dir")?), ( @@ -85,7 +82,7 @@ fn resolve_plan<'py>( ) -> PyResult> { native( request.py(), - "resolve_dbt_interop_plan", + NativeAttr::ResolveDbtInteropPlan, &[ ("command", request.getattr("command")?), ("invocation", invocation.clone()), @@ -105,10 +102,10 @@ fn resolve_plan<'py>( fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult { let py = request.py(); let command = request.getattr("command")?; - let kind = imported(py, "sqlbuild._native", "DbtInteropCommand")?; + let kind = registry::class(py, RecordId::DbtInteropCommand)?; if !command.eq(kind.getattr("RUN")?)? && !command.eq(kind.getattr("BUILD")?)? { return Err(PyErr::from_value( - imported(py, "sqlbuild._native", "DbtInteropArgumentError")?.call1((format!( + registry::error(py, ErrorId::DbtInteropArgument)?.call1((format!( "unsupported dbt interop execution command: {}", command.str()? ),))?, @@ -119,7 +116,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult let plan = resolution.getattr("plan")?; let merged = native( py, - "build_merged_dbt_execution_argv", + NativeAttr::BuildMergedDbtExecutionArgv, &[ ("command", command), ("options", invocation.getattr("dbt_options")?), @@ -132,7 +129,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; native( py, - "write_dbt_execution_plan_text", + NativeAttr::WriteDbtExecutionPlanText, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -142,7 +139,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; let execution = native( py, - "execute_dbt_without_state_tracking", + NativeAttr::ExecuteDbtWithoutStateTracking, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -151,7 +148,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; native( py, - "write_dbt_execution_summary", + NativeAttr::WriteDbtExecutionSummary, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -160,7 +157,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; let failed = native( py, - "build_failed_sqlbuild_model_names", + NativeAttr::BuildFailedSqlbuildModelNames, &[ ("graph", resolution.getattr("graph")?), ("manifest", manifest.clone()), @@ -173,7 +170,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult } let output = native( py, - "resolve_sqlbuild_execution_plan_output", + NativeAttr::ResolveSqlbuildExecutionPlanOutput, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -188,7 +185,13 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult return Ok(code); } Some("No SQLBuild work selected.") - } else if !native(py, "plan_has_executable_work", &[("plan", output.clone())])?.is_truthy()? { + } else if !native( + py, + NativeAttr::PlanHasExecutableWork, + &[("plan", output.clone())], + )? + .is_truthy()? + { Some("Skipping SQLBuild: no executable work selected.") } else { None @@ -196,7 +199,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult if let Some(message) = skip { native( py, - "write_sqlbuild_skip_notice", + NativeAttr::WriteSqlbuildSkipNotice, &[ ("request", request.clone()), ("invocation", invocation), @@ -207,7 +210,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult } let connection = native( py, - "resolve_dbt_connection_config", + NativeAttr::ResolveDbtConnectionConfig, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -216,7 +219,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; let result = native( py, - "run_dbt_sqlbuild_work", + NativeAttr::RunDbtSqlbuildWork, &[ ("request", request.clone()), ("invocation", invocation), @@ -237,11 +240,11 @@ fn plan_dbt_interop_from_project<'py>( ) -> PyResult> { let args = KeywordArguments::read(py, "plan_dbt_interop_from_project", options, PLAN_OPTIONS)?; let request = call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropExecutionRequest")?, + ®istry::class(py, RecordId::DbtInteropExecutionRequest)?, &[ ( "command", - imported(py, "sqlbuild._native", "DbtInteropCommand")?.getattr("PLAN")?, + registry::class(py, RecordId::DbtInteropCommand)?.getattr("PLAN")?, ), ("project_dir", args.value("project_dir")?), ("args", args.value("args")?), @@ -271,7 +274,7 @@ fn plan_dbt_interop_from_project<'py>( } else { native( py, - "ConnectionProgressReporter", + NativeAttr::ConnectionProgressReporter, &[ ("adapter_name", compiled.getattr("adapter_name")?), ("stream", stream), @@ -283,7 +286,7 @@ fn plan_dbt_interop_from_project<'py>( let resolution = resolve_plan(&request, &invocation, &compiled, &manifest)?; let plan = native( py, - "attach_sqlbuild_plan_output", + NativeAttr::AttachSqlbuildPlanOutput, &[ ("plan", resolution.getattr("plan")?), ("project_dir", args.value("project_dir")?), @@ -317,7 +320,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> let args = KeywordArguments::read(py, "debug_dbt_from_project", options, DEBUG_OPTIONS)?; let discovered = native( py, - "discover_project_inputs", + NativeAttr::DiscoverProjectInputs, &[("project_dir", args.value("project_dir")?)], )?; let dbt_args: Vec = args @@ -328,7 +331,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> let overrides = config_overrides(py, &dbt_args)?; let resolved = native( py, - "resolve_dbt_config", + NativeAttr::ResolveDbtConfig, &[ ("project_root", args.value("project_dir")?), ( @@ -344,7 +347,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> ], )?; let dbt_options = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtCliOptions")?, + ®istry::class(py, RecordId::DbtCliOptions)?, &[ ("project_dir", resolved.getattr("project_dir")?), ("profiles_dir", resolved.getattr("profiles_dir")?), @@ -354,7 +357,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> let mut runner = args.value("dbt_runner")?; if !runner.is_truthy()? { runner = call_with( - &imported(py, "sqlbuild._native", "DbtRunner")?, + ®istry::native(py, NativeAttr::DbtRunner)?, &[("dbt_executable", args.value("dbt_executable")?)], )?; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs index 7a5d4ff0ac..f3e96c7711 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs @@ -10,8 +10,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; use crate::bindings::_helpers::dbt_interop::plan_output::{connection_config, connection_hooks}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const OPTIONS: (&[&str], &[&str]) = ( &["request", "invocation"], &[ @@ -31,7 +32,7 @@ fn environment<'py>(args: &KeywordArguments<'py>) -> PyResult> let request = args.value("request")?; let compiled = args.value("compiled")?; call_with( - &imported(request.py(), NATIVE_RECORDS, "DbtPlanEnvironment")?, + ®istry::class(request.py(), RecordId::DbtPlanEnvironment)?, &[ ("project_dir", request.getattr("project_dir")?), ( @@ -90,7 +91,7 @@ fn write_dbt_execution_plan_text( pyo3::types::PyInt::new(py, super::constants::PLAN_DISPLAY_LIMIT).into_any() }; let rendered = call_with( - &imported(py, "sqlbuild._native", "render_dbt_interop_plan")?, + ®istry::native(py, NativeAttr::RenderDbtInteropPlan)?, &[ ("plan", display), ("json_output", boolean(py, false)), @@ -98,7 +99,7 @@ fn write_dbt_execution_plan_text( ( "display_options", call_with( - &imported(py, "sqlbuild._native", "DisplayOptions")?, + ®istry::class(py, RecordId::DisplayOptions)?, &[("max_entries_per_section", limit)], )?, ), @@ -121,7 +122,7 @@ fn execute_dbt_without_state_tracking<'py>( let request = args.value("request")?; let invocation = args.value("invocation")?; call_with( - &imported(py, "sqlbuild._native", "execute_dbt_commands")?, + ®istry::native(py, NativeAttr::ExecuteDbtCommands)?, &[ ("runner", invocation.getattr("runner")?), ("options", invocation.getattr("dbt_options")?), @@ -144,11 +145,7 @@ fn execute_dbt_without_state_tracking<'py>( fn write_dbt_execution_summary(py: Python<'_>, options: Option>) -> PyResult<()> { let args = KeywordArguments::read(py, "write_dbt_execution_summary", options, OPTIONS)?; let footer = call_with( - &imported( - py, - "sqlbuild._native", - "render_dbt_execution_summary_footer", - )?, + ®istry::native(py, NativeAttr::RenderDbtExecutionSummaryFooter)?, &[ ( "node_results", @@ -191,7 +188,7 @@ fn resolve_sqlbuild_execution_plan_output<'py>( let stream = invocation.getattr("output_stream")?; emit(&stream, "\n")?; let reporter = call_with( - &imported(py, "sqlbuild._native", "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ( "adapter_name", @@ -203,7 +200,7 @@ fn resolve_sqlbuild_execution_plan_output<'py>( ], )?; let request = call_with( - &imported(py, NATIVE_RECORDS, "DbtSqlbuildPlanRequest")?, + ®istry::class(py, RecordId::DbtSqlbuildPlanRequest)?, &[ ("selected_model_names", selected), ( @@ -217,7 +214,7 @@ fn resolve_sqlbuild_execution_plan_output<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "build_sqlbuild_plan_output")?, + ®istry::native(py, NativeAttr::BuildSqlbuildPlanOutput)?, &[ ("environment", environment(&args)?), ("request", request), @@ -232,10 +229,10 @@ fn execution_limits<'py>( ) -> PyResult> { let py = discovered.py(); if target.is_none() { - return imported(py, "sqlbuild._native", "ExecutionLimitsConfig")?.call0(); + return registry::class(py, RecordId::ExecutionLimitsConfig)?.call0(); } call_with( - &imported(py, "sqlbuild._native", "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -259,7 +256,7 @@ fn run_dbt_sqlbuild_work<'py>( let project = compiled.getattr("project")?; let discovered = invocation.getattr("discovered_inputs")?; let context = call_with( - &imported(py, "sqlbuild._native", "DbtSqlbuildWorkContext")?, + ®istry::class(py, RecordId::DbtSqlbuildWorkContext)?, &[ ("plan_output", args.value("plan_output")?), ("connection_config", args.value("connection_config")?), @@ -278,7 +275,7 @@ fn run_dbt_sqlbuild_work<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "execute_dbt_sqlbuild_work")?, + ®istry::native(py, NativeAttr::ExecuteDbtSqlbuildWork)?, &[ ("context", context), ("command", request.getattr("command")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs index f77d959915..30c8a18552 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs @@ -1,8 +1,6 @@ //! Discover, compile and resolve the combined dbt pipeline through its existing boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -12,10 +10,11 @@ use crate::bindings::_helpers::dbt_interop::reference_resolver::DbtCompileRefere use crate::bindings::_helpers::dbt_interop::runtime_config::{ interop_adapter, manifest_path, parse_dbt_execution_args, plan_options, vars_mapping, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::{pyfunction, wrap_pyfunction}; use std::time::Instant; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const COMPILE_OPTIONS: (&[&str], &[&str]) = ( &[ "project_dir", @@ -65,7 +64,7 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let py = request.py(); let mut executable = request.getattr("dbt_executable")?; if !executable.is_truthy()? { - executable = imported(py, "sqlbuild._native", "resolve_dbt_executable")?.call0()?; + executable = registry::native(py, NativeAttr::ResolveDbtExecutable)?.call0()?; } let mut stream = request.getattr("progress_stream")?; if !stream.is_truthy()? { @@ -74,12 +73,12 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let command = request.getattr("command")?; let parsed = parse_dbt_execution_args(&command, &request.getattr("args")?)?; let routed = call_with( - &imported(py, "sqlbuild._native", "route_dbt_interop_args")?, + ®istry::native(py, NativeAttr::RouteDbtInteropArgs)?, &[("command", command), ("parsed", parsed)], )?; let project_dir = request.getattr("project_dir")?; let discovered = call_with( - &imported(py, "sqlbuild._native", "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let dbt_args: Vec = routed @@ -97,13 +96,13 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let mut runner = request.getattr("dbt_runner")?; if !runner.is_truthy()? { runner = call_with( - &imported(py, "sqlbuild._native", "DbtRunner")?, + ®istry::native(py, NativeAttr::DbtRunner)?, &[("dbt_executable", executable.clone())], )?; } let dbt_stream = request.getattr("dbt_stdout_stream")?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropInvocation")?, + ®istry::class(py, RecordId::DbtInteropInvocation)?, &[ ("dbt_executable", executable), ("output_stream", stream.clone()), @@ -144,8 +143,8 @@ fn load_compiled_dbt_manifest<'py>( ], )?; if result.getattr("returncode")?.extract::()? != 0 { - let detail = imported(py, "sqlbuild._native", "dbt_failure_detail")?.call1((result,))?; - let error = imported(py, "sqlbuild._native", "DbtInteropRuntimeError")?.call( + let detail = registry::native(py, NativeAttr::DbtFailureDetail)?.call1((result,))?; + let error = registry::error(py, ErrorId::DbtInteropRuntime)?.call( ("dbt compile failed",), Some( &crate::bindings::_helpers::compile_driver::python_calls::keywords( @@ -161,7 +160,7 @@ fn load_compiled_dbt_manifest<'py>( progress(&callback, "Loading dbt manifest...")?; let path = manifest_path(&args.value("dbt_options")?)?; let manifest = call_with( - &imported(py, "sqlbuild._native", "load_dbt_manifest_index")?, + ®istry::native(py, NativeAttr::LoadDbtManifestIndex)?, &[("manifest_path", path)], )?; completed(&callback, "Loaded dbt manifest.", start)?; @@ -181,7 +180,7 @@ fn compile_dbt_interop_project<'py>( progress(&callback, "Compiling SQLBuild project...")?; let discovered = args.value("discovered_inputs")?; let name = call_with( - &imported(py, "sqlbuild._native", "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -194,7 +193,7 @@ fn compile_dbt_interop_project<'py>( )? .into_any(); let project = call_with( - &imported(py, "sqlbuild._native", "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -205,7 +204,7 @@ fn compile_dbt_interop_project<'py>( )?; completed(&callback, "Compiled SQLBuild project.", start)?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropCompiledProject")?, + ®istry::class(py, RecordId::DbtInteropCompiledProject)?, &[ ("adapter_name", name), ("adapter", adapter), @@ -227,7 +226,7 @@ fn resolve_dbt_interop_plan<'py>( progress(&callback, "Building dbt interop graph...")?; let project = args.value("compiled")?.getattr("project")?; let graph = call_with( - &imported(py, "sqlbuild._native", "build_dbt_combined_graph")?, + ®istry::native(py, NativeAttr::BuildDbtCombinedGraph)?, &[ ("manifest", args.value("manifest")?), ("project", project.clone()), @@ -240,7 +239,7 @@ fn resolve_dbt_interop_plan<'py>( let routed = invocation.getattr("routed")?; let tuple = py.import("builtins")?.getattr("tuple")?; let command_args = call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropCommandArgs")?, + ®istry::class(py, RecordId::DbtInteropCommandArgs)?, &[ ("select", tuple.call1((routed.getattr("select")?,))?), ("exclude", tuple.call1((routed.getattr("exclude")?,))?), @@ -257,7 +256,7 @@ fn resolve_dbt_interop_plan<'py>( ], )?; let plan = call_with( - &imported(py, "sqlbuild._native", "plan_dbt_interop_command")?, + ®istry::native(py, NativeAttr::PlanDbtInteropCommand)?, &[ ("command", args.value("command")?), ("project", project), @@ -270,7 +269,7 @@ fn resolve_dbt_interop_plan<'py>( )?; completed(&callback, "Resolved dbt and SQLBuild selection.", start)?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropPlanResolution")?, + ®istry::class(py, RecordId::DbtInteropPlanResolution)?, &[("graph", graph), ("plan", plan)], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs index c47a3cf46f..90e993a695 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs @@ -8,13 +8,10 @@ use sqlbuild_core::cli_text::main::format_dbt_interop_plan::format_dbt_interop_p use sqlbuild_core::cli_text::main::format_dbt_interop_plan_json::format_dbt_interop_plan_json; use sqlbuild_core::cli_text::models::{DbtInteropPlanText, DbtLsNodeText, DisplayLimit}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DBT_TYPES: &str = "sqlbuild._native"; -const PRESENTATION_MODELS: &str = "sqlbuild._native"; const BUILD_OPTIONS: (&[&str], &[&str]) = ( &[ "command", @@ -123,7 +120,7 @@ fn skip_reason<'py>(py: Python<'py>, has_work: bool, member: &str) -> PyResult( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "build_dbt_interop_plan", options, BUILD_OPTIONS)?; let command: Bound<'py, PyAny> = - imported(py, DBT_TYPES, "DbtInteropCommand")?.call1((arguments.value("command")?,))?; + registry::class(py, RecordId::DbtInteropCommand)?.call1((arguments.value("command")?,))?; let nodes: Bound<'py, PyAny> = tuple_of(arguments.value("dbt_ls_nodes")?)?; let mut selected: Vec = Vec::new(); for node in nodes.try_iter()? { @@ -152,7 +149,7 @@ pub(crate) fn build_dbt_interop_plan<'py>( dbt_has_work || sqlbuild_has_work, ); call_with( - &imported(py, NATIVE_MODULE, "DbtInteropPlan")?, + ®istry::class(py, RecordId::DbtInteropPlan)?, &[ ("command", command), ( @@ -214,7 +211,7 @@ fn render_dbt_interop_plan(py: Python<'_>, options: Option>) - } let mut display: Bound<'_, PyAny> = arguments.value("display_options")?; if !display.is_truthy()? { - display = imported(py, PRESENTATION_MODELS, "DisplayOptions")?.call0()?; + display = registry::class(py, RecordId::DisplayOptions)?.call0()?; } let use_color: bool = arguments.flag("use_color")?; let plan_output: Bound<'_, PyAny> = plan.getattr("sqlbuild_plan_output")?; @@ -224,7 +221,7 @@ fn render_dbt_interop_plan(py: Python<'_>, options: Option>) - } else { Some( call_with( - &imported(py, NATIVE_MODULE, "format_plan")?, + ®istry::native(py, NativeAttr::FormatPlan)?, &[ ("plan", plan_output), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs index 946cd50284..72ad4eeb3e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs @@ -12,12 +12,11 @@ use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hook use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const HOOKS: &str = "sqlbuild.runtime.contracts.models"; const BUILD_OPTIONS: (&[&str], &[&str]) = (&["environment", "request", "hooks"], &[]); const ATTACH_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -38,7 +37,7 @@ pub(crate) fn connection_config<'py>( let py = environment.py(); let discovered = environment.getattr("discovered_inputs")?; let raw = call_with( - &imported(py, "sqlbuild._native", "build_effective_connection_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[("discovered_inputs", discovered.clone())], )?; let raw: Bound<'_, PyDict> = raw.cast_into()?; @@ -56,7 +55,7 @@ pub(crate) fn connection_config<'py>( )?; let config = config.into_any(); call_with( - &imported(py, "sqlbuild._native", "apply_command_warehouse")?, + ®istry::native(py, NativeAttr::ApplyCommandWarehouse)?, &[ ("config", config), ("adapter_name", environment.getattr("adapter_name")?), @@ -100,7 +99,7 @@ pub(crate) fn connection_hooks<'py>( )); } } - call_with(&imported(py, HOOKS, "ConnectionHooks")?, &pairs) + call_with(&host::entry(py, ExecutorHost::ConnectionHooks)?, &pairs) } /// Open through the shared adapter boundary while retaining keyword-only timing callbacks. @@ -122,7 +121,7 @@ fn open_connection<'py>( }, )); } - let relayed = call_with(&imported(py, HOOKS, "ConnectionHooks")?, &pairs)?; + let relayed = call_with(&host::entry(py, ExecutorHost::ConnectionHooks)?, &pairs)?; connection_with_hooks(&environment.getattr("adapter")?, &config, &relayed) } @@ -146,7 +145,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( let planned = (|| { let args = request.getattr("sqlbuild_args")?; let deferral = call_with( - &imported(py, "sqlbuild._native", "resolve_model_deferral_inputs")?, + ®istry::native(py, NativeAttr::ResolveModelDeferralInputs)?, &[ ("project", environment.getattr("project")?), ( @@ -159,7 +158,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ], )?; let cursor = call_with( - &imported(py, PLANNER_MODELS, "CursorOverrides")?, + ®istry::class(py, RecordId::CursorOverrides)?, &[ ("start_ts", flag_value(&args, "--start-cursor-ts")?), ("end_ts", flag_value(&args, "--end-cursor-ts")?), @@ -169,11 +168,11 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( )?; let full_refresh = boolean(py, args.contains("--full-refresh")?); let selection = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[("select", selected.clone())], )?; let overrides = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerOverrides")?, + ®istry::class(py, RecordId::PlannerOverrides)?, &[ ("cursor_overrides", cursor), ("full_refresh", full_refresh.clone()), @@ -185,7 +184,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( )?; let discovered = environment.getattr("discovered_inputs")?; let result = call_with( - &imported(py, "sqlbuild._native", "build_execution_plan")?, + ®istry::native(py, NativeAttr::BuildExecutionPlan)?, &[ ("project", environment.getattr("project")?), ("adapter", environment.getattr("adapter")?), @@ -195,7 +194,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ("deferral", deferral), ( "policies", - imported(py, NATIVE_RECORDS_MODULE, "PlannerPolicies")?.call0()?, + registry::class(py, RecordId::PlannerPolicies)?.call0()?, ), ("on_progress", hooks.getattr("on_progress")?), ("project_config", discovered.getattr("project_config")?), @@ -203,12 +202,9 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ], ); match result { - Err(error) - if error - .is_instance(py, &imported(py, "sqlbuild._native", "PlannerInputError")?) => - { + Err(error) if error.is_instance(py, ®istry::error(py, ErrorId::PlannerInput)?) => { call_with( - &imported(py, "sqlbuild._native", "build_display_only_sqlbuild_plan")?, + ®istry::native(py, NativeAttr::BuildDisplayOnlySqlbuildPlan)?, &[ ("project", environment.getattr("project")?), ("selected_model_names", selected), @@ -236,7 +232,7 @@ fn attach_sqlbuild_plan_output<'py>( let compiled = args.value("compiled")?; let plan = args.value("plan")?; let environment = call_with( - &imported(py, NATIVE_RECORDS, "DbtPlanEnvironment")?, + ®istry::class(py, RecordId::DbtPlanEnvironment)?, &[ ("project_dir", args.value("project_dir")?), ("discovered_inputs", args.value("discovered_inputs")?), @@ -246,7 +242,7 @@ fn attach_sqlbuild_plan_output<'py>( ], )?; let request = call_with( - &imported(py, NATIVE_RECORDS, "DbtSqlbuildPlanRequest")?, + ®istry::class(py, RecordId::DbtSqlbuildPlanRequest)?, &[ ( "selected_model_names", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs index 2e1672a8f4..01dc966401 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs @@ -12,13 +12,12 @@ use sqlbuild_config::dbt_profile::models::{ ConnectionKey, ConnectionSource, DbtInitConfig, NormalizedProfile, ProfileEntry, ProfileValue, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DBT_ERRORS_MODULE: &str = "sqlbuild.integrations.dbt.exceptions"; const INIT_TOML_OPTIONS: (&[&str], &[&str]) = ( &[ "project_name", @@ -35,8 +34,7 @@ const INIT_TOML_OPTIONS: (&[&str], &[&str]) = ( ); pub(crate) fn raised(py: Python<'_>, error: DbtProfileError) -> PyErr { - match imported(py, DBT_ERRORS_MODULE, "DbtProfileError") - .and_then(|class| class.call1((error.0,))) + match host::entry(py, ThirdPartyHost::DbtProfileError).and_then(|class| class.call1((error.0,))) { Ok(instance) => PyErr::from_value(instance), Err(failure) => failure, @@ -83,7 +81,7 @@ fn normalized_connection<'py>( connection.set_item(key, value)?; } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NormalizedDbtProfileConnection")?, + ®istry::class(py, RecordId::NormalizedDbtProfileConnection)?, &[ ("adapter", PyString::new(py, profile.adapter).into_any()), ("connection", connection.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs index 1b632d3c96..28be17377b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs @@ -7,17 +7,16 @@ use pyo3::types::{PyDict, PyDictMethods, PyString}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::text::main::python_strip::python_strip; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::cli::resolved_path; use crate::bindings::_helpers::dbt_interop::profile_load::{ default_profiles_dir, load_dbt_project_metadata, load_selected_dbt_profile_output, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PROFILE_RENDER_MODULE: &str = "sqlbuild.integrations.dbt._helpers.profile.render"; -const DBT_ERRORS_MODULE: &str = "sqlbuild.integrations.dbt.exceptions"; const DBT_PROFILE_SOURCE: &str = "dbt_profile"; const PROFILE_CONNECTION_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -31,8 +30,7 @@ const PROFILE_CONNECTION_OPTIONS: (&[&str], &[&str]) = ( const NON_EMPTY_MESSAGE: &str = "dbt_profile connection values must be non-empty strings"; pub(crate) fn profile_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, DBT_ERRORS_MODULE, "DbtProfileError") - .and_then(|class| class.call1((message,))) + match host::entry(py, ThirdPartyHost::DbtProfileError).and_then(|class| class.call1((message,))) { Ok(instance) => PyErr::from_value(instance), Err(error) => error, @@ -91,11 +89,7 @@ fn profile_connection<'py>( let selected: Bound<'py, PyAny> = load_selected_dbt_profile_output(&profiles_dir, &profile_name, target_name.as_deref())?; let resolved: Bound<'py, PyAny> = call_with( - &imported( - py, - PROFILE_RENDER_MODULE, - "render_selected_dbt_profile_output", - )?, + &host::entry(py, ThirdPartyHost::RenderSelectedDbtProfileOutput)?, &[ ("selected", selected), ("project_dir", dbt_project_dir), @@ -104,7 +98,7 @@ fn profile_connection<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "normalize_dbt_profile_connection")?, + ®istry::native(py, NativeAttr::NormalizeDbtProfileConnection)?, &[("resolved", resolved)], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs index 83a7dc39ea..60090e0373 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs @@ -13,9 +13,11 @@ use sqlbuild_config::dbt_profile::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::dbt_interop::profile::raised; use crate::bindings::_helpers::dbt_interop::profile_connection::profile_error; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const PROFILE_LOAD_MODULE: &str = "sqlbuild.integrations.dbt._helpers.profile.load"; -const DBT_MODELS: &str = "sqlbuild._native"; const DBT_PROJECT_FILE: &str = "dbt_project.yml"; const PROFILES_FILE: &str = "profiles.yml"; const DEFAULT_PROFILES_DIR: &str = "~/.dbt"; @@ -64,7 +66,7 @@ fn yaml_mapping<'py>(file: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, PROFILE_LOAD_MODULE, "load_dbt_yaml")?, + &host::entry(py, ThirdPartyHost::LoadDbtYaml)?, &[("file_path", file.clone())], )?; dbt_yaml_mapping(&display, &yaml_value(Some(&document))?).map_err(|error| raised(py, error))?; @@ -130,7 +132,7 @@ pub(crate) fn load_selected_dbt_profile_output<'py>( let (target, index): (String, usize) = select_dbt_profile_target(&validated, target_name).map_err(|error| raised(py, error))?; call_with( - &imported(py, DBT_MODELS, "SelectedDbtProfileOutput")?, + ®istry::class(py, RecordId::SelectedDbtProfileOutput)?, &[ ("profile_name", PyString::new(py, profile_name).into_any()), ("target_name", PyString::new(py, &target).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs index d7803540f0..6eeea47c5c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs @@ -10,11 +10,11 @@ use std::collections::HashSet; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, within, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const VALIDATE_OPTIONS: (&[&str], &[&str]) = ( &[ "ref_kind", @@ -31,14 +31,13 @@ const SEED_CONFLICT_CODE: &str = "C217"; fn compile_input_error(py: Python<'_>, message: String, code: &str) -> PyErr { let created = || -> PyResult { - let error: Bound<'_, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call( - (message,), - Some(&keywords( - py, - &[("code", PyString::new(py, code).into_any())], - )?), - )?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::CompileInput)?.call( + (message,), + Some(&keywords( + py, + &[("code", PyString::new(py, code).into_any())], + )?), + )?; Ok(PyErr::from_value(error)) }; created().unwrap_or_else(|failure| failure) @@ -107,7 +106,7 @@ impl DbtCompileReferenceResolver { known_model_names: Bound<'_, PyAny>, ) -> Result<(), PyErr> { call_with( - &imported(py, NATIVE_MODULE, "validate_compile_dbt_model_names")?, + ®istry::native(py, NativeAttr::ValidateCompileDbtModelNames)?, &[ ("known_model_names", known_model_names), ("dbt_manifest", self.manifest_or_none(py)), @@ -258,7 +257,7 @@ impl DbtCompileReferenceResolver { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "validate_reference", options, VALIDATE_OPTIONS)?; let reference: Bound<'_, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompileSqlReference")?, + ®istry::class(py, RecordId::CompileSqlReference)?, &[ ("ref_kind", arguments.value("ref_kind")?), ("ref_name", arguments.value("ref_name")?), @@ -266,7 +265,7 @@ impl DbtCompileReferenceResolver { ], )?; call_with( - &imported(py, NATIVE_MODULE, "validate_compile_dbt_model_reference")?, + ®istry::native(py, NativeAttr::ValidateCompileDbtModelReference)?, &[ ("reference", reference), ( @@ -288,7 +287,7 @@ impl DbtCompileReferenceResolver { ref_package: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, NATIVE_MODULE, "resolve_dbt_reference_relation")?, + ®istry::native(py, NativeAttr::ResolveDbtReferenceRelation)?, &[ ("manifest", self.manifest_or_none(py)), ("ref_kind", ref_kind), @@ -310,7 +309,7 @@ pub(crate) fn build_compile_reference_resolver<'py>( return Ok(py.None().into_bound(py)); } let manifest: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_compile_dbt_manifest_index")?, + ®istry::native(py, NativeAttr::BuildCompileDbtManifestIndex)?, &[("manifest_contents", manifest_contents)], )?; Ok(Bound::new( @@ -358,7 +357,7 @@ pub(crate) fn resolve_external_sql_reference_resolver<'py>( py.None().into_bound(py) } else { let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "project").into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs index 2a17ec7c68..1d69871d7c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs @@ -20,11 +20,12 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::dbt_interop::cli::{ argv, ls_nodes, resolve_dbt_executable, sequence, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const LAUNCH_FAILURE: &str = "failed to execute dbt"; const POISONED_CACHE: &str = "dbt ls cache is poisoned"; @@ -123,7 +124,7 @@ impl DbtRunner { let result: Bound<'py, PyAny> = self.run(command, &options.getattr("project_dir")?)?; let stdout: String = result.getattr("stdout")?.extract()?; let listing: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtLsResult")?, + ®istry::class(py, RecordId::DbtLsResult)?, &[ ("nodes", ls_nodes(py, &stdout)?.into_any()), ("command", result), @@ -191,7 +192,7 @@ impl DbtRunner { }); let output: DbtProcessOutput = output.map_err(|error| launch_failure(py, error))?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtCommandResult")?, + ®istry::class(py, RecordId::DbtCommandResult)?, &[ ("argv", command.into_any()), ("returncode", output.returncode.into_pyobject_any(py)?), @@ -219,7 +220,7 @@ pub(crate) fn dbt_command_lifecycle(py: Python<'_>) -> PyResult options.set_item("operation_kind", "subprocess")?; options.set_item("operation_name", "dbt_command")?; options.set_item("auto_fail_base_exceptions", false)?; - imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?.call((), Some(&options)) + host::entry(py, ExecutorHost::OperationLifecycle)?.call((), Some(&options)) } /// Complete a zero exit, or fail with the exit code and any signal number. @@ -282,9 +283,8 @@ pub(crate) fn launch_failure(py: Python<'_>, error: PyErr) -> PyErr { let created = || -> PyResult { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("help", error.value(py).str()?)?; - let instance: Bound<'_, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DbtInteropRuntimeError")? - .call((LAUNCH_FAILURE,), Some(&options))?; + let instance: Bound<'_, PyAny> = registry::error(py, ErrorId::DbtInteropRuntime)? + .call((LAUNCH_FAILURE,), Some(&options))?; let raised: PyErr = PyErr::from_value(instance); raised.set_cause(py, Some(error.clone_ref(py))); Ok(raised) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs index 6236e085bd..d707c38bb0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs @@ -12,16 +12,15 @@ use sqlbuild_core::dbt_cli::errors::DbtCliError; use sqlbuild_core::dbt_cli::main::dbt_flag_value::dbt_flag_value; use sqlbuild_core::dbt_cli::models::DbtParsedFlags; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_interop::cli::{raised, resolved_path}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const BUILTIN_ADAPTERS_MODULE: &str = "sqlbuild.adapter.discovery.main.builtins"; -const PROJECT_ADAPTERS_MODULE: &str = "sqlbuild.adapter.discovery.main.project_adapters"; -const ARGUMENT_ERROR: &str = "DbtInteropArgumentError"; -const CONFIG_ERROR: &str = "DbtInteropConfigError"; +const ARGUMENT_ERROR: ErrorId = ErrorId::DbtInteropArgument; +const CONFIG_ERROR: ErrorId = ErrorId::DbtInteropConfig; const ARGUMENT_CODE: &str = "C238"; const UNRECOGNIZED_HELP: &str = "SQLBuild flags go before `--`; pass raw dbt flags after `--`."; const PARSER_HELP: &str = "Run `-h` to list supported flags; pass raw dbt flags after `--`."; @@ -105,7 +104,7 @@ pub(crate) fn parsed_flags( fn parsed_args_model<'py>(py: Python<'py>, flags: DbtParsedFlags) -> PyResult> { call_with( - &imported(py, NATIVE_MODULE, "DbtInteropParsedArgs")?, + ®istry::class(py, RecordId::DbtInteropParsedArgs)?, &[ ("select", PyTuple::new(py, flags.select)?.into_any()), ("exclude", PyTuple::new(py, flags.exclude)?.into_any()), @@ -185,7 +184,7 @@ pub(crate) fn config_overrides<'py>( ) -> PyResult> { let value = |name: &str| optional_text(py, dbt_flag_value(args, name)); call_with( - &imported(py, NATIVE_MODULE, "DbtCliConfigOverrides")?, + ®istry::class(py, RecordId::DbtCliConfigOverrides)?, &[ ("project_dir", value("--project-dir")), ("profiles_dir", value("--profiles-dir")), @@ -266,7 +265,7 @@ pub(crate) fn plan_options<'py>( let local_config: Bound<'py, PyAny> = discovered_inputs.getattr("local_config")?.getattr("dbt")?; let resolved: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_dbt_config")?, + ®istry::native(py, NativeAttr::ResolveDbtConfig)?, &[ ("project_root", project_dir.clone()), ("config", project_config.clone()), @@ -285,7 +284,7 @@ pub(crate) fn plan_options<'py>( )? }; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtCliOptions")?, + ®istry::class(py, RecordId::DbtCliOptions)?, &[ ("project_dir", resolved.getattr("project_dir")?), ("profiles_dir", resolved.getattr("profiles_dir")?), @@ -321,10 +320,9 @@ pub(crate) fn interop_adapter<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = adapter_name.py(); - let builtin: Bound<'py, PyDict> = - imported(py, BUILTIN_ADAPTERS_MODULE, "builtin_adapter_classes")? - .call0()? - .cast_into()?; + let builtin: Bound<'py, PyDict> = host::entry(py, AdapterHost::BuiltinAdapterClasses)? + .call0()? + .cast_into()?; let mut adapter_class: Option> = None; if !project_dir.is_none() { let reserved: Bound<'py, PyAny> = py @@ -332,7 +330,7 @@ pub(crate) fn interop_adapter<'py>( .getattr("frozenset")? .call1((builtin.clone(),))?; let local: Bound<'py, PyDict> = call_with( - &imported(py, PROJECT_ADAPTERS_MODULE, "discover_project_adapters")?, + &host::entry(py, AdapterHost::DiscoverProjectAdapters)?, &[ ("project_dir", project_dir.clone()), ("reserved_names", reserved), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs index bf09afd152..704d2a8e74 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs @@ -1,9 +1,9 @@ //! Execute dbt through the subprocess boundary and finalize results in artifact order. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::runtime_results::{append_missing, result_ids}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyInt, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -39,14 +39,14 @@ fn expected_total<'py>( ) -> PyResult> { let py = argv.py(); let ls = call_with( - &imported(py, "sqlbuild._native", "dbt_execution_ls_argv")?, + ®istry::native(py, NativeAttr::DbtExecutionLsArgv)?, &[("argv", argv.clone())], )?; if ls.is_none() { return Ok(py.None().into_bound(py)); } let status = call_with( - &imported(py, "sqlbuild._native", "TransientStatusReporter")?, + ®istry::native(py, NativeAttr::TransientStatusReporter)?, &[ ("stream", arguments.value("progress_stream")?), ("use_color", arguments.value("use_color")?), @@ -83,7 +83,7 @@ fn expected_total<'py>( return Ok(py.None().into_bound(py)); } let nodes = call_with( - &imported(py, "sqlbuild._native", "parse_dbt_ls_json_lines")?, + ®istry::native(py, NativeAttr::ParseDbtLsJsonLines)?, &[("stdout", result.getattr("stdout")?)], )?; Ok(PyInt::new(py, nodes.len()?).into_any()) @@ -108,10 +108,10 @@ fn execute_dbt_commands<'py>( let argv = arguments.value("merged_argv")?; let output = arguments.value("progress_stream")?; let style = call_with( - &imported(py, "sqlbuild._native", "CliStyle")?, + ®istry::native(py, NativeAttr::CliStyle)?, &[("use_color", arguments.value("use_color")?)], )?; - let result_type = imported(py, "sqlbuild._native", "DbtCommandExecutionResult")?; + let result_type = registry::class(py, RecordId::DbtCommandExecutionResult)?; if argv.is_none() { let message = arguments.value("skip_message")?; let message = if message.is_none() { @@ -174,7 +174,7 @@ fn execute_dbt_commands<'py>( target = project_dir.call_method1("__truediv__", ("target",))?; } let (returncode, streamed): (Bound<'py, PyAny>, Bound<'py, PyAny>) = call_with( - &imported(py, "sqlbuild._native", "execute_dbt_json_event_stream")?, + ®istry::native(py, NativeAttr::ExecuteDbtJsonEventStream)?, &[ ("argv", argv), ("cwd", project_dir), @@ -211,7 +211,7 @@ fn execute_dbt_commands<'py>( let count = results.len()? - appended.len(); for (offset, result) in appended.into_iter().enumerate() { let rendered = call_with( - &imported(py, "sqlbuild._native", "render_dbt_node_result")?, + ®istry::native(py, NativeAttr::RenderDbtNodeResult)?, &[ ("result", result.clone()), ("use_color", style.getattr("use_color")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs index b49aeecd87..92af81bd67 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs @@ -1,6 +1,8 @@ //! Backfill streamed dbt results using Python's JSON and UTF-8 artifact contract. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyOSError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ @@ -8,10 +10,6 @@ use pyo3::types::{ }; use std::collections::HashSet; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; - pub(crate) fn result_ids(results: &Bound<'_, PyAny>) -> PyResult> { results .try_iter()? @@ -50,7 +48,7 @@ fn messages<'py>(raw: &Bound<'py, PyDict>) -> PyResult> { PyTuple::new( py, [call_with( - &imported(py, NATIVE_RECORDS, "DbtNodeMessage")?, + ®istry::class(py, RecordId::DbtNodeMessage)?, &[ ("level", PyString::new(py, "error").into_any()), ("message", text), @@ -130,7 +128,7 @@ pub(crate) fn append_missing<'py>( py.None().into_bound(py) }; appended.push(call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtNodeExecutionResult")?, + ®istry::class(py, RecordId::DbtNodeExecutionResult)?, &[ ("unique_id", unique_id), ("resource_type", PyString::new(py, resource_type).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs index 2a12498023..ab53114c3d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs @@ -11,9 +11,11 @@ use sqlbuild_core::build_command::constants::{ use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, keyword_relay, none, text, tuple, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; const WORK_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -26,14 +28,12 @@ const WORK_OPTIONS: (&[&str], &[&str]) = ( ], &[], ); -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const NATIVE_RECORDS: &str = "sqlbuild._native"; /// Interop has no allow flags; destructive work uses the ordinary interactive safety gates. #[pyfunction] fn dbt_interop_plan_safety_gates(py: Python<'_>) -> PyResult> { call_with( - &imported(py, NATIVE_RECORDS, "PlanSafetyGates")?, + ®istry::class(py, RecordId::PlanSafetyGates)?, &[ ("allow_missing_migration_origin", boolean(py, false)), ("allow_snapshot_full_refresh", boolean(py, false)), @@ -77,7 +77,7 @@ fn execute_dbt_sqlbuild_work<'py>( let output = context.getattr("output_stream")?; let color = context.getattr("use_color")?; call_with( - &imported(py, "sqlbuild._native", "enforce_plan_safety_policies")?, + ®istry::native(py, NativeAttr::EnforcePlanSafetyPolicies)?, &[ ("plan", plan.clone()), ("gates", dbt_interop_plan_safety_gates(py)?), @@ -91,7 +91,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let callbacks = call_with( - &imported(py, "sqlbuild._native", "BuildProgressCallbacks")?, + ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, &[ ("plan", plan.clone()), ("use_color", color.clone()), @@ -100,7 +100,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let command = args.value("command")?; - let kinds = imported(py, "sqlbuild._native", "DbtInteropCommand")?; + let kinds = registry::class(py, RecordId::DbtInteropCommand)?; let display = if command.eq(kinds.getattr("RUN")?)? { "sqb build --no-tests --no-audits".to_owned() } else { @@ -108,7 +108,7 @@ fn execute_dbt_sqlbuild_work<'py>( }; let concurrency = project.getattr("settings")?.getattr("concurrency")?; let header = call_with( - &imported(py, "sqlbuild._native", "format_execution_header")?, + ®istry::native(py, NativeAttr::FormatExecutionHeader)?, &[ ("command", text(py, &display)), ("target", none(py)), @@ -116,7 +116,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let style = call_with( - &imported(py, "sqlbuild._native", "CliStyle")?, + ®istry::native(py, NativeAttr::CliStyle)?, &[("use_color", color.clone())], )?; emit( @@ -132,7 +132,7 @@ fn execute_dbt_sqlbuild_work<'py>( ), )?; let connection = call_with( - &imported(py, "sqlbuild._native", "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.getattr("adapter_name")?), ("stream", output.clone()), @@ -142,7 +142,7 @@ fn execute_dbt_sqlbuild_work<'py>( )?; let build = command.eq(kinds.getattr("BUILD")?)?; let runtime = call_with( - &imported(py, BUILD_MODELS, "BuildRuntimeParams")?, + &host::entry(py, ExecutorHost::BuildRuntimeParams)?, &[ ("run_id", project.getattr("run_id")?), ("run_tests", boolean(py, build)), @@ -153,7 +153,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let hooks = call_with( - &imported(py, BUILD_MODELS, "BuildCallbacks")?, + &host::entry(py, ExecutorHost::BuildCallbacks)?, &[ ( "on_node_start", @@ -185,11 +185,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let result = call_with( - &imported( - py, - "sqlbuild.executor.pipeline.main.run", - "run_build_pipeline", - )?, + &host::entry(py, ExecutorHost::RunBuildPipeline)?, &[ ("plan", plan.clone()), ("connection_config", context.getattr("connection_config")?), @@ -200,7 +196,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "write_runtime_target")?, + ®istry::native(py, NativeAttr::WriteRuntimeTarget)?, &[ ( "target_dir", @@ -211,7 +207,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let footer = call_with( - &imported(py, "sqlbuild._native", "format_build_footer")?, + ®istry::native(py, NativeAttr::FormatBuildFooter)?, &[ ("result", result.clone()), ("elapsed", callbacks.getattr("elapsed")?), @@ -220,7 +216,7 @@ fn execute_dbt_sqlbuild_work<'py>( )?; emit(&output, &format!("\n{}\n", footer.extract::()?))?; Ok(i64::from(!result.getattr("status")?.eq( - imported(py, "sqlbuild.executor.build.types", "BuildStatus")?.getattr("SUCCESS")?, + host::entry(py, ExecutorHost::BuildStatus)?.getattr("SUCCESS")?, )?)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs b/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs index de3f8f48f1..fd439a288e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs @@ -34,10 +34,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; const DEBUG_OPTIONS: (&[&str], &[&str]) = ( &[], &[ @@ -119,7 +118,7 @@ fn warehouse_checks( selected_target: &context.selected_target, }; let mut checks: Vec = Vec::new(); - for group in imported(py, CONTRACT_TYPES_MODULE, "WarehouseGroup")?.try_iter()? { + for group in registry::class(py, RecordId::WarehouseGroup)?.try_iter()? { let group: Bound<'_, PyAny> = group?; let group_name: String = group.getattr("value")?.str()?.extract()?; if !supports_session_warehouse { @@ -258,12 +257,12 @@ fn build_report( let project_dir_object: Bound<'_, PyAny> = imported(py, "pathlib", "Path")?.call1((project_dir,))?; let discovered_inputs: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir_object.clone())], )?; let files: DebugConfigFiles = debug_config_files(project_dir); let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ( "project_config", @@ -293,7 +292,7 @@ fn build_report( let connection_config: Bound<'_, PyDict> = effective_connection(&context, (&adapter_name, &base_connection))?.cast_into()?; let namespace: Bound<'_, PyTuple> = call_with( - &imported(py, NATIVE_MODULE, "build_effective_target_namespace")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetNamespace)?, &[ ("discovered_inputs", context.discovered_inputs.clone()), ("selected_target", context.selected_target.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs index efe696e343..9e207eb743 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs @@ -24,11 +24,10 @@ use sqlbuild_core::redaction::main::redact_text::redact_text; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::diagnostics::redaction::redacted_value; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const PRIVATE_SQL_MARKER: (&str, &str) = ( - "sqlbuild.diagnostics.constants", - "SQL_PRIVATE_RECORD_MARKER", -); +const PRIVATE_SQL_MARKER: ExecutorHost = ExecutorHost::SqlPrivateRecordMarker; /// `context | fields | identity`, with later mappings overriding earlier keys in place. fn merged<'py>( @@ -315,7 +314,7 @@ fn compute_diagnostic_log_fields<'py>( return Ok(None); } let trusted_private_sql: bool = match values.get_item(SQL_PRIVATE_RECORD_FIELD)? { - Some(marker) => marker.is(&imported(py, PRIVATE_SQL_MARKER.0, PRIVATE_SQL_MARKER.1)?), + Some(marker) => marker.is(&host::entry(py, PRIVATE_SQL_MARKER)?), None => false, }; let mut identities: Vec<(&str, Option)> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs index 1ca3d76d6d..d1455f1450 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs @@ -8,10 +8,11 @@ use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyType}; use pyo3::{pyclass, pymethods}; use crate::bindings::_helpers::compile_driver::python_calls::{context_variable, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DIAGNOSTICS_MODELS_MODULE: &str = "sqlbuild.diagnostics.models"; -const PROCESS_RESOURCES_MODULE: &str = "sqlbuild.diagnostics._helpers.process_resources"; const TRACKER_VARIABLE: &str = "sqlbuild_build_phase_timing_tracker"; const PHASES: [&str; 6] = [ "compile_seconds", @@ -101,7 +102,7 @@ impl BuildPhaseTimingTracker { keywords.set_item(phase, value)?; } keywords.set_item("total_seconds", non_negative(now, self.started_at))?; - imported(py, NATIVE_MODULE, "PartialBuildPhaseTimings")?.call((), Some(&keywords)) + registry::class(py, RecordId::PartialBuildPhaseTimings)?.call((), Some(&keywords)) } /// Return the active build timing tracker. @@ -171,7 +172,7 @@ impl ProcessResourceTracker { let monotonic: Bound<'_, PyAny> = clock(py, monotonic)?; let resource_reader: Bound<'_, PyAny> = match resource_reader { Some(reader) => reader, - None => imported(py, PROCESS_RESOURCES_MODULE, "read_process_resources")?, + None => host::entry(py, ExecutorHost::ReadProcessResources)?, }; let started_at: f64 = monotonic.call0()?.extract()?; let (started_user, started_system, _): (f64, f64, Bound<'_, PyAny>) = @@ -201,7 +202,7 @@ impl ProcessResourceTracker { non_negative(system_cpu, self.started_system), )?; keywords.set_item("max_rss_bytes", max_rss_bytes)?; - imported(py, DIAGNOSTICS_MODELS_MODULE, "ProcessResourceUsage")?.call((), Some(&keywords)) + host::entry(py, ExecutorHost::ProcessResourceUsage)?.call((), Some(&keywords)) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs index a9a614ba8e..e9a96b89df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs @@ -24,29 +24,23 @@ use crate::bindings::_helpers::diff_report::query::{ }; use crate::bindings::_helpers::diff_report::registration::publish_diff_output; use crate::bindings::_helpers::diff_report::support::{ - EXCEPTIONS_MODULE, call, caused, cli_error, error_text, is_exception, matches_class, none, - object, request_facts, user_error_parts, + call, caused, cli_error, error_text, is_exception, matches_class, matches_native, none, object, + request_facts, user_error_parts, }; use crate::bindings::_helpers::discovery::configuration::discover_project_configuration; use crate::bindings::_helpers::frontier::encoder::emit_error; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; const DEFAULT_DIFF_MAX_FULL_ROWS: i64 = 10_000_000; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const EXECUTE_MODULE: &str = "sqlbuild.executor.diff.main.execute"; -const DIFF_MODELS_MODULE: &str = "sqlbuild.executor.diff.models"; -const ADAPTER_ERRORS: (&str, &str) = ("sqlbuild.adapter.contract.exceptions", "AdapterUserError"); -const EXECUTOR_INPUT_ERRORS: (&str, &str) = - ("sqlbuild.errors.contracts.exceptions", "ExecutorInputError"); -const SIZE_GUARD_ERRORS: (&str, &str) = ( - "sqlbuild.executor.diff.exceptions", - "FullDiffSizeGuardError", -); -const OUTCOME_ERRORS: (&str, &str) = (EXCEPTIONS_MODULE, "QueryDiffOutcomeError"); -const USER_ERRORS: (&str, &str) = (EXCEPTIONS_MODULE, "CliUserError"); +const ADAPTER_ERRORS: AdapterHost = AdapterHost::AdapterUserError; +const EXECUTOR_INPUT_ERRORS: ExecutorHost = ExecutorHost::ExecutorInputError; +const SIZE_GUARD_ERRORS: ExecutorHost = ExecutorHost::FullDiffSizeGuardError; +const OUTCOME_ERRORS: ErrorId = ErrorId::QueryDiffOutcome; +const USER_ERRORS: ErrorId = ErrorId::CliUser; const ROW_DIFF_KEY_ERROR_PREFIX: &str = "row diff "; const ROW_DIFF_KEY_ERROR_FRAGMENT: &str = " unique_key values"; @@ -60,7 +54,7 @@ fn user(message: &str, code: &str, help: Option<&str>) -> DiffUserError { /// `CliUserError` from a pure rejection. fn rejected(py: Python<'_>, error: &DiffUserError) -> PyErr { - cli_error(py, "CliUserError", error) + cli_error(py, ErrorId::CliUser, error) } /// The project directory and discovered inputs, after the request's flags validate. @@ -74,9 +68,9 @@ fn resolve_invocation<'py>( return Err(cli_error( py, if query_mode { - "QueryDiffIncompleteError" + ErrorId::QueryDiffIncomplete } else { - "CliUserError" + ErrorId::CliUser }, &error, )); @@ -89,12 +83,12 @@ fn resolve_invocation<'py>( discover_project_configuration(py, &project_dir)? } else { call( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", &project_dir)], )? }; call( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", &request.getattr("select")?), ("exclude", &request.getattr("exclude")?), @@ -117,10 +111,10 @@ fn query_outcome_error( parts: &DiffUserError, cause: PyErr, ) -> PyErr { - let class: &str = if incomplete { - "QueryDiffIncompleteError" + let class: ErrorId = if incomplete { + ErrorId::QueryDiffIncomplete } else { - "QueryDiffExecutionError" + ErrorId::QueryDiffExecution }; caused(py, cli_error(py, class, parts), cause) } @@ -132,7 +126,7 @@ fn execution_failure(py: Python<'_>, code: &str, cause: PyErr) -> PyResult, error: PyErr) -> PyResult { - if matches_class(py, &error, OUTCOME_ERRORS)? || !is_exception(py, &error) { + if matches_native(py, &error, OUTCOME_ERRORS)? || !is_exception(py, &error) { return Ok(error); } let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); @@ -152,7 +146,7 @@ fn query_execution_error(py: Python<'_>, error: PyErr) -> PyResult { }; return Ok(query_outcome_error(py, true, &parts, error)); } - if matches_class(py, &error, USER_ERRORS)? { + if matches_native(py, &error, USER_ERRORS)? { let parts: DiffUserError = user_error_parts(&value)?; let incomplete: bool = is_incomplete_query_failure(QueryDiffPhase::Execution, &parts.code); return Ok(query_outcome_error(py, incomplete, &parts, error)); @@ -173,7 +167,7 @@ fn run_query_diff( let py: Python<'_> = request.py(); let preparation: Bound<'_, PyAny> = match prepare_query_diff(request, invocation) { Ok(preparation) => preparation, - Err(error) if matches_class(py, &error, USER_ERRORS)? => { + Err(error) if matches_native(py, &error, USER_ERRORS)? => { let parts: DiffUserError = user_error_parts(&error.value(py).clone().into_any())?; let incomplete: bool = is_incomplete_query_failure(QueryDiffPhase::Preparation, &parts.code); @@ -230,7 +224,7 @@ fn target_max_full_rows<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let configured: Bound<'py, PyAny> = call( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", &discovered.getattr("project_config")?), ("local_config", &discovered.getattr("local_config")?), @@ -260,7 +254,7 @@ fn diff_to_target<'py>( return Ok(to_name); } let active: Bound<'py, PyAny> = call( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", &discovered.getattr("project_config")?), ("local_config", &discovered.getattr("local_config")?), @@ -338,7 +332,7 @@ fn size_guard_error( keywords.set_item("details", details)?; keywords.set_item("code", "C270")?; keywords.set_item("help", report.help)?; - let replacement: Bound<'_, PyAny> = imported(py, EXCEPTIONS_MODULE, "DiffSizeGuardError")? + let replacement: Bound<'_, PyAny> = registry::error(py, ErrorId::DiffSizeGuard)? .call((report.message.as_str(),), Some(&keywords))?; Ok(caused(py, PyErr::from_value(replacement), error)) } @@ -378,7 +372,7 @@ fn run_model_diff( let (_, adapter) = project_adapter(discovered, project_dir)?; let cli_vars: Bound<'_, PyAny> = request.getattr("cli_vars")?; let connection_config: Bound<'_, PyAny> = call( - &imported(py, NATIVE_MODULE, "resolve_target_connection_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConnectionConfig)?, &[ ("discovered_inputs", discovered), ("project_dir", project_dir), @@ -387,18 +381,14 @@ fn run_model_diff( ], )?; let resolver: Bound<'_, PyAny> = call( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", project_dir), ("discovered_inputs", discovered), ], )?; let pipeline: Bound<'_, PyTuple> = call( - &imported(py, NATIVE_MODULE, "run_diff_pipeline")?, + ®istry::native(py, NativeAttr::RunDiffPipeline)?, &[ ("discovered_inputs", discovered), ("adapter", &adapter), @@ -431,7 +421,7 @@ fn run_model_diff( none(py) } else { call( - &imported(py, DIFF_MODELS_MODULE, "FullDiffSizeLimits")?, + &host::entry(py, ExecutorHost::FullDiffSizeLimits)?, &[ ("left_target", &from_target), ("right_target", &to_target), @@ -459,7 +449,7 @@ fn run_model_diff( let executed: PyResult> = execution_options(request, &extra).and_then(|options| { call( - &imported(py, EXECUTE_MODULE, "execute_diff")?, + &host::entry(py, ExecutorHost::ExecuteDiff)?, &[ ("adapter", &adapter), ("connection", &connection), @@ -498,7 +488,7 @@ pub(crate) fn run_diff_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> Py Ok(invocation) => invocation, Err(error) if query_mode - && !matches_class(py, &error, OUTCOME_ERRORS)? + && !matches_native(py, &error, OUTCOME_ERRORS)? && is_exception(py, &error) => { return Err(execution_failure(py, "C252", error)?); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs index 3fed5deff2..7c20b69051 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs @@ -19,27 +19,22 @@ use crate::bindings::_helpers::diff_report::support::{ call, cli_error, error_text, none, object, progress_line, }; use crate::bindings::_helpers::diff_report::tolerances::cli_tolerance_overrides; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; use sqlbuild_analysis::diff_selection::main::raw_query_shape::raw_query_shape; use sqlbuild_analysis::diff_selection::models::RawQueryShape; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const NAMESPACE_MODULE: &str = "sqlbuild._native"; -const RUNTIME_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; -const COMMAND_MODELS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.executor.diff.classes.query_artifact_lifecycle"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; -const CURSOR_MODELS_MODULE: &str = "sqlbuild._native"; -const EXECUTE_QUERY_MODULE: &str = "sqlbuild.executor.diff.main._execute_query"; -const DIFF_MODELS_MODULE: &str = "sqlbuild.executor.diff.models"; const NAMESPACELESS_ADAPTERS: [&str; 2] = ["duckdb", "motherduck"]; fn failure(py: Python<'_>, message: String, code: &str, help: Option<&str>) -> PyErr { cli_error( py, - "CliUserError", + ErrorId::CliUser, &DiffUserError { message, code: code.to_owned(), @@ -49,7 +44,7 @@ fn failure(py: Python<'_>, message: String, code: &str, help: Option<&str>) -> P } fn user_failure(py: Python<'_>, error: DiffUserError) -> PyErr { - cli_error(py, "CliUserError", &error) + cli_error(py, ErrorId::CliUser, &error) } /// The adapter of the effective adapter name, resolved for the project directory. @@ -59,7 +54,7 @@ pub(crate) fn project_adapter<'py>( ) -> PyResult<(String, Bound<'py, PyAny>)> { let py: Python<'py> = discovered.py(); let name: Bound<'py, PyAny> = call( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", &discovered.getattr("project_config")?), ("local_config", &discovered.getattr("local_config")?), @@ -175,7 +170,7 @@ pub(crate) fn prepare_query_diff<'py>( let selected_target: Bound<'py, PyAny> = request.getattr("selected_target")?; let cli_vars: Bound<'py, PyAny> = request.getattr("cli_vars")?; let config: Bound<'py, PyAny> = call( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered), ("project_dir", project_dir), @@ -189,7 +184,7 @@ pub(crate) fn prepare_query_diff<'py>( config.call_method1("get", ("database",))? }; let namespace: Bound<'py, PyAny> = call( - &imported(py, NAMESPACE_MODULE, "build_effective_target_namespace")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetNamespace)?, &[ ("discovered_inputs", discovered), ("selected_target", &selected_target), @@ -234,7 +229,7 @@ pub(crate) fn prepare_query_diff<'py>( let left_sql: String = read_only_query(py, &left_sql, "left", &adapter)?; let right_sql: String = read_only_query(py, &right_sql, "right", &adapter)?; let runtime: Bound<'py, PyTuple> = call( - &imported(py, RUNTIME_MODULE, "build_effective_runtime_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", discovered), ("selected_target", &selected_target), @@ -259,7 +254,7 @@ pub(crate) fn prepare_query_diff<'py>( ) }; call( - &imported(py, COMMAND_MODELS_MODULE, "QueryDiffPreparation")?, + ®istry::class(py, RecordId::QueryDiffPreparation)?, &[ ("adapter", &adapter), ("connection_config", &config), @@ -308,7 +303,7 @@ pub(crate) fn execution_options<'py>( let py: Python<'py> = request.py(); let attribute = |name: &str| request.getattr(name); let sampling: Bound<'py, PyAny> = call( - &imported(py, DIFF_MODELS_MODULE, "RowDiffSamplingOverride")?, + &host::entry(py, ExecutorHost::RowDiffSamplingOverride)?, &[ ("row_limit", &attribute("sample_rows")?), ("seed", &attribute("sample_seed")?), @@ -337,7 +332,7 @@ pub(crate) fn execution_options<'py>( ]); pairs.extend_from_slice(extra); call( - &imported(py, DIFF_MODELS_MODULE, "DiffExecutionOptions")?, + &host::entry(py, ExecutorHost::DiffExecutionOptions)?, &pairs, ) } @@ -376,8 +371,7 @@ pub(crate) fn execute_query_diff<'py>( py, &format!("Query diff connection OK ({:.2}s)", phases[0].1), )?; - let lifecycle: Bound<'py, PyAny> = - imported(py, LIFECYCLE_MODULE, "QueryDiffArtifactLifecycle")?; + let lifecycle: Bound<'py, PyAny> = host::entry(py, ExecutorHost::QueryDiffArtifactLifecycle)?; let artifact = |side: &str| -> PyResult> { call( &lifecycle.getattr("build")?, @@ -451,7 +445,7 @@ pub(crate) fn execute_query_diff<'py>( if model.is_none() { return Err(cli_error( py, - "QueryDiffExecutionError", + ErrorId::QueryDiffExecution, &DiffUserError { message: "query diff comparison completed without a result".to_owned(), code: "C256".to_owned(), @@ -460,7 +454,7 @@ pub(crate) fn execute_query_diff<'py>( )); } let result: Bound<'py, PyAny> = call( - &imported(py, DIFF_MODELS_MODULE, "DiffExecutionResult")?, + &host::entry(py, ExecutorHost::DiffExecutionResult)?, &[("model_results", &PyTuple::new(py, [model])?.into_any())], )?; Ok((result, phases)) @@ -538,7 +532,7 @@ fn compare_artifacts<'py>( let compared: PyResult> = execution_options(request, &extra).and_then(|options| { call( - &imported(py, EXECUTE_QUERY_MODULE, "execute_query_diff")?, + &host::entry(py, ExecutorHost::ExecuteQueryDiff)?, &[ ("adapter", &adapter), ("connection", connection), @@ -670,7 +664,7 @@ fn inspect_inputs<'py>( }; let locations: Bound<'py, PyTuple> = PyTuple::new(py, [location(left)?, location(right)?])?; let lookup: Bound<'py, PyAny> = call( - &imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")?, + &host::entry(py, AdapterHost::BuildRelationLookup)?, &[ ("adapter", &adapter), ("connection", connection), @@ -698,7 +692,7 @@ fn inspect_inputs<'py>( )); } } - let ttl: Bound<'py, PyAny> = imported(py, CURSOR_MODELS_MODULE, "Duration")? + let ttl: Bound<'py, PyAny> = registry::class(py, RecordId::Duration)? .call_method1("parse", (preparation.getattr("artifact_ttl")?,))?; if ttl.is_none() { return Err(failure( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs index 5d8f4c05a9..36d8f85712 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs @@ -7,8 +7,10 @@ use sqlbuild_cli::diff_report::errors::DiffUserError; use sqlbuild_cli::diff_report::models::DiffRequestFacts; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::models::HostId; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `name(**pairs)` with keyword arguments. pub(crate) fn call<'py>( @@ -34,13 +36,13 @@ pub(crate) fn object<'py, T: pyo3::IntoPyObjectExt<'py>>( } /// `class(message, code=code, help=help)` from the CLI exceptions module. -pub(crate) fn cli_error(py: Python<'_>, class: &str, error: &DiffUserError) -> PyErr { +pub(crate) fn cli_error(py: Python<'_>, class: ErrorId, error: &DiffUserError) -> PyErr { let raised = (|| -> PyResult> { let help: Bound<'_, PyAny> = object(py, error.help.as_deref())?; let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", error.code.as_str())?; keywords.set_item("help", help)?; - imported(py, EXCEPTIONS_MODULE, class)?.call((error.message.as_str(),), Some(&keywords)) + registry::error(py, class)?.call((error.message.as_str(),), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), @@ -71,9 +73,14 @@ pub(crate) fn caused(py: Python<'_>, replacement: PyErr, cause: PyErr) -> PyErr pub(crate) fn matches_class( py: Python<'_>, error: &PyErr, - (module, class): (&str, &str), + class: impl Into, ) -> PyResult { - error.value(py).is_instance(&imported(py, module, class)?) + error.value(py).is_instance(&host::entry(py, class)?) +} + +/// Whether `error` is an instance of the native error class `class`. +pub(crate) fn matches_native(py: Python<'_>, error: &PyErr, class: ErrorId) -> PyResult { + error.value(py).is_instance(®istry::error(py, class)?) } /// Whether `error` is an `Exception` rather than a `BaseException` such as an interrupt. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs index 308f7960de..ddac90a6c2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs @@ -7,10 +7,11 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFloat, PyInt, PyString}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -const EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; const DECIMAL_MODULE: &str = "decimal"; const ABSOLUTE_KEY: &str = "absolute"; const RELATIVE_KEY: &str = "relative"; @@ -21,7 +22,7 @@ const CLI_TOLERANCE_MESSAGE: &str = /// `ExecutorInputError(message, code=code)`. fn input_error(py: Python<'_>, message: String, code: &str) -> PyErr { - imported(py, EXCEPTIONS_MODULE, "ExecutorInputError") + host::entry(py, ExecutorHost::ExecutorInputError) .and_then(|class| { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; @@ -42,7 +43,7 @@ fn parse_row_diff_tolerances<'py>( fn parsed_tolerances<'py>(raw: &Bound<'py, PyAny>, label: &str) -> PyResult> { let py: Python<'py> = raw.py(); - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "RowDiffTolerances")?; + let class: Bound<'py, PyAny> = host::entry(py, AdapterHost::RowDiffTolerances)?; if raw.is_none() { return class.call0(); } @@ -161,7 +162,7 @@ fn tolerance_rule<'py>(raw: &Bound<'py, PyAny>, label: &str) -> PyResult( &format!("{label}.row_diff_sample_seed"), )?; call_with( - &imported(py, MODELS_MODULE, "RowDiffSampling")?, + &host::entry(py, AdapterHost::RowDiffSampling)?, &[ ("row_limit", row_limit.into_bound_py_any(py)?), ("seed", seed.unwrap_or(0).into_bound_py_any(py)?), @@ -331,7 +332,7 @@ fn merge_row_diff_tolerances<'py>( Ok(combined.into_any()) }; call_with( - &imported(py, MODELS_MODULE, "RowDiffTolerances")?, + &host::entry(py, AdapterHost::RowDiffTolerances)?, &[ ("by_type", merged("by_type")?), ("by_column", merged("by_column")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs index ab63518455..a3364bcffc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs @@ -3,11 +3,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// `DiscoveredProjectInputs` with the loaded configuration, inside the project discovery lifecycle. pub(crate) fn discover_project_configuration<'py>( @@ -15,7 +15,7 @@ pub(crate) fn discover_project_configuration<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "project").into_any()), ( @@ -26,17 +26,17 @@ pub(crate) fn discover_project_configuration<'py>( )?; within(&lifecycle, |_| { call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &[ ("project_dir", project_dir.clone()), ( "project_config", - imported(py, NATIVE_MODULE, "load_project_config_file")? + registry::native(py, NativeAttr::LoadProjectConfigFile)? .call1((project_dir,))?, ), ( "local_config", - imported(py, NATIVE_MODULE, "load_local_config_file")?.call1((project_dir,))?, + registry::native(py, NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?, ), ], ) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs index 6bc8b68467..1759145de6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs @@ -1,6 +1,5 @@ //! Declaration file collections discovered natively and built as Python discovery records. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; @@ -28,16 +27,12 @@ use crate::bindings::_helpers::discovery::snapshot::{ }; use crate::bindings::_helpers::discovery::sorted_keys::sorted_keys; use crate::bindings::_helpers::model_config::header_columns::schema_columns; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::authored_values::map_to_python; use crate::bindings::models::{NativeDiscoverySession, NativeProjectTree}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const AUDITING_MODELS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const SESSION_MEMO_KEY: &str = "native_discovery_session"; const MEASUREMENT_MODE: &str = "MEASUREMENT"; @@ -75,8 +70,8 @@ impl<'py> FileContext<'_, 'py> { } } - fn record(&self, class: &str, fields: &Bound<'py, PyDict>) -> PyResult> { - shape_class_at(self.py(), MODELS_MODULE, class)?.call((), Some(fields)) + fn record(&self, class: RecordId, fields: &Bound<'py, PyDict>) -> PyResult> { + registry::class(self.py(), class)?.call((), Some(fields)) } } @@ -89,7 +84,7 @@ pub(crate) fn add_scope_fields( let py: Python<'_> = fields.py(); fields.set_item( "scope_kind", - imported(py, SCOPE_TYPES_MODULE, "ScopeKind")?.call1((scope.scope_kind,))?, + registry::class(py, RecordId::ScopeKind)?.call1((scope.scope_kind,))?, )?; for (name, path) in [ ("ownership_root", &scope.ownership_root), @@ -132,8 +127,8 @@ fn single_value<'py>( fn enum_file<'py>(context: &FileContext<'_, 'py>, file: &EnumFile) -> PyResult> { let py: Python<'py> = context.py(); - let member_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "EnumMember")?; - let declaration_class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "EnumDeclaration")?; + let member_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumMember)?; + let declaration_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumDeclaration)?; let mut declarations: Vec> = Vec::with_capacity(file.declarations.len()); for declaration in &file.declarations { let members: Vec> = authored(py, &declaration.members)? @@ -156,7 +151,7 @@ fn enum_file<'py>(context: &FileContext<'_, 'py>, file: &EnumFile) -> PyResult = context.fields(&file.contents)?; fields.set_item("declarations", PyTuple::new(py, declarations)?)?; context.add_scope(&fields)?; - context.record("DiscoveredEnumFile", &fields) + context.record(RecordId::DiscoveredEnumFile, &fields) } fn constant_file<'py>( @@ -184,7 +179,7 @@ fn constant_file<'py>( let fields: Bound<'py, PyDict> = context.fields(&file.contents)?; fields.set_item("declarations", PyTuple::new(py, declarations)?)?; context.add_scope(&fields)?; - context.record("DiscoveredConstantFile", &fields) + context.record(RecordId::DiscoveredConstantFile, &fields) } /// Python's `native_locations`: a repeated name keeps its first position and last value. @@ -193,7 +188,7 @@ pub(crate) fn source_locations<'py>( relative_path: &Bound<'py, PyAny>, rows: &[(String, LineColumnSpan)], ) -> PyResult> { - let class: Bound<'py, PyAny> = imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let located: Bound<'py, PyDict> = PyDict::new(py); for (name, span) in rows { located.set_item( @@ -220,7 +215,7 @@ fn schema_file<'py>( let py: Python<'py> = context.py(); let file_path: String = context.file_path.str()?.to_string(); let declaration_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "ModelSchemaDeclaration")?; + registry::class(py, RecordId::ModelSchemaDeclaration)?; let mut declarations: Vec> = Vec::with_capacity(file.declarations.len()); for declaration in &file.declarations { let raw_columns: Bound<'py, PyAny> = @@ -257,7 +252,7 @@ fn schema_file<'py>( let fields: Bound<'py, PyDict> = context.fields(&file.contents)?; fields.set_item("declarations", PyTuple::new(py, declarations)?)?; context.add_scope(&fields)?; - context.record("DiscoveredModelSchemaFile", &fields) + context.record(RecordId::DiscoveredModelSchemaFile, &fields) } fn function_file<'py>( @@ -270,7 +265,7 @@ fn function_file<'py>( authored(context.py(), &file.header_values)?, )?; fields.set_item("body_sql", &file.body_sql)?; - context.record("DiscoveredSqlFunctionFile", &fields) + context.record(RecordId::DiscoveredSqlFunctionFile, &fields) } fn hook_file<'py>(context: &FileContext<'_, 'py>, file: &HookFile) -> PyResult> { @@ -283,7 +278,7 @@ fn hook_file<'py>(context: &FileContext<'_, 'py>, file: &HookFile) -> PyResult(py: Python<'py>, block: &AuditBlock) -> PyResult> { @@ -295,7 +290,7 @@ fn audit_block<'py>(py: Python<'py>, block: &AuditBlock) -> PyResult(py: Python<'py>, block: &AuditBlock) -> PyResult( let declaration_kind: &str = context.scope.map_or("", |scope| scope.declaration_kind); fields.set_item( "declaration_kind", - imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?.call1((declaration_kind,))?, + registry::class(py, RecordId::DeclarationKind)?.call1((declaration_kind,))?, )?; context.add_scope(&fields)?; - context.record("DiscoveredAuditFile", &fields) + context.record(RecordId::DiscoveredAuditFile, &fields) } fn macro_file<'py>( @@ -363,7 +358,7 @@ fn macro_file<'py>( ) -> PyResult> { let fields: Bound<'py, PyDict> = context.fields(&file.contents)?; context.add_scope(&fields)?; - context.record("DiscoveredMacroFile", &fields) + context.record(RecordId::DiscoveredMacroFile, &fields) } /// The session of this pass, created and memoised by the first declaration collection. @@ -524,7 +519,7 @@ fn seed_records<'py, T>( files: &[ScopedFile], ) -> PyResult> { let py: Python<'py> = faults.project_dir.py(); - let class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "DiscoveredSeedFile")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSeedFile)?; let seeds: Vec> = files .iter() .map(|scoped| { @@ -672,7 +667,7 @@ pub(crate) fn discover_adapter_file<'py>( return Ok(py.None().into_bound(py)); } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredAdapterFile")?, + ®istry::class(py, RecordId::DiscoveredAdapterFile)?, &[ ("file_path", file_path), ("relative_path", path_class(py)?.call1(("adapter.py",))?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs index dc303e0764..2fb0491bd0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs @@ -4,11 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyBool, PyCFunction, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Models and model schemas, skipping files that cannot be read. #[pyfunction] @@ -17,9 +15,9 @@ pub(crate) fn discover_model_description_inputs<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_dir.py(); - let native = |name: &str| imported(py, NATIVE_MODULE, name); + let native = |name: NativeAttr| registry::native(py, name); let project_config: Bound<'py, PyAny> = - native("load_project_config_file")?.call1((project_dir,))?; + native(NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; let skip: Bound<'py, PyAny> = PyCFunction::new_closure(py, None, None, |_arguments, _keywords| -> PyResult<()> { Ok(()) @@ -27,22 +25,22 @@ pub(crate) fn discover_model_description_inputs<'py>( .into_any(); let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); let scope: Bound<'py, PyAny> = call_with( - &native("directory_snapshot_scope")?, + &native(NativeAttr::DirectorySnapshotScope)?, &[("project_dir", project_dir.clone())], )?; within(&scope, |_| { call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &[ ("project_config", project_config), ( "local_config", - imported(py, CONTRACT_MODELS_MODULE, "LocalConfig")?.call0()?, + registry::class(py, RecordId::LocalConfig)?.call0()?, ), ( "model_files", call_with( - &native("discover_model_files")?, + &native(NativeAttr::DiscoverModelFiles)?, &[ ("project_dir", project_dir.clone()), ("extract_implicit_alias_columns", disabled.clone()), @@ -54,7 +52,7 @@ pub(crate) fn discover_model_description_inputs<'py>( ( "model_schema_files", call_with( - &native("discover_model_schema_files")?, + &native(NativeAttr::DiscoverModelSchemaFiles)?, &[("project_dir", project_dir.clone()), ("on_fault", skip)], )?, ), @@ -70,9 +68,9 @@ pub(crate) fn externally_described_models<'py>( let py: Python<'py> = project_dir.py(); let inputs: Bound<'py, PyAny> = discover_model_description_inputs(project_dir)?; let schemas: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "public_model_schema_index")?.call1((&inputs,))?; + registry::native(py, NativeAttr::PublicModelSchemaIndex)?.call1((&inputs,))?; let described: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "externally_described_models")?.call1((&inputs, schemas))?; + registry::native(py, NativeAttr::ExternallyDescribedModels)?.call1((&inputs, schemas))?; PyFrozenSet::new(py, described.try_iter()?.collect::>>()?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs index 56b7b29f06..dcecb736de 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs @@ -4,10 +4,10 @@ use pyo3::exceptions::{PyOSError, PySyntaxError, PyUnicodeError, PyValueError}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::PyTuple; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; -const FAULT_CLASS: &str = "DiscoveryFileFault"; const PROJECT_DIR_MARKER: &str = "."; /// The files one discovery builds, with the caller's optional `on_fault` reporter. @@ -46,7 +46,7 @@ impl<'py> FileFaults<'py> { .str()? .call_method1("replace", (self.project_dir.str()?, PROJECT_DIR_MARKER))?; on_fault.call1((call_with( - &imported(py, NATIVE_RECORDS_MODULE, FAULT_CLASS)?, + ®istry::class(py, RecordId::DiscoveryFileFault)?, &[("path", relative_path.clone()), ("message", message)], )?,))?; Ok(None) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs index 08024d074d..d8223255b9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs @@ -15,30 +15,22 @@ use sqlbuild_model_config::declarations::models::{ use sqlbuild_model_config::sql_values::errors::SqlValueFailure; use sqlbuild_model_config::sql_values::models::RawSqlValue; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use crate::bindings::_helpers::contracts::sql_values::{ SqlValueClasses, raw_value, sql_value, sql_value_error, }; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; fn declaration_error(py: Python<'_>, failure: DeclarationFailure) -> PyResult { Ok(match failure { - DeclarationFailure::Parse(message) => PyErr::from_value( - py.import(DISCOVERY_ERRORS_MODULE)? - .getattr("DeclarationParseError")? - .call1((message,))?, - ), + DeclarationFailure::Parse(message) => { + PyErr::from_value(registry::error(py, ErrorId::DeclarationParse)?.call1((message,))?) + } DeclarationFailure::Identity(message, help) => { let keywords = PyDict::new(py); keywords.set_item("help", help)?; PyErr::from_value( - py.import(IDENTITY_ERRORS_MODULE)? - .getattr("ResourceIdentityError")? + registry::error(py, ErrorId::ResourceIdentity)? .call((message,), Some(&keywords))?, ) } @@ -59,8 +51,7 @@ fn enum_record<'py>( facts: EnumFacts, owner: &Owner<'py>, ) -> PyResult> { - let models = py.import(MODELS_MODULE)?; - let member_class = shape_class(models.as_any(), "EnumMember")?; + let member_class = registry::class(py, RecordId::EnumMember)?; let int_class = py.import("builtins")?.getattr("int")?; let members = facts .members @@ -73,7 +64,7 @@ fn enum_record<'py>( member_class.call1((name, value)) }) .collect::>>()?; - models.getattr("EnumDeclaration")?.call1(( + registry::class(py, RecordId::EnumDeclaration)?.call1(( facts.name, PyTuple::new(py, members)?, facts.scalar_type, @@ -89,21 +80,18 @@ fn constant_record<'py>( classes: &SqlValueClasses<'py>, ) -> PyResult> { let render_as = match facts.render_as { - Some(rendering) => py - .import(NATIVE_RECORDS_MODULE)? - .getattr("CollectionRendering")? - .call1((rendering,))?, + Some(rendering) => { + registry::class(py, RecordId::CollectionRendering)?.call1((rendering,))? + } None => py.None().into_bound(py), }; - py.import(MODELS_MODULE)? - .getattr("ConstantDeclaration")? - .call1(( - facts.name, - sql_value(&facts.value, classes)?, - &owner.relative_path, - owner.model_name.as_deref(), - render_as, - )) + registry::class(py, RecordId::ConstantDeclaration)?.call1(( + facts.name, + sql_value(&facts.value, classes)?, + &owner.relative_path, + owner.model_name.as_deref(), + render_as, + )) } fn optional_raw( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs index f0d83ff160..c10008829f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs @@ -6,10 +6,9 @@ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const HEADER_CACHE_SIZE: usize = 4096; /// `(name, line, column, end line, end column)`. @@ -50,10 +49,8 @@ fn prepare_model_header_tokens(headers: &Bound<'_, PyAny>) -> PyResult<()> { cache.clear(); } let names: Bound<'_, PyList> = missing.keys(); - let parsed: Bound<'_, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("parse_model_headers")? - .call1((&names,))?; + let parsed: Bound<'_, PyAny> = + registry::native(py, NativeAttr::ParseModelHeaders)?.call1((&names,))?; for (header, row) in names.iter().zip(parsed.try_iter()?) { let row: Bound<'_, PyAny> = row?; cache.set_item( @@ -83,11 +80,10 @@ fn parse_header_values<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = request.get("header_line")?; - let (detail, help, line): (String, Option, Option) = py - .import(NATIVE_MODULE)? - .getattr("model_header_failure")? - .call1((error.str()?, &header, &header_line))? - .extract()?; + let (detail, help, line): (String, Option, Option) = + registry::native(py, NativeAttr::ModelHeaderFailure)? + .call1((error.str()?, &header, &header_line))? + .extract()?; let file_path: String = request.get("file_path")?.str()?.extract()?; let location: String = match line { Some(line) => format!("{file_path}:{line}"), @@ -96,9 +92,7 @@ fn parse_header_values<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = match request.optional("error_class")? { Some(class) if !class.is_none() => class, - _ => py - .import(DISCOVERY_ERRORS_MODULE)? - .getattr("ModelSqlParseError")?, + _ => registry::error(py, ErrorId::ModelSqlParse)?, }; let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; @@ -117,9 +111,7 @@ fn source_locations<'py>( relative_path: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = rows.py(); - let location_class: Bound<'py, PyAny> = py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?; + let location_class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let locations: Bound<'py, PyDict> = PyDict::new(py); for row in rows.try_iter()? { let (name, line, column, end_line, end_column): LocationRow<'py> = row?.extract()?; @@ -146,9 +138,7 @@ fn model_output_column_locations<'py>( Some(value) => value.is_truthy()?, None => true, }; - let rows: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("model_output_columns")? + let rows: Bound<'py, PyAny> = registry::native(py, NativeAttr::ModelOutputColumns)? .call1((request.get("contents")?, implicit))?; source_locations(&rows, &request.get("relative_path")?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs index 10b44186a4..7ee298031a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs @@ -4,9 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const RELATIVE_PATH_FIELD: &str = "relative_path"; const SQL_HOOK_FIELDS: [&str; 6] = [ "statement", @@ -47,8 +47,8 @@ pub(crate) fn hook_entries<'py>( if !value.is_instance_of::() && !value.is_instance_of::() { return Ok(hooks); } - let sql_class: Bound<'py, PyAny> = imported(py, DISCOVERY_MODELS_MODULE, "SqlHookEntry")?; - let python_class: Bound<'py, PyAny> = imported(py, DISCOVERY_MODELS_MODULE, "PythonHookEntry")?; + let sql_class: Bound<'py, PyAny> = registry::class(py, RecordId::SqlHookEntry)?; + let python_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonHookEntry)?; for entry in value.try_iter()? { let entry: Bound<'py, PyAny> = entry?; if entry.is_instance(&sql_class)? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs index 5257ca286a..ca782ab0ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs @@ -4,14 +4,12 @@ use pyo3::exceptions::PyException; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::event_exporting::scope::current_scope; - -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const SHIM_MODULE: &str = - "sqlbuild.compiler.discovery._helpers.filesystem.project_modules"; -pub(crate) const MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// One project directory under discovery. pub(crate) struct ProjectDir<'py> { @@ -31,7 +29,7 @@ impl<'py> ProjectDir<'py> { /// `project_relative_path(path=file_path, project_dir=project_dir)`. pub(crate) fn relative(&self, file_path: &Bound<'py, PyAny>) -> PyResult> { call_with( - &imported(self.py(), NATIVE_MODULE, "project_relative_path")?, + ®istry::native(self.py(), NativeAttr::ProjectRelativePath)?, &[ ("path", file_path.clone()), ("project_dir", self.path.clone()), @@ -55,7 +53,7 @@ impl<'py> ProjectDir<'py> { ) -> PyResult>> { let py: Python<'py> = self.py(); call_with( - &imported(py, NATIVE_MODULE, "discover_python_module_files")?, + ®istry::native(py, NativeAttr::DiscoverPythonModuleFiles)?, &[ ("project_dir", self.path.clone()), ("root", PyString::new(py, root).into_any()), @@ -74,7 +72,7 @@ impl<'py> ProjectDir<'py> { /// `shim.(file_path=file_path, project_dir=project_dir)`. pub(crate) fn load( &self, - loader: &str, + loader: UserCodeHost, file_path: &Bound<'py, PyAny>, ) -> PyResult> { call_with( @@ -101,15 +99,15 @@ impl<'py> ProjectDir<'py> { } /// `project_modules.`. -pub(crate) fn shim<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, SHIM_MODULE, name) +pub(crate) fn shim<'py>(py: Python<'py>, name: UserCodeHost) -> PyResult> { + host::entry(py, name) } /// Functions defined in one loaded module. pub(crate) fn module_functions<'py>( module: &Bound<'py, PyAny>, ) -> PyResult>> { - shim(module.py(), "module_functions")? + shim(module.py(), UserCodeHost::ModuleFunctions)? .call1((module,))? .try_iter()? .collect() @@ -118,15 +116,15 @@ pub(crate) fn module_functions<'py>( /// `sqlbuild._native.(**pairs)`. pub(crate) fn discovered<'py>( py: Python<'py>, - class_name: &str, + class_name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, MODELS_MODULE, class_name)?, pairs) + call_with(®istry::class(py, class_name)?, pairs) } /// `sqlbuild._native.(message)`. -pub(crate) fn discovery_error(py: Python<'_>, class_name: &str, message: String) -> PyErr { - imported(py, ERRORS_MODULE, class_name) +pub(crate) fn discovery_error(py: Python<'_>, class_name: ErrorId, message: String) -> PyErr { + registry::error(py, class_name) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value) } @@ -134,7 +132,7 @@ pub(crate) fn discovery_error(py: Python<'_>, class_name: &str, message: String) /// `discovery_error` raised from `cause`. pub(crate) fn discovery_error_from( py: Python<'_>, - (class_name, message): (&str, String), + (class_name, message): (ErrorId, String), cause: PyErr, ) -> PyErr { let error: PyErr = discovery_error(py, class_name, message); @@ -163,7 +161,7 @@ pub(crate) fn provider_usages<'py>( provider_by_name: &Bound<'py, PyDict>, ) -> PyResult> { call_with( - &shim(function.py(), "provider_usages")?, + &shim(function.py(), UserCodeHost::ProviderUsages)?, &[ ("function", function.clone()), ("provider_by_name", provider_by_name.clone().into_any()), @@ -182,8 +180,8 @@ pub(crate) fn tuple_of<'py>( /// `name` of the read definition, or `None` when the function carries no such definition. pub(crate) fn definition<'py>( function: &Bound<'py, PyAny>, - (module, reader): (&str, &str), + reader: UserCodeHost, ) -> PyResult>> { - let read: Bound<'py, PyAny> = imported(function.py(), module, reader)?.call1((function,))?; + let read: Bound<'py, PyAny> = host::entry(function.py(), reader)?.call1((function,))?; Ok((!read.is_none()).then_some(read)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs index 1ea77ab871..4f4232d00a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs @@ -8,18 +8,19 @@ use pyo3::types::{PyDict, PyDictMethods, PyString}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::discovery::imported_modules::{ - NATIVE_MODULE, ProjectDir, discovered, discovery_error, discovery_error_from, module_functions, + ProjectDir, discovered, discovery_error, discovery_error_from, module_functions, provider_by_name, provider_usages, shim, tuple_of, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const PROVIDER_ERROR: &str = "ProviderDiscoveryError"; -const SINK_ERROR: &str = "EventExporterDiscoveryError"; -const PROVIDER_INPUT_ERROR: (&str, &str) = ("sqlbuild.provider.exceptions", "ProviderInputError"); +const PROVIDER_ERROR: ErrorId = ErrorId::ProviderDiscovery; +const SINK_ERROR: ErrorId = ErrorId::EventExporterDiscovery; +const PROVIDER_INPUT_ERROR: UserCodeHost = UserCodeHost::ProviderInputError; const VALIDATION_ERROR: (&str, &str) = ("pydantic", "ValidationError"); -const SINK_DEFINITION: (&str, &str) = ( - "sqlbuild.runtime.event_exporting.main.get_lifecycle_event_sink_definition", - "get_lifecycle_event_sink_definition", -); +const SINK_DEFINITION: UserCodeHost = UserCodeHost::GetLifecycleEventSinkDefinition; const ROOT_LOCATION: &str = ""; const DEFAULT_MESSAGE: &str = "invalid value"; const DEFAULT_ERROR_TYPE: &str = "validation_error"; @@ -31,7 +32,7 @@ fn reject_invalid_identity( ) -> PyResult<()> { let py: Python<'_> = name.py(); call_with( - &imported(py, NATIVE_MODULE, "validate_resource_identity")?, + ®istry::native(py, NativeAttr::ValidateResourceIdentity)?, &[ ("name", name.clone()), ("kind", PyString::new(py, "provider").into_any()), @@ -54,8 +55,7 @@ fn provider_name<'py>( let name: Bound<'py, PyAny> = match provider_class.call_method0("name") { Ok(name) => name, Err(error) => { - let input_error: Bound<'py, PyAny> = - imported(py, PROVIDER_INPUT_ERROR.0, PROVIDER_INPUT_ERROR.1)?; + let input_error: Bound<'py, PyAny> = host::entry(py, PROVIDER_INPUT_ERROR)?; if !error.value(py).is_instance(&input_error)? { return Err(error); } @@ -138,7 +138,7 @@ pub(crate) fn discover_provider_classes<'py>( ) -> PyResult> { let py: Python<'py> = project_dir.py(); let project = ProjectDir { path: project_dir }; - let record_read: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "record_settings_class_read")?; + let record_read: Bound<'py, PyAny> = registry::native(py, NativeAttr::RecordSettingsClassRead)?; if let Some(cached) = project.cached_extensions()? { let providers: Bound<'py, PyAny> = cached.get_item(0)?; for provider in providers.try_iter()? { @@ -156,8 +156,10 @@ pub(crate) fn discover_provider_classes<'py>( let mut providers: Vec> = Vec::new(); let mut seen: Vec<(String, Bound<'py, PyAny>)> = Vec::new(); for file_path in project.module_files("providers", true)? { - let module: Bound<'py, PyAny> = project.load("load_provider_module", &file_path)?; - let classes: Bound<'py, PyAny> = shim(py, "module_provider_classes")?.call1((&module,))?; + let module: Bound<'py, PyAny> = + project.load(UserCodeHost::LoadProviderModule, &file_path)?; + let classes: Bound<'py, PyAny> = + shim(py, UserCodeHost::ModuleProviderClasses)?.call1((&module,))?; for provider_class in classes.try_iter()? { let provider_class: Bound<'py, PyAny> = provider_class?; let relative_path: Bound<'py, PyAny> = project.relative(&file_path)?; @@ -180,7 +182,7 @@ pub(crate) fn discover_provider_classes<'py>( provider_instance(&provider_class, (&name, &relative_path))?; providers.push(discovered( py, - "DiscoveredProvider", + RecordId::DiscoveredProvider, &[ ("file_path", file_path.clone()), ("relative_path", relative_path), @@ -220,11 +222,11 @@ pub(crate) fn discover_event_exporter_declarations<'py>( { return tuple_of(py, Vec::new()); } - let read_definition: Bound<'py, PyAny> = imported(py, SINK_DEFINITION.0, SINK_DEFINITION.1)?; + let read_definition: Bound<'py, PyAny> = host::entry(py, SINK_DEFINITION)?; let mut declarations: Vec> = Vec::new(); let mut seen: Vec<(String, Bound<'py, PyAny>)> = Vec::new(); for file_path in project.module_files("sinks", true)? { - let module: Bound<'py, PyAny> = project.load("load_sink_module", &file_path)?; + let module: Bound<'py, PyAny> = project.load(UserCodeHost::LoadSinkModule, &file_path)?; let mut seen_functions: HashSet = HashSet::new(); for function in module_functions(&module)? { if !seen_functions.insert(function.as_ptr() as usize) { @@ -248,7 +250,7 @@ pub(crate) fn discover_event_exporter_declarations<'py>( } let relative_path: Bound<'py, PyAny> = project.relative(&file_path)?; call_with( - &shim(py, "validate_event_exporter_signature")?, + &shim(py, UserCodeHost::ValidateEventExporterSignature)?, &[ ("function", function.clone()), ("exporter_name", read.getattr("name")?), @@ -258,7 +260,7 @@ pub(crate) fn discover_event_exporter_declarations<'py>( seen.push((name, file_path.clone())); declarations.push(discovered( py, - "DiscoveredEventExporterDeclaration", + RecordId::DiscoveredEventExporterDeclaration, &[ ("file_path", file_path.clone()), ("relative_path", relative_path), @@ -286,7 +288,8 @@ fn exporter_provider_usages<'py>( .str()? .extract()?; let empty: Bound<'py, PyAny> = imported(py, "inspect", "Parameter")?.getattr("empty")?; - let annotations: Bound<'py, PyAny> = shim(py, "parameter_annotations")?.call1((&function,))?; + let annotations: Bound<'py, PyAny> = + shim(py, UserCodeHost::ParameterAnnotations)?.call1((&function,))?; for (index, pair) in annotations.try_iter()?.enumerate() { if index == 0 { continue; @@ -344,7 +347,7 @@ pub(crate) fn bind_event_exporter_declarations<'py>( pairs.push((field, declaration.getattr(field)?)); } pairs.push(("provider_usages", usages)); - bound.push(discovered(py, "DiscoveredEventExporter", &pairs)?); + bound.push(discovered(py, RecordId::DiscoveredEventExporter, &pairs)?); } tuple_of(py, bound) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs index b2fb22082c..2aacc4c22e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs @@ -6,35 +6,29 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyBytes, PyDict, PyDictMethods, PyList, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::imported_modules::{ - MODELS_MODULE, NATIVE_MODULE, ProjectDir, definition, discovered, discovery_error, - discovery_error_from, is_exception, module_functions, provider_by_name, provider_usages, shim, - tuple_of, + ProjectDir, definition, discovered, discovery_error, discovery_error_from, is_exception, + module_functions, provider_by_name, provider_usages, shim, tuple_of, }; use crate::bindings::_helpers::discovery::imported_providers; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PYTHON_NODE_ROOT; -const NODE_ERROR: &str = "PythonNodeDiscoveryError"; -const AUDIT_CASE: (&str, &str) = ("sqlbuild.python_nodes.models", "AuditCase"); -const READ_FACTORY: (&str, &str) = ( - "sqlbuild.python_nodes.main.read_factory_definition", - "read_factory_definition", -); -const READ_AUDIT_FACTORY: (&str, &str) = ( - "sqlbuild.python_nodes.main.read_audit_factory_definition", - "read_audit_factory_definition", -); -const READ_HOOK: (&str, &str) = ( - "sqlbuild.python_nodes.main.read_hook_definition", - "read_hook_definition", -); +const NODE_ERROR: ErrorId = ErrorId::PythonNodeDiscovery; +const AUDIT_CASE: UserCodeHost = UserCodeHost::AuditCase; +const READ_FACTORY: UserCodeHost = UserCodeHost::ReadFactoryDefinition; +const READ_AUDIT_FACTORY: UserCodeHost = UserCodeHost::ReadAuditFactoryDefinition; +const READ_HOOK: UserCodeHost = UserCodeHost::ReadHookDefinition; const NOT_A_NODE: &str = "that is not a SQLBuild task, asset, loader, or check"; /// One Python-node kind: its reader, record class, bucket and copied definition fields. struct NodeKind { - reader: (&'static str, &'static str), - record: &'static str, + reader: UserCodeHost, + record: RecordId, bucket: usize, fields: &'static [&'static str], } @@ -42,11 +36,8 @@ struct NodeKind { /// Readers in precedence order; the first matching definition decides the kind. const NODE_KINDS: [NodeKind; 4] = [ NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_loader_definition", - "read_loader_definition", - ), - record: "DiscoveredLoaderFunction", + reader: UserCodeHost::ReadLoaderDefinition, + record: RecordId::DiscoveredLoaderFunction, bucket: 0, fields: &[ "depends_on", @@ -60,11 +51,8 @@ const NODE_KINDS: [NodeKind; 4] = [ ], }, NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_task_definition", - "read_task_definition", - ), - record: "DiscoveredTaskFunction", + reader: UserCodeHost::ReadTaskDefinition, + record: RecordId::DiscoveredTaskFunction, bucket: 1, fields: &[ "depends_on", @@ -76,11 +64,8 @@ const NODE_KINDS: [NodeKind; 4] = [ ], }, NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_asset_definition", - "read_asset_definition", - ), - record: "DiscoveredAssetFunction", + reader: UserCodeHost::ReadAssetDefinition, + record: RecordId::DiscoveredAssetFunction, bucket: 2, fields: &[ "depends_on", @@ -94,11 +79,8 @@ const NODE_KINDS: [NodeKind; 4] = [ ], }, NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_check_definition", - "read_check_definition", - ), - record: "DiscoveredCheckFunction", + reader: UserCodeHost::ReadCheckDefinition, + record: RecordId::DiscoveredCheckFunction, bucket: 3, fields: &[ "depends_on", @@ -191,10 +173,11 @@ impl<'py> Buckets<'py> { )); } let cases: Vec> = audit_cases(&function, (&name, &relative_text))?; - let line: Bound<'py, PyAny> = shim(py, "source_line")?.call1((&function,))?; + let line: Bound<'py, PyAny> = + shim(py, UserCodeHost::SourceLine)?.call1((&function,))?; let record: Bound<'py, PyAny> = discovered( py, - "DiscoveredAuditFactory", + RecordId::DiscoveredAuditFactory, &[ ("name", read.getattr("name")?), ("function", function.clone()), @@ -259,7 +242,7 @@ impl<'py> Buckets<'py> { let mut modules: Vec> = Vec::new(); for file_path in project.module_files(PYTHON_NODE_ROOT, false)? { let module: Bound<'py, PyAny> = - project.load("import_python_root_module", &file_path)?; + project.load(UserCodeHost::ImportPythonRootModule, &file_path)?; modules.push(ImportedModule { file_path, module }); } let mut direct: HashMap> = HashMap::new(); @@ -293,7 +276,7 @@ fn node_definition_names(function: &Bound<'_, PyAny>) -> PyResult) -> PyResult { - shim(function.py(), "requires_arguments")? + shim(function.py(), UserCodeHost::RequiresArguments)? .call1((function,))? .is_truthy() } @@ -338,7 +321,7 @@ fn audit_cases<'py>( ), )); } - let audit_case: Bound<'py, PyAny> = imported(py, AUDIT_CASE.0, AUDIT_CASE.1)?; + let audit_case: Bound<'py, PyAny> = host::entry(py, AUDIT_CASE)?; let mut cases: Vec> = Vec::new(); for (index, item) in result.try_iter()?.enumerate() { let item: Bound<'py, PyAny> = item?; @@ -416,7 +399,7 @@ pub(crate) fn discover_python_node_functions<'py>( let node_root: Bound<'py, PyAny> = project.child(PYTHON_NODE_ROOT)?; if node_root.call_method0("is_dir")?.is_truthy()? { let saved: Bound<'py, PyAny> = call_with( - &shim(py, "install_python_root_package")?, + &shim(py, UserCodeHost::InstallPythonRootPackage)?, &[ ("node_root", node_root), ("project_dir", project.path.clone()), @@ -424,7 +407,7 @@ pub(crate) fn discover_python_node_functions<'py>( )?; let discovered_nodes: PyResult<()> = buckets.discover_nodes(&project, &providers); call_with( - &shim(py, "restore_python_root_modules")?, + &shim(py, UserCodeHost::RestorePythonRootModules)?, &[("saved_modules", saved)], )?; discovered_nodes?; @@ -432,7 +415,7 @@ pub(crate) fn discover_python_node_functions<'py>( let [loaders, tasks, assets, checks] = buckets.nodes; discovered( py, - "DiscoveredPythonNodeFunctions", + RecordId::DiscoveredPythonNodeFunctions, &[ ("loaders", tuple_of(py, loaders)?), ("tasks", tuple_of(py, tasks)?), @@ -467,7 +450,7 @@ pub(crate) fn discover_hook_functions<'py>( None => PyDict::new(py), }; let hook_files: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_python_hook_files")?, + ®istry::native(py, NativeAttr::DiscoverPythonHookFiles)?, &[("project_dir", project.path.clone())], )?; let mut hooks: Vec> = Vec::new(); @@ -475,7 +458,8 @@ pub(crate) fn discover_hook_functions<'py>( for hook_file in hook_files.try_iter()? { let hook_file: Bound<'py, PyDict> = hook_file?.cast_into()?; let file_path: Bound<'py, PyAny> = hook_file.as_any().get_item("file_path")?; - let module: Bound<'py, PyAny> = project.load("load_python_node_module", &file_path)?; + let module: Bound<'py, PyAny> = + project.load(UserCodeHost::LoadPythonNodeModule, &file_path)?; for function in module_functions(&module)? { let Some(read) = definition(&function, READ_HOOK)? else { continue; @@ -504,7 +488,7 @@ pub(crate) fn discover_hook_functions<'py>( arguments.set_item(field, value)?; } hooks.push( - imported(py, MODELS_MODULE, "DiscoveredHookFunction")? + registry::class(py, RecordId::DiscoveredHookFunction)? .call((), Some(&arguments))?, ); } @@ -535,7 +519,7 @@ pub(crate) fn discover_materialization_files<'py>( let mut files: Vec> = Vec::new(); for file_path in project.module_files("materializations", false)? { let materialize: Bound<'py, PyAny> = - project.load("load_materialize_function", &file_path)?; + project.load(UserCodeHost::LoadMaterializeFunction, &file_path)?; let usages: Bound<'py, PyAny> = if materialize.is_none() { tuple_of(py, Vec::new())? } else { @@ -543,7 +527,7 @@ pub(crate) fn discover_materialization_files<'py>( }; files.push(discovered( py, - "DiscoveredMaterializationFile", + RecordId::DiscoveredMaterializationFile, &[ ("file_path", file_path.clone()), ("relative_path", project.relative(&file_path)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs index f948566f07..a95fe3ec57 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs @@ -1,5 +1,7 @@ //! A discovered SQL model file as a native record whose Python attributes are built on demand. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyTypeError; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::sync::PyOnceLock; @@ -7,8 +9,6 @@ use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{FromPyObject, IntoPyObjectExt, pyclass, pymethods}; use sqlbuild_discovery::models::LineColumnSpan; -const SOURCE_LOCATION_MODULE: &str = "sqlbuild._native"; -const SOURCE_LOCATION_CLASS: &str = "SourceLocation"; const FIELD_NAMES: [&str; 10] = [ "file_path", "relative_path", @@ -62,9 +62,7 @@ impl Locations { Ok(self .projected .get_or_try_init(py, || { - let class: Bound<'py, PyAny> = py - .import(SOURCE_LOCATION_MODULE)? - .getattr(SOURCE_LOCATION_CLASS)?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let dict: Bound<'py, PyDict> = PyDict::new(py); for (name, span) in &self.rows { let location: Bound<'py, PyAny> = class.call1(( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs index 52a298c6c9..261782f5b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs @@ -17,17 +17,15 @@ use sqlbuild_pysource::udf_functions::main::udf_function::udf_function; use sqlbuild_pysource::udf_functions::models::{UdfFunction, UdfShape}; use crate::bindings::_helpers::boundary::panics::compiler_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::declaration_records::path_class; use crate::bindings::_helpers::discovery::project_files::path_object; use crate::bindings::_helpers::discovery::raised::{failure_error, read_error, stage_error}; use crate::bindings::_helpers::discovery::snapshot::{in_snapshot, project_root, project_tree}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::NativeProjectTree; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const PARSE_ERROR_CLASS: &str = "ModelSqlParseError"; const AST_MODULE: &str = "ast"; const DESCRIPTION_KEY: &str = "description"; const ENTRY_POINT_KEY: &str = "entry_point"; @@ -45,7 +43,7 @@ fn parse_error(source: &FunctionSource<'_>, detail: &str, cause: Option) let py: Python<'_> = source.file_path.py(); let raised = (|| { let message: String = format!("Python function '{}' {detail}", source.file_path.str()?); - imported(py, DISCOVERY_ERRORS_MODULE, PARSE_ERROR_CLASS)?.call1((message,)) + registry::error(py, ErrorId::ModelSqlParse)?.call1((message,)) })(); match raised { Ok(instance) => { @@ -128,8 +126,8 @@ fn unsupported_keys(source: &FunctionSource<'_>, function: &UdfFunction) -> PyRe let help: String = unsupported_keys_help(&keys, &supported); let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; - let error: Bound<'_, PyAny> = imported(py, DISCOVERY_ERRORS_MODULE, PARSE_ERROR_CLASS)? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::ModelSqlParse)?.call((message,), Some(&keywords))?; Err(PyErr::from_value(error)) } @@ -250,7 +248,7 @@ fn function_record<'py>( }; let (header_values, entry_point, body_python) = parsed_function(&source)?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredPythonFunctionFile")?, + ®istry::class(py, RecordId::DiscoveredPythonFunctionFile)?, &[ ("file_path", source.file_path.clone()), ("relative_path", source.relative_path.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs index 4dda7e1e1d..f126bcd051 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs @@ -10,48 +10,28 @@ use sqlbuild_discovery::models::{DiscoveryFailure, ReadFailure, StageFailure}; use sqlbuild_discovery::tree::models::ProjectTree; use crate::bindings::_helpers::discovery::project_files::path_object; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; const BUILTINS_MODULE: &str = "builtins"; const OS_MODULE: &str = "os"; const DECODE_ENCODING: &str = "utf-8"; /// The exception class of each native failure kind. -const FAILURE_CLASSES: [(&str, &str, &str); 13] = [ - ("model_sql", DISCOVERY_ERRORS_MODULE, "ModelSqlParseError"), - ( - "declaration", - DISCOVERY_ERRORS_MODULE, - "DeclarationParseError", - ), - ("sql_test", DISCOVERY_ERRORS_MODULE, "SqlTestParseError"), - ( - "sql_scenario", - DISCOVERY_ERRORS_MODULE, - "SqlScenarioParseError", - ), - ("schema", DISCOVERY_ERRORS_MODULE, "SchemaParseError"), - ("source", DISCOVERY_ERRORS_MODULE, "SourceParseError"), - ("project_path", DISCOVERY_ERRORS_MODULE, "ProjectPathError"), - ("sql_hook", DISCOVERY_ERRORS_MODULE, "SqlHookParseError"), - ("sql_audit", DISCOVERY_ERRORS_MODULE, "SqlAuditParseError"), - ( - "resource_identity", - IDENTITY_ERRORS_MODULE, - "ResourceIdentityError", - ), - ( - "conflict", - DISCOVERY_ERRORS_MODULE, - "DiscoveryConflictError", - ), - ("seed", DISCOVERY_ERRORS_MODULE, "SeedDiscoveryError"), - ( - "python_path", - DISCOVERY_ERRORS_MODULE, - "ProjectPythonPathError", - ), +const FAILURE_CLASSES: [(&str, ErrorId); 13] = [ + ("model_sql", ErrorId::ModelSqlParse), + ("declaration", ErrorId::DeclarationParse), + ("sql_test", ErrorId::SqlTestParse), + ("sql_scenario", ErrorId::SqlScenarioParse), + ("schema", ErrorId::SchemaParse), + ("source", ErrorId::SourceParse), + ("project_path", ErrorId::ProjectPath), + ("sql_hook", ErrorId::SqlHookParse), + ("sql_audit", ErrorId::SqlAuditParse), + ("resource_identity", ErrorId::ResourceIdentity), + ("conflict", ErrorId::DiscoveryConflict), + ("seed", ErrorId::SeedDiscovery), + ("python_path", ErrorId::ProjectPythonPath), ]; fn raised(result: PyResult>) -> PyErr { @@ -68,15 +48,13 @@ pub(crate) fn discovery_error( message: String, help: Option, ) -> PyErr { - let Some((_, module, class)) = FAILURE_CLASSES.iter().find(|(name, _, _)| *name == kind) else { + let Some((_, class)) = FAILURE_CLASSES.iter().find(|(name, _)| *name == kind) else { return PyKeyError::new_err(kind.to_owned()); }; raised((|| { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; - py.import(*module)? - .getattr(*class)? - .call((message,), Some(&keywords)) + registry::error(py, *class)?.call((message,), Some(&keywords)) })()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs index cf869ed15b..d18a1db2d6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs @@ -7,7 +7,7 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::project_config::{ load_local_config_file, load_project_config_file, }; @@ -24,10 +24,8 @@ use crate::bindings::_helpers::discovery::sql_test_records::{ }; use crate::bindings::_helpers::discovery::yaml_records::discover_source_files; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; const LOCAL_CONFIG_FILENAME: &str = "sqlbuild_local.toml"; const FALLBACK_PROJECT_NAME: &str = "project"; @@ -68,7 +66,7 @@ impl<'py> Faults<'py> { None => py.None().into_bound(py), }; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveryFileFault")?, + ®istry::class(py, RecordId::DiscoveryFileFault)?, &[ ("path", path), ("message", PyString::new(py, &message).into_any()), @@ -122,7 +120,7 @@ fn configs<'py>( PyString::new(py, FALLBACK_PROJECT_NAME).into_any() }; call_with( - &imported(py, CONTRACT_MODELS_MODULE, "ProjectConfig")?, + ®istry::class(py, RecordId::ProjectConfig)?, &[ ("name", name), ("adapter", PyString::new(py, FALLBACK_ADAPTER).into_any()), @@ -134,7 +132,7 @@ fn configs<'py>( faults, (&list, Some(LOCAL_CONFIG_FILENAME)), load_local_config_file(py, project_dir).map(|config| config.into_bound(py)), - || imported(py, CONTRACT_MODELS_MODULE, "LocalConfig")?.call0(), + || registry::class(py, RecordId::LocalConfig)?.call0(), )?; Ok((project_config, local_config, list)) } @@ -229,9 +227,9 @@ fn tolerant_discovery<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult = - imported(py, MODELS_MODULE, "DiscoveredProjectInputs")?.call((), Some(&inputs))?; + registry::class(py, RecordId::DiscoveredProjectInputs)?.call((), Some(&inputs))?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "TolerantScopeDiscovery")?, + ®istry::class(py, RecordId::TolerantScopeDiscovery)?, &[ ("discovered_inputs", discovered_inputs), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs index 2913333a00..21d9b44d28 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs @@ -1,6 +1,5 @@ //! SQL `TEST(...)` blocks validated natively and built as discovery records. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::IntoPyObjectExt; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -10,21 +9,16 @@ use sqlbuild_model_config::sql_tests::models::{SqlTestBlock, SqlTestCase}; use sqlbuild_model_config::sql_values::models::{RawKey, RawSqlValue}; use crate::bindings::_helpers::contracts::sql_values::{SqlValueClasses, raw_value, sql_value}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const SQL_VALUE_TYPES_MODULE: &str = "sqlbuild._native"; pub(crate) type NativeBlock<'py> = (Bound<'py, PyDict>, String, Bound<'py, PyAny>); fn parse_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - py.import(ERRORS_MODULE)? - .getattr("SqlTestParseError")? - .call1((message,))?, + registry::error(py, ErrorId::SqlTestParse)?.call1((message,))?, )) } @@ -50,9 +44,11 @@ fn case_record<'py>( [name.into_bound_py_any(py)?, sql_value(value, classes)?], )?); } - py.import(NATIVE_RECORDS_MODULE)? - .getattr("DiscoveredSqlTestCase")? - .call1((case.name.as_str(), PyTuple::new(py, values)?, case_index)) + registry::class(py, RecordId::DiscoveredSqlTestCase)?.call1(( + case.name.as_str(), + PyTuple::new(py, values)?, + case_index, + )) } fn block_record<'py>( @@ -61,13 +57,12 @@ fn block_record<'py>( block: SqlTestBlock, classes: &SqlValueClasses<'py>, ) -> PyResult> { - let models = py.import(MODELS_MODULE)?; - let kind = py.import(SQL_VALUE_TYPES_MODULE)?.getattr("SqlValueKind")?; + let kind = registry::class(py, RecordId::SqlValueKind)?; let parameters = block .parameters .iter() .map(|parameter| { - models.getattr("SqlTestParameterDeclaration")?.call1(( + registry::class(py, RecordId::SqlTestParameterDeclaration)?.call1(( parameter.name.as_str(), kind.call1((parameter.value_type,))?, parameter.nullable, @@ -96,7 +91,7 @@ fn block_record<'py>( keywords.set_item("cursor_start", block.cursor_start)?; keywords.set_item("cursor_end", block.cursor_end)?; keywords.set_item("sql_body_span", &native.2)?; - shape_class(models.as_any(), "DiscoveredSqlTestBlock")?.call((), Some(&keywords)) + registry::class(py, RecordId::DiscoveredSqlTestBlock)?.call((), Some(&keywords)) } /// Each `(header, sql_body, span)` block of a SQL test file as a `DiscoveredSqlTestBlock`. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs index 624322f883..3e0f807641 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs @@ -27,10 +27,11 @@ use crate::bindings::_helpers::discovery::sorted_keys::sorted_keys; use crate::bindings::_helpers::discovery::sql_test_blocks::{ sql_test_block_records, validate_block_names, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::authored_values::map_to_python; use crate::bindings::models::NativeProjectTree; -const MODELS_MODULE: &str = "sqlbuild._native"; const SQL_TESTS_OWNERSHIP_ROOT: &str = "tests/unit"; const SQL_SCENARIOS_OWNERSHIP_ROOT: &str = "tests/scenarios"; const PATHLIB_MODULE: &str = "pathlib"; @@ -127,7 +128,7 @@ fn test_file<'py>( let (file_path, relative_path) = paths; let py: Python<'py> = file_path.py(); call_with( - &imported(py, MODELS_MODULE, "DiscoveredSqlTestFile")?, + ®istry::class(py, RecordId::DiscoveredSqlTestFile)?, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), @@ -180,7 +181,7 @@ fn scenario_file<'py>( )); } call_with( - &imported(py, MODELS_MODULE, "DiscoveredSqlScenarioFile")?, + ®istry::class(py, RecordId::DiscoveredSqlScenarioFile)?, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs index 1f360f8c84..66b0625650 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs @@ -16,14 +16,13 @@ use sqlbuild_model_config::errors::{ConfigError, ErrorClass}; use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::contract_classes::yaml_entry_classes; use crate::bindings::_helpers::model_config::header_metadata::{ContractClasses, audit_tuple}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERROR: &str = "ResourceIdentityError"; /// The directory whose schema YAML files may declare seeds. const SEEDS_DIRECTORY: &str = "seeds"; -const SCHEMA_PARSE_ERROR: &str = "SchemaParseError"; -const SOURCE_PARSE_ERROR: &str = "SourceParseError"; +const SCHEMA_PARSE_ERROR: ErrorId = ErrorId::SchemaParse; +const SOURCE_PARSE_ERROR: ErrorId = ErrorId::SourceParse; /// The Python classes parsed YAML entries become. #[derive(FromPyObject)] @@ -46,12 +45,10 @@ pub(crate) struct YamlClasses<'py> { } /// The Python error `error` raises: a resource identity error, else the file kind's parse error. -fn parse_error(py: Python<'_>, error: ConfigError, parse_class: &str) -> PyResult { +fn parse_error(py: Python<'_>, error: ConfigError, parse_class: ErrorId) -> PyResult { let class: Bound<'_, PyAny> = match error.class { - ErrorClass::ResourceIdentity => { - py.import(IDENTITY_ERRORS_MODULE)?.getattr(IDENTITY_ERROR)? - } - _ => py.import(DISCOVERY_ERRORS_MODULE)?.getattr(parse_class)?, + ErrorClass::ResourceIdentity => registry::error(py, ErrorId::ResourceIdentity)?, + _ => registry::error(py, parse_class)?, }; let keywords: Bound<'_, PyDict> = PyDict::new(py); if let Some(help) = error.help { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs index 2f34461ba9..a70c58da43 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs @@ -29,11 +29,10 @@ use crate::bindings::_helpers::discovery::snapshot::{ use crate::bindings::_helpers::discovery::yaml_entries::{ parse_schema_document, parse_sources_document, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::{NativeProjectTree, YamlDiscoveryRequest}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; /// The most YAML bytes one native load reads before its files are built and released. const YAML_BATCH_BYTES: u64 = 4 * 1024 * 1024; const PATHLIB_MODULE: &str = "pathlib"; @@ -224,7 +223,7 @@ pub(crate) fn discover_source_files<'py>( project_dir: project_dir.clone(), on_fault, }; - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "DiscoveredSourceFile")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSourceFile)?; let mut records: Vec> = Vec::new(); load_files( project_dir, @@ -284,7 +283,7 @@ pub(crate) fn discover_schema_files<'py>( if paths.is_empty() { return record_tuple(py, Vec::new()); } - let class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "DiscoveredSchemaFile")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSchemaFile)?; let mut records: Vec> = Vec::new(); load_files( project_dir, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs index cd153535c8..7e9756e372 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs @@ -1,13 +1,5 @@ //! Python modules, thread names and wait limits of native lifecycle-event export. -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.event_exporting.exceptions"; -pub(crate) const SEVERITY_MODULE: &str = "sqlbuild._native"; -pub(crate) const INVOKE_LIFECYCLE_MODULE: &str = - "sqlbuild.runtime.event_exporting.main._invoke_lifecycle_event_sink"; -pub(crate) const RUN_OUTPUT_SINKS_MODULE: &str = - "sqlbuild.runtime.event_exporting.main._run_command_output_sinks"; -pub(crate) const SESSION_MODULE: &str = "sqlbuild.provider.main.session"; -pub(crate) const OUTPUT_MODELS_MODULE: &str = "sqlbuild.runtime.output_capture.models"; pub(crate) const SCOPE_CONTEXT_VARIABLE: &str = "sqlbuild_event_exporter_command_scope"; pub(crate) const DISPATCHER_THREAD_NAME: &str = "sqlbuild-event-exporter-dispatcher"; pub(crate) const NOTIFIER_THREAD_NAME: &str = "sqlbuild-event-exporter-notifier"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs index 676c3bccf0..1158baef24 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs @@ -29,8 +29,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::event_exporting::constants::{ DISPATCHER_THREAD_NAME, EXIT_JOIN_SECONDS, HEALTH_THREAD_NAME, JOIN_GRACE_SECONDS, NO_SECONDS, - NOTIFIER_THREAD_NAME, OUTPUT_SINK_THREAD_NAME, POLL_SECONDS, RUN_OUTPUT_SINKS_MODULE, - WORKER_ATTRIBUTE, + NOTIFIER_THREAD_NAME, OUTPUT_SINK_THREAD_NAME, POLL_SECONDS, WORKER_ATTRIBUTE, }; use crate::bindings::_helpers::event_exporting::models::{ BoundEventExporter, EventExportSummary, EventExporterFailure, @@ -39,6 +38,8 @@ use crate::bindings::_helpers::event_exporting::values::{ export_policy, input_error, severity_from, state_error, }; use crate::bindings::_helpers::event_exporting::workers::{DispatcherWorker, WorkerRole}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::native_threads::constants::OUTPUT_SUPPRESSED_ATTRIBUTE; use crate::bindings::_helpers::native_threads::exit_signal::{ interpreter_exiting, signal_interpreter_exit, @@ -510,7 +511,7 @@ impl Shared { &[ ( "target", - imported(py, RUN_OUTPUT_SINKS_MODULE, "run_command_output_sinks")?, + host::entry(py, UserCodeHost::RunCommandOutputSinks)?, ), ("kwargs", arguments.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs index fbfb35564c..adcf228f24 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs @@ -11,12 +11,13 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, context_variable, imported, -}; -use crate::bindings::_helpers::event_exporting::constants::{ - OUTPUT_MODELS_MODULE, SCOPE_CONTEXT_VARIABLE, SESSION_MODULE, + KeywordArguments, call_with, context_variable, }; +use crate::bindings::_helpers::event_exporting::constants::SCOPE_CONTEXT_VARIABLE; use crate::bindings::_helpers::event_exporting::models::BoundEventExporter; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; static CURRENT_SCOPE: PyOnceLock> = PyOnceLock::new(); @@ -103,8 +104,7 @@ fn bindings<'py>( .into_any(), ); } - let sink_class: Bound<'py, PyAny> = - imported(py, OUTPUT_MODELS_MODULE, "BoundCommandOutputSink")?; + let sink_class: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BoundCommandOutputSink)?; let mut sinks: Vec> = Vec::new(); for sink in command_output_sinks.try_iter()? { let sink: Bound<'py, PyAny> = sink?; @@ -162,7 +162,7 @@ impl EventExporterCommandScope { return self.bind_dispatcher(py, (empty.clone(), empty)); } let session: Bound<'_, PyAny> = call_with( - &imported(py, SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[ ("discovered_providers", providers), ("allow_shared", PyBool::new(py, false).to_owned().into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs index c9a7c33915..8b99a62e64 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs @@ -12,8 +12,10 @@ use sqlbuild_core::event_export::models::{ExportPolicy, ExportSeverity, Exporter use sqlbuild_core::lifecycle_events::constants::CURRENT_LIFECYCLE_EVENT_SCHEMA_VERSION; use sqlbuild_core::lifecycle_events::main::catalog::catalog; -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::event_exporting::constants::{EXCEPTIONS_MODULE, SEVERITY_MODULE}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `BoundEventExporter` parameters, in positional order. const BOUND_EXPORTER_PARAMETERS: [&str; 5] = [ @@ -28,8 +30,8 @@ const REQUIRED_BOUND_EXPORTER_PARAMETERS: usize = 3; static POLICIES: PyOnceLock> = PyOnceLock::new(); /// The exception class `name` of the event exporter exceptions module, raised with `message`. -pub(crate) fn exporter_error(py: Python<'_>, name: &str, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, name).and_then(|class| class.call1((message,))) { +pub(crate) fn exporter_error(py: Python<'_>, name: UserCodeHost, message: &str) -> PyErr { + match host::entry(py, name).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -37,12 +39,12 @@ pub(crate) fn exporter_error(py: Python<'_>, name: &str, message: &str) -> PyErr /// `EventExporterStateError(message)`. pub(crate) fn state_error(py: Python<'_>, message: &str) -> PyErr { - exporter_error(py, "EventExporterStateError", message) + exporter_error(py, UserCodeHost::EventExporterStateError, message) } /// `EventExporterInputError(message)`. pub(crate) fn input_error(py: Python<'_>, message: &str) -> PyErr { - exporter_error(py, "EventExporterInputError", message) + exporter_error(py, UserCodeHost::EventExporterInputError, message) } /// The Python `EventExportSeverity` member of `severity`. @@ -50,7 +52,7 @@ pub(crate) fn severity_object( py: Python<'_>, severity: ExportSeverity, ) -> PyResult> { - imported(py, SEVERITY_MODULE, "EventExportSeverity")?.call1((severity.value(),)) + registry::class(py, RecordId::EventExportSeverity)?.call1((severity.value(),)) } /// An `EventExportSeverity` or its string value, as `EventExportSeverity(value)` accepts it. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs index 2f4e6f1cce..1cde088682 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs @@ -13,13 +13,15 @@ use sqlbuild_core::event_export::models::{EventQueue, ExportSnapshot, QueuedEven use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::event_exporting::constants::{ - INVOCATION_THREAD_PREFIX, INVOKE_LIFECYCLE_MODULE, NO_SECONDS, POLL_SECONDS, + INVOCATION_THREAD_PREFIX, NO_SECONDS, POLL_SECONDS, }; use crate::bindings::_helpers::event_exporting::dispatcher::{Exporter, Queued, Shared}; use crate::bindings::_helpers::event_exporting::models::{ EventExportSummary, EventExporterFailure, }; use crate::bindings::_helpers::event_exporting::values::error_type_of; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::native_threads::exit_signal::interpreter_exiting; /// Which long-lived dispatcher thread a worker runs. @@ -199,7 +201,7 @@ impl DispatcherWorker { &[ ( "target", - imported(py, INVOKE_LIFECYCLE_MODULE, "invoke_lifecycle_event_sink")?, + host::entry(py, UserCodeHost::InvokeLifecycleEventSink)?, ), ("kwargs", arguments.into_any()), ("name", PyString::new(py, &name).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs index 5e51a40232..9dcb146d8e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs @@ -14,37 +14,30 @@ use sqlbuild_state::execution_history::types::UtcTime; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::cursor::values::{DATETIME_MODULE, local_date_time}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.execution_history.exceptions"; -pub(crate) const MODELS_MODULE: &str = "sqlbuild.runtime.execution_history.models"; -const TYPES_MODULE: &str = "sqlbuild.runtime.execution_history.types"; -const EVENT_MODELS_MODULE: &str = "sqlbuild.runtime.observability.models"; -const EVENT_TO_JSON_MODULE: &str = "sqlbuild.runtime.observability.main.lifecycle_event_to_json"; -const EVENT_FROM_JSON_MODULE: &str = - "sqlbuild.runtime.observability.main.lifecycle_event_from_json"; const UTC_SUFFIX: &str = "Z"; const UTC_OFFSET: &str = "+00:00"; const DEFAULT_SCHEMA_VERSION: i64 = 1; const UTC_OFFSET_SECONDS: f64 = 0.0; -fn exception_name(kind: HistoryErrorKind) -> &'static str { +fn exception_class(kind: HistoryErrorKind) -> ExecutorHost { match kind { - HistoryErrorKind::Storage => "ExecutionHistoryStorageError", - HistoryErrorKind::IntegrityConflict => "IntegrityConflictError", - HistoryErrorKind::InvalidEvent => "InvalidEventError", - HistoryErrorKind::InvalidCursor => "InvalidCursorError", - HistoryErrorKind::InvalidFilter => "InvalidFilterError", - HistoryErrorKind::InvalidLimit => "InvalidLimitError", - HistoryErrorKind::UnsupportedSchemaVersion => "UnsupportedSchemaVersionError", - HistoryErrorKind::ProjectionConsistency => "ProjectionConsistencyError", + HistoryErrorKind::Storage => ExecutorHost::ExecutionHistoryStorageError, + HistoryErrorKind::IntegrityConflict => ExecutorHost::IntegrityConflictError, + HistoryErrorKind::InvalidEvent => ExecutorHost::InvalidEventError, + HistoryErrorKind::InvalidCursor => ExecutorHost::InvalidCursorError, + HistoryErrorKind::InvalidFilter => ExecutorHost::InvalidFilterError, + HistoryErrorKind::InvalidLimit => ExecutorHost::InvalidLimitError, + HistoryErrorKind::UnsupportedSchemaVersion => ExecutorHost::UnsupportedSchemaVersionError, + HistoryErrorKind::ProjectionConsistency => ExecutorHost::ProjectionConsistencyError, } } /// The execution history exception for a rejection of `kind`. pub(crate) fn rejection(py: Python<'_>, kind: HistoryErrorKind, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, exception_name(kind)) - .and_then(|class| class.call1((message,))) - { + match host::entry(py, exception_class(kind)).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -68,8 +61,12 @@ pub(crate) fn checked(py: Python<'_>, result: Result) -> PyR } /// Whether `error` is an instance of the execution history exception named `name`. -pub(crate) fn history_error_of_kind(py: Python<'_>, error: &PyErr, name: &str) -> PyResult { - Ok(error.is_instance(py, &imported(py, EXCEPTIONS_MODULE, name)?)) +pub(crate) fn history_error_of_kind( + py: Python<'_>, + error: &PyErr, + name: ExecutorHost, +) -> PyResult { + Ok(error.is_instance(py, &host::entry(py, name)?)) } /// `value` when it is an `int` other than `bool` that fits, as `isinstance(value, int)` checks. @@ -190,7 +187,7 @@ pub(crate) fn parsed_status(py: Python<'_>, text: &str) -> PyResult { if let Some(status) = known { return Ok(status); } - imported(py, TYPES_MODULE, "RunStatus")?.call1((text,))?; + host::entry(py, ExecutorHost::RunStatus)?.call1((text,))?; Ok(RunStatus::Unknown) } @@ -211,11 +208,7 @@ pub(crate) fn run_filter_spec(value: &Bound<'_, PyAny>) -> PyResult) -> PyResult { - event.is_instance(&imported( - event.py(), - EVENT_MODELS_MODULE, - "LifecycleEvent", - )?) + event.is_instance(&host::entry(event.py(), ExecutorHost::LifecycleEvent)?) } /// The `event_id` a known event carries or an opaque envelope holds as a string. @@ -232,19 +225,14 @@ fn raw_field<'py>(event: &Bound<'py, PyAny>, name: &str) -> PyResult) -> PyResult { - imported(event.py(), EVENT_TO_JSON_MODULE, "lifecycle_event_to_json")? + host::entry(event.py(), ExecutorHost::LifecycleEventToJson)? .call1((event,))? .extract() } /// `lifecycle_event_from_json(payload)`. pub(crate) fn event_from_json<'py>(payload: &Bound<'py, PyAny>) -> PyResult> { - imported( - payload.py(), - EVENT_FROM_JSON_MODULE, - "lifecycle_event_from_json", - )? - .call1((payload,)) + host::entry(payload.py(), ExecutorHost::LifecycleEventFromJson)?.call1((payload,)) } /// An opaque envelope's `occurred_at` when it is ISO text with a zero UTC offset. @@ -345,9 +333,9 @@ fn optional_python_time<'py>( /// The `RunRecord` of a projected run. pub(crate) fn run_record<'py>(py: Python<'py>, run: &RunRow) -> PyResult> { - let status = imported(py, TYPES_MODULE, "RunStatus")?.call1((run.status.value(),))?; + let status = host::entry(py, ExecutorHost::RunStatus)?.call1((run.status.value(),))?; call_with( - &imported(py, MODELS_MODULE, "RunRecord")?, + &host::entry(py, ExecutorHost::RunRecord)?, &[ ("run_id", run.run_id.clone().into_bound_py_any(py)?), ( @@ -413,7 +401,7 @@ pub(crate) fn stored_event<'py>( payload: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, MODELS_MODULE, "StoredEvent")?, + &host::entry(py, ExecutorHost::StoredEvent)?, &[ ("storage_order", storage_order.into_bound_py_any(py)?), ("cursor", cursor.into_bound_py_any(py)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs index b55b674307..7482d7d61b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs @@ -42,13 +42,15 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::execution_history::appends::prepared_batch; use crate::bindings::_helpers::execution_history::conversion::{ - MODELS_MODULE, checked, event_filter_spec, exact_int, history_error_of_kind, new_namespace, - now, optional_text, parsed_status, projection_facts, rejection, run_filter_spec, run_records, + checked, event_filter_spec, exact_int, history_error_of_kind, new_namespace, now, + optional_text, parsed_status, projection_facts, rejection, run_filter_spec, run_records, run_rows, storage_error, stored_event, text_of, utc, utc_time, }; use crate::bindings::_helpers::execution_history::statements::{ column, execute, execute_plain, execute_query, fetch_all, fetch_one, tuple_of, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const PSYCOPG_MODULE: &str = "psycopg"; const PSYCOPG_ROWS_MODULE: &str = "psycopg.rows"; @@ -58,7 +60,7 @@ const CONNECT_FAILED: &str = "cannot connect to PostgreSQL execution history; ve const READ_FAILED: &str = "PostgreSQL execution history read failed"; const METADATA_INVALID: &str = "PostgreSQL execution history schema metadata is invalid"; const NAMESPACE_MISSING: &str = "PostgreSQL execution history cursor namespace is missing"; -const STORAGE_ERROR: &str = "ExecutionHistoryStorageError"; +const STORAGE_ERROR: ExecutorHost = ExecutorHost::ExecutionHistoryStorageError; /// The `psycopg.Error` class. fn driver_error_class(py: Python<'_>) -> PyResult> { @@ -667,7 +669,7 @@ impl PostgresExecutionHistory { None => py.None().into_bound(py), }; call_with( - &imported(py, MODELS_MODULE, "EventPage")?, + &host::entry(py, ExecutorHost::EventPage)?, &[ ("records", tuple_of(py, records)?.into_any()), ("next_cursor", next_cursor), @@ -734,7 +736,7 @@ impl PostgresExecutionHistory { None => py.None().into_bound(py), }; call_with( - &imported(py, MODELS_MODULE, "RunPage")?, + &host::entry(py, ExecutorHost::RunPage)?, &[ ("records", run_records(py, &runs)?.into_any()), ("next_cursor", next_cursor), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs index 06767860f5..503d21f626 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs @@ -50,6 +50,8 @@ use crate::bindings::_helpers::execution_history::conversion::{ use crate::bindings::_helpers::execution_history::statements::{ column, execute, execute_plain, execute_query, fetch_one, locked, tuple_of, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const SQLITE_MODULE: &str = "sqlite3"; const PATHLIB_MODULE: &str = "pathlib"; @@ -57,8 +59,8 @@ const THREADING_MODULE: &str = "threading"; const MILLIS_PER_SECOND: f64 = 1000.0; const CLOSED: &str = "SQLite execution history storage is closed"; const NOT_AWARE: &str = "SQLite history timestamp is not timezone-aware"; -const INTEGRITY_CONFLICT: &str = "IntegrityConflictError"; -const STORAGE_ERROR: &str = "ExecutionHistoryStorageError"; +const INTEGRITY_CONFLICT: ExecutorHost = ExecutorHost::IntegrityConflictError; +const STORAGE_ERROR: ExecutorHost = ExecutorHost::ExecutionHistoryStorageError; /// `Path(path).parent.mkdir(mode=0o700, parents=True, exist_ok=True)` for a file-backed history. fn prepare_history_directory(path: &str) -> std::io::Result<()> { @@ -634,11 +636,7 @@ impl SqliteExecutionHistory { None => py.None().into_bound(py), }; crate::bindings::_helpers::compile_driver::python_calls::call_with( - &imported( - py, - crate::bindings::_helpers::execution_history::conversion::MODELS_MODULE, - "EventPage", - )?, + &host::entry(py, ExecutorHost::EventPage)?, &[ ("records", tuple_of(py, records)?.into_any()), ("next_cursor", next_cursor), @@ -707,11 +705,7 @@ impl SqliteExecutionHistory { None => py.None().into_bound(py), }; crate::bindings::_helpers::compile_driver::python_calls::call_with( - &imported( - py, - crate::bindings::_helpers::execution_history::conversion::MODELS_MODULE, - "RunPage", - )?, + &host::entry(py, ExecutorHost::RunPage)?, &[ ("records", run_records(py, &runs)?.into_any()), ("next_cursor", next_cursor), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs index 0718d4e3f7..c02be409fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs @@ -42,10 +42,9 @@ use crate::bindings::_helpers::compile_command::constants::{ REUSE_DISABLE_ENV_VAR, TRACKED_ENVIRONMENT_PREFIXES, UNTRACKED_ENVIRONMENT_NAMES, }; use crate::bindings::_helpers::frontier::stage_capture::CAPTURE_DIR_ENV_VAR; +use crate::bindings::_helpers::native_host::constants::CUSTOM_HOST_LAUNCH_MODULE; use crate::bindings::_helpers::result_output::event_writer::RESULT_PATH_ENV; -use crate::bindings::_helpers::rules::constants::{ - CUSTOM_HOST_LAUNCH_MODULE, CUSTOM_HOST_RUNTIME_VERSION, -}; +use crate::bindings::_helpers::rules::constants::CUSTOM_HOST_RUNTIME_VERSION; use crate::bindings::_helpers::semantic_shapes::shapes::{VALID_FROM_DEFAULT, VALID_TO_DEFAULT}; use crate::bindings::_helpers::state_rows::constants::MIGRATION_WRITE_ATTEMPTS; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs index b764698f81..cc20c8e1c3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs @@ -2,6 +2,8 @@ use std::ffi::CString; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::{PyUnicodeDecodeError, PyValueError}; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, @@ -14,8 +16,6 @@ use sqlbuild_analysis::fingerprint_storage::main::latest_index::latest_index; use sqlbuild_analysis::fingerprint_storage::main::selected_positions::selected_positions; use sqlbuild_analysis::fingerprint_storage::models::{FingerprintIdentity, LatestIndex}; -const MODELS_MODULE: &str = "sqlbuild._native"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATETIME_MODULE: &str = "datetime"; const BINASCII_MODULE: &str = "binascii"; const BASE64_MODULE: &str = "base64"; @@ -23,11 +23,7 @@ const TIMESTAMP_COLUMN: usize = 11; /// `FingerprintInputError(message)`. pub(crate) fn fingerprint_input_error(py: Python<'_>, message: &str) -> PyErr { - match py - .import(EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("FingerprintInputError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::FingerprintInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -40,8 +36,7 @@ pub(crate) fn fingerprint_set_from_rows<'py>( qualified_name: &str, rows: &Bound<'py, PyAny>, ) -> PyResult> { - let models: Bound<'py, PyModule> = py.import(MODELS_MODULE)?; - let fingerprint_class: Bound<'py, PyAny> = models.getattr("Fingerprint")?; + let fingerprint_class: Bound<'py, PyAny> = registry::class(py, RecordId::Fingerprint)?; let from_iso: Bound<'py, PyAny> = py .import(DATETIME_MODULE)? .getattr("datetime")? @@ -55,7 +50,7 @@ pub(crate) fn fingerprint_set_from_rows<'py>( qualified_name, )?); } - indexed_set(&models, schema, &fingerprints) + indexed_set(py, schema, &fingerprints) } /// `select_latest_fingerprints`: an unfiltered latest read narrowed as a filtered read would be. @@ -67,10 +62,9 @@ fn select_latest_fingerprints<'py>( node_names: Vec, filtered_node_types: Vec, ) -> PyResult> { - let models: Bound<'py, PyModule> = py.import(MODELS_MODULE)?; let schema: String = fingerprint_set.getattr("schema")?.extract()?; if node_names.is_empty() && filtered_node_types.is_empty() { - return indexed_set(&models, &schema, &[]); + return indexed_set(py, &schema, &[]); } let by_identity: Bound<'py, PyAny> = fingerprint_set.getattr("fingerprints_by_identity")?; let latest: Vec> = if by_identity.is_truthy()? { @@ -89,15 +83,14 @@ fn select_latest_fingerprints<'py>( .into_iter() .map(|position| latest[position].clone()) .collect(); - indexed_set(&models, &schema, &selected) + indexed_set(py, &schema, &selected) } fn indexed_set<'py>( - models: &Bound<'py, PyModule>, + py: Python<'py>, schema: &str, fingerprints: &[Bound<'py, PyAny>], ) -> PyResult> { - let py: Python<'py> = models.py(); let identities: Vec<(String, String)> = fingerprints .iter() .map(identity_of) @@ -115,7 +108,7 @@ fn indexed_set<'py>( kwargs.set_item("schema", schema)?; kwargs.set_item("fingerprints", by_name)?; kwargs.set_item("fingerprints_by_identity", by_identity)?; - models.getattr("FingerprintSet")?.call((), Some(&kwargs)) + registry::class(py, RecordId::FingerprintSet)?.call((), Some(&kwargs)) } fn identity_of(fingerprint: &Bound<'_, PyAny>) -> PyResult<(String, String)> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs index 745865ab83..9532cbf23b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs @@ -9,19 +9,16 @@ use sqlbuild_analysis::sql_tokens::main::normalized_query_text::normalized_query use sqlbuild_core::panics::main::is_compiler_panic::is_compiler_panic; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; /// `QueryFingerprintError(message)`, or the compiler error a native panic becomes. fn query_fingerprint_error(py: Python<'_>, message: String) -> PyErr { if is_compiler_panic(&message) { return compiler_error(message); } - match py - .import(EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("QueryFingerprintError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::QueryFingerprint).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs index b4b8774651..01ea896109 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs @@ -20,9 +20,11 @@ use crate::bindings::_helpers::fingerprints::storage_sql::{ fingerprint_insert_batches, fingerprint_insert_sql, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::FINGERPRINT_TABLE_NAME; -const MODELS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_TYPES_MODULE: &str = "sqlbuild.adapter.contract.types"; const TIME_MODULE: &str = "time"; const LOGGING_MODULE: &str = "logging"; const LOGGER_NAME: &str = "sqlbuild.fingerprints"; @@ -79,7 +81,7 @@ fn create_sql( render_framework_type: &Bound<'_, PyAny>, ) -> PyResult { let py: Python<'_> = render_framework_type.py(); - let framework: Bound<'_, PyAny> = py.import(ADAPTER_TYPES_MODULE)?.getattr("FrameworkType")?; + let framework: Bound<'_, PyAny> = host::entry(py, AdapterHost::FrameworkType)?; let string_type: String = render_framework_type .call1((framework.getattr("STRING")?,))? .extract()?; @@ -269,9 +271,7 @@ fn empty_set<'py>(py: Python<'py>, schema: &str) -> PyResult> let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("schema", schema)?; keywords.set_item("fingerprints", PyDict::new(py))?; - py.import(MODELS_MODULE)? - .getattr("FingerprintSet")? - .call((), Some(&keywords)) + registry::class(py, RecordId::FingerprintSet)?.call((), Some(&keywords)) } /// `FingerprintInputError` for an unreadable table, caused by the warehouse error. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs index d72d384f86..18734a6846 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs @@ -26,8 +26,9 @@ use crate::bindings::_helpers::format_command::run::{FormatOutcome, FormatReques use crate::bindings::_helpers::format_command::selection::{LintInputs, resolve_lint_inputs}; use crate::bindings::_helpers::frontier::encoder::emit_error; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; const SQL_ANALYSIS_SETTING_KEY: &str = "sql_analysis"; const DEFAULT_DIALECT: &str = "generic"; const START_MESSAGE: &str = "Formatting SQL START"; @@ -115,7 +116,7 @@ fn resolve_inputs<'py>( } if fix { resolved.discovered = Some(call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs index 851ffca206..a2b573c76b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs @@ -9,10 +9,9 @@ use pyo3::types::PyString; use sqlbuild_rules::lint_text::main::resolve_format_settings::resolve_format_settings; use sqlbuild_rules::lint_text::models::FormatSettings; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Format settings and whether lint suppressions are allowed. pub(crate) struct FormatConfig { @@ -30,7 +29,7 @@ pub(crate) fn format_config( resolve_format_settings(Path::new(&directory)).map_err(PyValueError::new_err)?; let allow_suppressions: bool = if settings.has_project_config { call_with( - &imported(py, NATIVE_MODULE, "load_rules_config")?, + ®istry::native(py, NativeAttr::LoadRulesConfig)?, &[("project_dir", project_dir.call_method0("resolve")?)], )? .getattr("allow_exceptions")? @@ -52,7 +51,7 @@ pub(crate) fn lint_config<'py>( ) -> PyResult> { let settings: &FormatSettings = &config.settings; call_with( - &imported(py, LINT_MODELS_MODULE, "LintConfig")?, + ®istry::class(py, RecordId::LintConfig)?, &[ ( "line_width", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs index 7c4995d502..e1df98565a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs @@ -7,16 +7,14 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use sqlbuild_core::format_command::main::collect_lint_files::collect_lint_files; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::description_inputs::externally_described_models; use crate::bindings::_helpers::format_command::files::{ FormatChange, file_dictionary, overlaid, persist_changes, sorted_items, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const FIXES_MODULE: &str = "sqlbuild._native"; -const FIXTURE_AUTOFIX_MODULE: &str = "sqlbuild._native"; -const LINT_ERRORS_MODULE: &str = "sqlbuild._native"; const DESCRIPTION_PRESENT: &str = "description-present"; /// What one format run formats and how. @@ -42,8 +40,8 @@ pub(crate) struct FormatOutcome<'py> { pub(crate) final_contents: Bound<'py, PyDict>, } -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, NATIVE_MODULE, name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } /// Fixture-null rewrites proven safe by the contracts, when fixture inputs are known. @@ -57,7 +55,7 @@ fn fixture_autofix<'py>( return Ok(PyDict::new(py)); }; Ok(call_with( - &imported(py, FIXTURE_AUTOFIX_MODULE, "format_redundant_fixture_nulls")?, + ®istry::native(py, NativeAttr::FormatRedundantFixtureNulls)?, &[ ("files", files.clone().into_any()), ("project_dir", request.project_dir.clone()), @@ -78,7 +76,7 @@ fn format_bodies<'py>( .into_iter() .map(|(path, contents)| Ok(PyTuple::new(py, [path, contents])?.into_any())) .collect::>()?; - let result: Bound<'py, PyAny> = native(py, "format_lint_sql_bodies")?.call1(( + let result: Bound<'py, PyAny> = native(py, NativeAttr::FormatLintSqlBodies)?.call1(( PyList::new(py, items)?, request.project_dir, request.config, @@ -103,12 +101,12 @@ fn apply_fixes<'py>( } let current: Bound<'py, PyDict> = overlaid(files, &updated)?; call_with( - &native(py, "prepare_native_header_cache")?, + &native(py, NativeAttr::PrepareNativeHeaderCache)?, &[("files", current.clone().into_any())], )?; for (path, contents) in sorted_items(¤t)? { let result: Bound<'py, PyAny> = call_with( - &native(py, "format_native_headers")?, + &native(py, NativeAttr::FormatNativeHeaders)?, &[ ("contents", contents.clone()), ("file_path", path.clone()), @@ -139,7 +137,7 @@ fn apply_fixes<'py>( updated.update(final_native.as_mapping())?; } let result: Bound<'py, PyAny> = call_with( - &native(py, "reject_unparseable_header_rewrites")?, + &native(py, NativeAttr::RejectUnparseableHeaderRewrites)?, &[ ("updated", updated.into_any()), ("config", request.config.clone()), @@ -163,11 +161,11 @@ fn lint_final_contents<'py>( for (path, contents) in sorted_items(files)? { let final_contents: Bound<'py, PyAny> = updated.get_item(&path)?.unwrap_or(contents); let headers: Bound<'py, PyAny> = call_with( - &native(py, "scan_headers")?, + &native(py, NativeAttr::ScanHeaders)?, &[("contents", final_contents.clone())], )?; let found: Bound<'py, PyAny> = call_with( - &native(py, "lint_native_headers")?, + &native(py, NativeAttr::LintNativeHeaders)?, &[ ("contents", final_contents), ("file_path", path), @@ -222,7 +220,7 @@ fn plan_rule_fixes<'py>( ) -> PyResult<(Bound<'py, PyDict>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py: Python<'py> = files.py(); let result: Bound<'py, PyAny> = call_with( - &imported(py, FIXES_MODULE, "plan_rule_fixes")?, + ®istry::native(py, NativeAttr::PlanRuleFixes)?, &[ ("project_dir", request.project_dir.clone()), ("files", files.clone().into_any()), @@ -261,7 +259,7 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult
{ - return Err(imported(py, LINT_ERRORS_MODULE, "LintError")? + return Err(registry::error(py, ErrorId::Lint)? .call1(("fixture-null formatting requires the project adapter",)) .map(pyo3::PyErr::from_value)?); } @@ -273,7 +271,7 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult = file_dictionary(py, collected)?; let safe: Bound<'py, PyAny> = call_with( - &native(py, "safe_format_files")?, + &native(py, NativeAttr::SafeFormatFiles)?, &[ ("files", files.clone().into_any()), ("config", request.config.clone()), @@ -310,7 +308,7 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult = overlaid(&files, &updated)?; let allow_suppressions: Bound<'py, PyAny> = request.config.getattr("allow_suppressions")?; - let violations: Vec> = native(py, "apply_lint_suppressions")? + let violations: Vec> = native(py, NativeAttr::ApplyLintSuppressions)? .call1((violations, &final_contents, allow_suppressions))? .extract()?; Ok(FormatOutcome { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs index a46e43cc78..65ca9574ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs @@ -16,14 +16,10 @@ use sqlbuild_rules::lint_text::main::scan_fixture_null_candidates::scan_fixture_ use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PLANNER_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const UNIT_TEST_SELECTORS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; const SELECTION_CODE: &str = "S007"; /// The adapter, the selected files when a selector scopes the run, and the discovered inputs. @@ -44,7 +40,7 @@ fn planner_error(py: Python<'_>, message: &str, code: &str) -> PyErr { let raised = (|| -> PyResult> { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; - imported(py, PLANNER_ERRORS_MODULE, "PlannerInputError")?.call((message,), Some(&keywords)) + registry::error(py, ErrorId::PlannerInput)?.call((message,), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), @@ -77,7 +73,7 @@ fn discovered_adapter<'py>( ) -> PyResult> { let py: Python<'py> = project_dir.py(); let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -156,14 +152,14 @@ fn model_paths( return Ok(paths); } let graph: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_graph")?, + ®istry::native(py, NativeAttr::BuildProjectGraph)?, &[ ("discovered_inputs", scope.discovered.clone()), ("adapter", scope.adapter.clone()), ], )?; let keys: Bound<'_, PyAny> = call_with( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", selectors(py, select)?), @@ -239,22 +235,22 @@ fn selected_contract_inputs<'py>( referenced_model_names: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_dir.py(); - let native = |name: &str| imported(py, NATIVE_MODULE, name); + let native = |name: NativeAttr| registry::native(py, name); let project_config: Bound<'py, PyAny> = - native("load_project_config_file")?.call1((project_dir,))?; + native(NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; let local_config: Bound<'py, PyAny> = - native("load_local_config_file")?.call1((project_dir,))?; + native(NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?; let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); let scope: Bound<'py, PyAny> = call_with( - &native("directory_snapshot_scope")?, + &native(NativeAttr::DirectorySnapshotScope)?, &[("project_dir", project_dir.clone())], )?; within(&scope, |_| { - let project_only = |name: &str| -> PyResult> { + let project_only = |name: NativeAttr| -> PyResult> { call_with(&native(name)?, &[("project_dir", project_dir.clone())]) }; call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &[ ("project_config", project_config), ("local_config", local_config), @@ -262,7 +258,7 @@ fn selected_contract_inputs<'py>( ( "model_files", call_with( - &native("discover_model_files")?, + &native(NativeAttr::DiscoverModelFiles)?, &[ ("project_dir", project_dir.clone()), ("extract_implicit_alias_columns", disabled.clone()), @@ -273,14 +269,20 @@ fn selected_contract_inputs<'py>( ), ( "model_schema_files", - project_only("discover_model_schema_files")?, + project_only(NativeAttr::DiscoverModelSchemaFiles)?, + ), + ( + "schema_files", + project_only(NativeAttr::DiscoverSchemaFiles)?, + ), + ( + "source_files", + project_only(NativeAttr::DiscoverSourceFiles)?, ), - ("schema_files", project_only("discover_schema_files")?), - ("source_files", project_only("discover_source_files")?), ( "test_files", call_with( - &native("discover_test_files")?, + &native(NativeAttr::DiscoverTestFiles)?, &[ ("project_dir", project_dir.clone()), ("selected_paths", selected_test_paths), @@ -326,7 +328,7 @@ pub(crate) fn resolve_lint_inputs<'py>( } let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("sql_analysis_enabled_override", disabled.clone()), @@ -334,7 +336,7 @@ pub(crate) fn resolve_lint_inputs<'py>( ], )?; call_with( - &imported(py, UNIT_TEST_SELECTORS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", selectors(py, select)?), ("exclude", selectors(py, exclude)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs index cb2627250b..91ceb4d2b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs @@ -20,15 +20,11 @@ use crate::bindings::_helpers::freshness::observation::Observer; use crate::bindings::_helpers::freshness::state_io::{StateRead, read_latest}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const CONNECTION_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; -const UPSTREAM_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::SOURCE_FRESHNESS_TABLE_NAME as STATE_TABLE_NAME; const STALE_REQUIRES_STATE_MESSAGE: &str = "freshness --fail-on-stale requires --state"; const STALE_REQUIRES_STATE_CODE: &str = "C238"; @@ -52,7 +48,7 @@ fn user_error(py: Python<'_>, (message, code): (&str, &str)) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -76,7 +72,7 @@ fn source_names_for_keys( let py: Python<'_> = graph.py(); let source_type: Bound<'_, PyAny> = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.getattr("SOURCE")?; - let expand: Bound<'_, PyAny> = imported(py, UPSTREAM_MODULE, "expand_project_upstream_keys")?; + let expand: Bound<'_, PyAny> = registry::native(py, NativeAttr::ExpandProjectUpstreamKeys)?; let upstream_deps: Bound<'_, PyAny> = graph.getattr("upstream_deps")?; for key in keys.try_iter()? { let key: Bound<'_, PyAny> = key?; @@ -103,7 +99,7 @@ fn graph_keys<'py>( ) -> PyResult> { let py: Python<'py> = graph.py(); let empty: Bound<'py, PyAny> = PyTuple::empty(py).into_any(); - imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?.call( + registry::native(py, NativeAttr::SelectProjectGraph)?.call( (), Some(&keywords( py, @@ -167,18 +163,17 @@ fn previous_records<'py>( ) }) .collect::>()?; - let lookup: Bound<'py, PyAny> = imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")? - .call( - (), - Some(&keywords( - py, - &[ - ("adapter", adapter), - ("connection", connection), - ("locations", &PyTuple::new(py, locations)?.into_any()), - ], - )?), - )?; + let lookup: Bound<'py, PyAny> = host::entry(py, AdapterHost::BuildRelationLookup)?.call( + (), + Some(&keywords( + py, + &[ + ("adapter", adapter), + ("connection", connection), + ("locations", &PyTuple::new(py, locations)?.into_any()), + ], + )?), + )?; let read: StateRead<'py> = StateRead { connection: connection.clone(), execute: adapter.getattr("execute")?, @@ -212,7 +207,7 @@ fn observe( ) -> PyResult> { let py: Python<'_> = adapter.py(); let connection_config: Bound<'_, PyAny> = - imported(py, CONNECTION_MODULE, "resolve_project_connection_config")?.call( + registry::native(py, NativeAttr::ResolveProjectConnectionConfig)?.call( (), Some(&keywords( py, @@ -266,7 +261,7 @@ fn write_json_output( let options: Bound<'_, PyDict> = keywords(py, &[("json_output_path", json_output_path)])?; options.set_item("payload", format!("{payload}\n"))?; options.set_item("json_output", json_output)?; - imported(py, NATIVE_MODULE, "write_execution_json_output")?.call((), Some(&options))?; + registry::native(py, NativeAttr::WriteExecutionJsonOutput)?.call((), Some(&options))?; Ok(()) } @@ -286,11 +281,11 @@ fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult directory if !directory.is_none() => directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; - let discovered: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "discover_project_inputs")? + let discovered: Bound<'_, PyAny> = registry::native(py, NativeAttr::DiscoverProjectInputs)? .call((), Some(&keywords(py, &[("project_dir", &project_dir)])?))?; let select: Bound<'_, PyAny> = request.getattr("select")?; let exclude: Bound<'_, PyAny> = request.getattr("exclude")?; - imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?.call( + registry::native(py, NativeAttr::RejectUnitTestSelectors)?.call( (), Some(&keywords( py, @@ -302,7 +297,7 @@ fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult )?), )?; let adapter_name: Bound<'_, PyAny> = - imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?.call( + registry::native(py, NativeAttr::ResolveEffectiveAdapterName)?.call( (), Some(&keywords( py, @@ -313,7 +308,7 @@ fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult )?), )?; let adapter: Bound<'_, PyAny> = resolve_cli_adapter(&adapter_name, &project_dir)?; - let graph: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "build_project_graph")?.call( + let graph: Bound<'_, PyAny> = registry::native(py, NativeAttr::BuildProjectGraph)?.call( (), Some(&keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs index d98cb58a74..8baefb7939 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs @@ -15,13 +15,14 @@ use crate::bindings::_helpers::freshness::observation::{ Observer, observation_record, observation_source, observe_adapter_batch, observe_configured, strategy, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::state_rows::freshness::{age_policy, equivalent}; use sqlbuild_core::freshness_text::main::freshness_status::freshness_status; use sqlbuild_core::freshness_text::models::{ FreshnessSourceResult, FreshnessStatus, StateComparison, }; -const RUN_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.run_scope"; const ADAPTER_STRATEGY: &str = "adapter"; const UNAVAILABLE_REASON: &str = "unavailable"; const OBSERVATION_RUN_ID: &str = "freshness"; @@ -276,10 +277,7 @@ pub(crate) fn observe_freshness( let Some(run_id) = run_id else { return observe_sources(observer, &chosen, state); }; - let scope: Bound<'_, PyAny> = py - .import(RUN_SCOPE_MODULE)? - .getattr("run_scope")? - .call1((run_id,))?; + let scope: Bound<'_, PyAny> = host::entry(py, ExecutorHost::RunScope)?.call1((run_id,))?; scope.call_method0("__enter__")?; match observe_sources(observer, &chosen, state) { Ok(results) => { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs index 531c0b7a83..92dcadea18 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs @@ -8,16 +8,19 @@ use sqlbuild_state::freshness::models::QueryShape; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; use crate::bindings::_helpers::freshness::support::{ - ADAPTER_MODELS, ADAPTER_TYPES, CONTRACT_MODELS, CONTRACT_TYPES, FRESHNESS_MODELS, - LIFECYCLE_MODULE, OBSERVABILITY_MODELS, OBSERVATION_ERROR, exception_text, freshness_error, - presumed_utc_datetime, presumed_utc_value, text, + OBSERVATION_ERROR, exception_text, freshness_error, presumed_utc_datetime, presumed_utc_value, + text, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::constants::{DATA_VERSION_FIELD, OBSERVED_AT_FIELD}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::references::source_relations::render_source_relation; use crate::bindings::_helpers::state_rows::freshness::{normalize, version_hash}; use crate::bindings::_helpers::state_rows::timestamps::{in_utc, offset_aware}; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const ADAPTER_STRATEGY: &str = "adapter"; const COLUMN_STRATEGY: &str = "column"; const DEFAULT_STRATEGY: &str = "sql"; @@ -62,7 +65,7 @@ pub(crate) fn observation_source<'py>( return Ok(None); } let config: Bound<'py, PyAny> = call_with( - &imported(py, CONTRACT_MODELS, "SourceFreshnessConfig")?, + ®istry::class(py, RecordId::SourceFreshnessConfig)?, &[("strategy", adapter_strategy(py)?)], )?; replaced_source(source, config).map(Some) @@ -78,7 +81,7 @@ fn replaced_source<'py>( } fn adapter_strategy(py: Python<'_>) -> PyResult> { - imported(py, CONTRACT_TYPES, "SourceFreshnessStrategy")?.getattr("ADAPTER") + registry::class(py, RecordId::SourceFreshnessStrategy)?.getattr("ADAPTER") } /// `OperationLifecycle(operation_kind="freshness", ...)` for one observation phase. @@ -88,7 +91,7 @@ fn lifecycle<'py>( item_count: Option, ) -> PyResult> { let attributes: Bound<'py, PyAny> = call_with( - &imported(py, OBSERVABILITY_MODELS, "OperationAttributes")?, + &host::entry(py, ExecutorHost::OperationAttributes)?, &[ ("phase", text(py, "observe")), ("strategy", text(py, strategy)), @@ -106,7 +109,7 @@ fn lifecycle<'py>( } options.push(("attributes", attributes)); call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &options, ) } @@ -117,11 +120,7 @@ fn observation<'py>( (data_version, observed_at): (Bound<'py, PyAny>, Bound<'py, PyAny>), ) -> PyResult> { call_with( - &imported( - source_name.py(), - NATIVE_RECORDS, - "SourceFreshnessObservation", - )?, + ®istry::class(source_name.py(), RecordId::SourceFreshnessObservation)?, &[ ("source_name", source_name.clone()), ("strategy", strategy), @@ -309,12 +308,12 @@ pub(crate) fn unknown<'py>( ) -> PyResult> { let py: Python<'py> = source_name.py(); call_with( - &imported(py, NATIVE_RECORDS, "SourceFreshnessUnknown")?, + ®istry::class(py, RecordId::SourceFreshnessUnknown)?, &[ ("source_name", source_name.clone()), ( "reason", - imported(py, NATIVE_RECORDS, "SourceFreshnessUnknownReason")?.getattr(reason)?, + registry::class(py, RecordId::SourceFreshnessUnknownReason)?.getattr(reason)?, ), ("message", message), ], @@ -329,7 +328,7 @@ fn metadata_unknown<'py>( let py: Python<'py> = source_name.py(); let name: String = source_name.str()?.extract()?; let unavailable: Bound<'py, PyAny> = - imported(py, ADAPTER_TYPES, "TableFreshnessStatus")?.getattr("UNAVAILABLE")?; + host::entry(py, AdapterHost::TableFreshnessStatus)?.getattr("UNAVAILABLE")?; if metadata.getattr("status")?.eq(unavailable)? { let message: Bound<'py, PyAny> = metadata.getattr("message")?; let message: String = if message.is_truthy()? { @@ -362,7 +361,7 @@ fn metadata_observation<'py>( source_name, ( adapter_strategy(py)?, - imported(py, CONTRACT_TYPES, "SourceFreshnessValueKind")? + registry::class(py, RecordId::SourceFreshnessValueKind)? .call1((metadata.getattr("value_kind")?,))?, ), ( @@ -406,7 +405,7 @@ pub(crate) fn observe_adapter_batch<'py>( ) -> PyResult> { let py: Python<'py> = observer.adapter.py(); let batch_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS, "AdapterSourceFreshnessBatch")?; + registry::class(py, RecordId::AdapterSourceFreshnessBatch)?; if sources.is_empty() { return batch_class.call0(); } @@ -434,10 +433,10 @@ fn observe_adapter_batch_inner<'py>( ) -> PyResult> { let py: Python<'py> = observer.adapter.py(); let batch_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS, "AdapterSourceFreshnessBatch")?; + registry::class(py, RecordId::AdapterSourceFreshnessBatch)?; let unknown_sources: Bound<'py, PyDict> = PyDict::new(py); let requests: Bound<'py, PyDict> = PyDict::new(py); - let request_class: Bound<'py, PyAny> = imported(py, ADAPTER_MODELS, "TableFreshnessRequest")?; + let request_class: Bound<'py, PyAny> = host::entry(py, AdapterHost::TableFreshnessRequest)?; for source in sources { let name: Bound<'py, PyAny> = source.getattr("name")?; if let Some(message) = unsupported_message(&observer.adapter, source)? { @@ -471,7 +470,7 @@ fn observe_adapter_batch_inner<'py>( ("requests", PyTuple::new(py, unique.keys())?.into_any()), ], )?; - let metadata_class: Bound<'py, PyAny> = imported(py, ADAPTER_MODELS, "TableFreshnessMetadata")?; + let metadata_class: Bound<'py, PyAny> = host::entry(py, AdapterHost::TableFreshnessMetadata)?; let observations: Bound<'py, PyDict> = PyDict::new(py); for (name, request) in requests.iter() { let missing: Bound<'py, PyAny> = call_with( @@ -526,7 +525,7 @@ pub(crate) fn observation_record<'py>( &data_version.extract::()?, )?; call_with( - &imported(py, FRESHNESS_MODELS, "SourceFreshnessRecord")?, + ®istry::class(py, RecordId::SourceFreshnessRecord)?, &[ ("source_name", source_name), ("target_database", source.getattr("database")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs index 637249be3c..5f7d89cc16 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs @@ -15,27 +15,28 @@ use crate::bindings::_helpers::freshness::observation::{ strategy, unknown, }; use crate::bindings::_helpers::freshness::state_io::{StateRead, read_latest}; -use crate::bindings::_helpers::freshness::support::{ - ADAPTER_EXCEPTIONS, EXCEPTIONS_MODULE, OBSERVATION_ERROR, text, -}; +use crate::bindings::_helpers::freshness::support::text; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_identity::graph::build_downstream_model_name_closure; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::freshness::{age_policy, equivalent}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; const ADAPTER_STRATEGY: &str = "adapter"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; const NO_CONFIG_MESSAGE: &str = "no freshness config and adapter metadata unavailable"; /// The message of an `AdapterUserError` or a source freshness observation error, if it is one. fn handled_message<'py>(py: Python<'py>, error: &PyErr) -> PyResult>> { let value: Bound<'py, PyAny> = error.value(py).clone().into_any(); - if value.is_instance(&imported(py, ADAPTER_EXCEPTIONS, "AdapterUserError")?)? { + if value.is_instance(&host::entry(py, AdapterHost::AdapterUserError)?)? { return value.getattr("message").map(Some); } - if value.is_instance(&imported(py, EXCEPTIONS_MODULE, OBSERVATION_ERROR)?)? { + if value.is_instance(®istry::native( + py, + NativeAttr::SourceFreshnessObservationError, + )?)? { return Ok(Some(value.str()?.into_any())); } Ok(None) @@ -126,7 +127,7 @@ fn adapter_batch<'py>( unknown_sources.set_item(&name, unknown(&name, ("ERROR", message.clone()))?)?; } call_with( - &imported(py, NATIVE_RECORDS, "AdapterSourceFreshnessBatch")?, + ®istry::class(py, RecordId::AdapterSourceFreshnessBatch)?, &[("unknown", unknown_sources.into_any())], ) } @@ -224,11 +225,9 @@ pub(crate) fn build_direct_source_freshness_planning_result<'py>( let Some(message) = handled_message(py, &error)? else { return Err(error); }; - let is_observation_error: bool = !error.value(py).is_instance(&imported( - py, - ADAPTER_EXCEPTIONS, - "AdapterUserError", - )?)?; + let is_observation_error: bool = !error + .value(py) + .is_instance(&host::entry(py, AdapterHost::AdapterUserError)?)?; if is_observation_error && configuration_error(&message.extract::()?) { return Err(error); } @@ -271,11 +270,7 @@ pub(crate) fn build_direct_source_freshness_planning_result<'py>( } let unknown_names: Bound<'py, PyDict> = sorted_dict(py, &unknown_sources)?; call_with( - &imported( - py, - NATIVE_RECORDS_MODULE, - "DirectSourceFreshnessPlanningResult", - )?, + ®istry::class(py, RecordId::DirectSourceFreshnessPlanningResult)?, &[ ( "observed_records", @@ -308,7 +303,7 @@ fn downstream_models<'py>( ) -> PyResult> { let py: Python<'py> = source_name.py(); let key: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS, "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ( "resource_type", @@ -351,7 +346,7 @@ pub(crate) fn build_direct_source_freshness_propagation_result<'py>( unknown_sources.set_item(name, models)?; } let error_status: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS, "SourceFreshnessAgeStatus")?.getattr("ERROR")?; + registry::class(py, RecordId::SourceFreshnessAgeStatus)?.getattr("ERROR")?; for (identity, status) in source_freshness .getattr("age_statuses")? .downcast::()? @@ -366,7 +361,7 @@ pub(crate) fn build_direct_source_freshness_propagation_result<'py>( errors.set_item(identity, models)?; } call_with( - &imported(py, NATIVE_RECORDS, "DirectSourceFreshnessPropagationResult")?, + ®istry::class(py, RecordId::DirectSourceFreshnessPropagationResult)?, &[ ("changed_source_model_names", changed.into_any()), ("unknown_source_model_names", unknown_sources.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs index 8ee5b46af2..71dc47541b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs @@ -7,16 +7,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; - -/// Module of the execution status enum. -const SCHEDULING_TYPES_MODULE: &str = "sqlbuild.executor.scheduling.types"; -/// Module of the run-scope context manager. -const RUN_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.run_scope"; -/// Module of the operation lifecycle context manager. -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -/// Module of the operation attributes record. -const OBSERVABILITY_MODELS_MODULE: &str = "sqlbuild.runtime.observability.models"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Observed records grouped by `(database, schema)` in first-seen order. type RecordsByLocation<'py> = Vec<( @@ -28,7 +22,7 @@ type RecordsByLocation<'py> = Vec<( fn successful_model_names<'py>(result: &Bound<'py, PyAny>) -> PyResult>> { let py: Python<'py> = result.py(); let success: Bound<'py, PyAny> = - imported(py, SCHEDULING_TYPES_MODULE, "ExecutionStatus")?.getattr("SUCCESS")?; + host::entry(py, ExecutorHost::ExecutionStatus)?.getattr("SUCCESS")?; let mut names: Vec> = Vec::new(); for model_result in result.getattr("model_results")?.try_iter()? { let model_result: Bound<'py, PyAny> = model_result?; @@ -157,7 +151,7 @@ fn eligible_records<'py>( /// `SourceFreshnessRenderers` bound to `adapter`. fn renderers<'py>(adapter: &Bound<'py, PyAny>) -> PyResult> { call_with( - &imported(adapter.py(), NATIVE_MODULE, "SourceFreshnessRenderers")?, + ®istry::class(adapter.py(), RecordId::SourceFreshnessRenderers)?, &[ ( "render_qualified_name", @@ -190,8 +184,8 @@ fn write_records<'py>( grouped: &RecordsByLocation<'py>, ) -> PyResult<()> { let py: Python<'py> = adapter.py(); - let cost_context: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "CostContext")?; - let write: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "write_source_freshness_records")?; + let cost_context: Bound<'py, PyAny> = registry::native(py, NativeAttr::CostContext)?; + let write: Bound<'py, PyAny> = registry::native(py, NativeAttr::WriteSourceFreshnessRecords)?; for ((database, schema), records) in grouped { let mut by_source: Vec<(Bound<'py, PyAny>, Vec>)> = Vec::new(); for record in records { @@ -251,20 +245,19 @@ pub(crate) fn append_freshness_records<'py>( if record_count == 0 { return Ok(()); } - let run_scope: Bound<'py, PyAny> = - imported(py, RUN_SCOPE_MODULE, "run_scope")?.call1((run_id,))?; + let run_scope: Bound<'py, PyAny> = host::entry(py, ExecutorHost::RunScope)?.call1((run_id,))?; within(&run_scope, |_| { let metadata: Bound<'py, PyDict> = PyDict::new(py); metadata.set_item("item_count", record_count)?; let attributes: Bound<'py, PyAny> = call_with( - &imported(py, OBSERVABILITY_MODELS_MODULE, "OperationAttributes")?, + &host::entry(py, ExecutorHost::OperationAttributes)?, &[ ("phase", PyString::new(py, "publish").into_any()), ("target_kind", PyString::new(py, "state_batch").into_any()), ], )?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "freshness").into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs index 308c987873..1d6b3465e9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs @@ -13,13 +13,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; use crate::bindings::_helpers::freshness::support::{ - FRESHNESS_MODELS, INPUT_ERROR, exception_text, freshness_error, optional_str, - presumed_utc_datetime, text, + INPUT_ERROR, exception_text, freshness_error, optional_str, presumed_utc_datetime, text, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::state_tables::state_table_create_sql; use crate::bindings::_helpers::state_rows::timestamps::date_time_typed; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const MISSING_SCHEMA_MESSAGE: &str = "source freshness table requires a target schema"; /// The qualified state table name the adapter renders for one target schema. @@ -110,7 +110,7 @@ pub(crate) struct StateRead<'py> { fn empty_set<'py>(schema: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = schema.py(); call_with( - &imported(py, NATIVE_RECORDS, "SourceFreshnessSet")?, + ®istry::class(py, RecordId::SourceFreshnessSet)?, &[ ("schema", schema.clone()), ("records", PyDict::new(py).into_any()), @@ -174,7 +174,7 @@ freshness table to regenerate source freshness state." records.set_item(record.getattr("identity")?, record)?; } call_with( - &imported(py, NATIVE_RECORDS, "SourceFreshnessSet")?, + ®istry::class(py, RecordId::SourceFreshnessSet)?, &[("schema", schema.clone()), ("records", records.into_any())], ) } @@ -195,7 +195,7 @@ fn row_record<'py>(row: &Bound<'py, PyAny>) -> PyResult> { let optional = |index: usize| -> PyResult> { optional_str(&row.get_item(index)?) }; call_with( - &imported(py, FRESHNESS_MODELS, "SourceFreshnessRecord")?, + ®istry::class(py, RecordId::SourceFreshnessRecord)?, &[ ("source_name", field(0)?), ("target_database", optional(1)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs index e448a148da..2f7e75a44e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs @@ -6,32 +6,23 @@ use pyo3::types::{PyDict, PyString}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::timestamps::{date_time_typed, in_utc, offset_aware}; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -pub(crate) const FRESHNESS_MODELS: &str = "sqlbuild._native"; -pub(crate) const CONTRACT_MODELS: &str = "sqlbuild._native"; -pub(crate) const CONTRACT_TYPES: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_MODELS: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const ADAPTER_TYPES: &str = "sqlbuild.adapter.contract.types"; -pub(crate) const ADAPTER_EXCEPTIONS: &str = "sqlbuild.adapter.contract.exceptions"; -pub(crate) const LIFECYCLE_MODULE: &str = - "sqlbuild.runtime.observability.classes.operation_lifecycle"; -pub(crate) const OBSERVABILITY_MODELS: &str = "sqlbuild.runtime.observability.models"; -pub(crate) const OBSERVATION_ERROR: &str = "SourceFreshnessObservationError"; -pub(crate) const INPUT_ERROR: &str = "SourceFreshnessInputError"; +pub(crate) const OBSERVATION_ERROR: NativeAttr = NativeAttr::SourceFreshnessObservationError; +pub(crate) const INPUT_ERROR: NativeAttr = NativeAttr::SourceFreshnessInputError; -/// `exceptions.(message)` as a raisable error, with an optional `__cause__`. +/// The native error `class(message)` as a raisable error, with an optional `__cause__`. pub(crate) fn freshness_error( py: Python<'_>, - (class, message): (&str, &str), + (class, message): (NativeAttr, &str), cause: Option, ) -> PyErr { - let error: PyErr = - match imported(py, EXCEPTIONS_MODULE, class).and_then(|class| class.call1((message,))) { - Ok(error) => PyErr::from_value(error), - Err(error) => return error, - }; + let error: PyErr = match registry::native(py, class).and_then(|class| class.call1((message,))) { + Ok(error) => PyErr::from_value(error), + Err(error) => return error, + }; if let Some(cause) = cause { error.set_cause(py, Some(cause)); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs b/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs index 36a0a2c68d..086309d934 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs @@ -4,13 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::frontier::stage_capture::write_stage_capture; - -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const RUN_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.classes.macro_bridge"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Produce the frontier object for `until`, capturing it on request. #[pyfunction] @@ -46,10 +45,10 @@ fn native_frontier<'py>( return native_stage.call0(); } let inputs_stage: Bound<'py, PyAny> = - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")?.getattr("COMPILE_PROJECT_INPUTS")?; + registry::class(py, RecordId::CompilerStage)?.getattr("COMPILE_PROJECT_INPUTS")?; if until.is(&inputs_stage) { return call_with( - &imported(py, RUN_BRIDGE_MODULE, "run_with_macro_bridge")?, + &host::entry(py, UserCodeHost::RunWithMacroBridge)?, &[("stage", python_stage.clone())], ); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs b/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs index 2f12ff6312..7ce3002cfb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs @@ -9,7 +9,7 @@ use pyo3::types::{ PyStringMethods, PyTuple, }; -use sqlbuild_cache::hosted_shapes::constants::HOSTED_SHAPE_TYPES; +use sqlbuild_cache::hosted_shapes::main::captured_shape_name::captured_shape_name; use sqlbuild_cache::stage_capture::constants::{ CANONICAL_TYPES, DECODED_SEQUENCES, OMITTED_ATTRIBUTES, PROJECTED_ATTRIBUTES, RESERVED_KEYS, SHARED_MARKER, SHARED_MIN_CHARS, SKIPPED_SLOTS, UNORDERED_ATTRIBUTES, @@ -66,7 +66,7 @@ fn lookup<'a, T: Copy>(table: &'a [(&'a str, T)], name: &str) -> Option { /// The `module:qualname` a captured class is written as, natively hosted shapes keeping their Python name. fn captured_type_name(class: &Bound<'_, PyAny>) -> PyResult { let name: String = qualified_name(class)?; - Ok(lookup(&HOSTED_SHAPE_TYPES, &name).map_or(name, str::to_owned)) + Ok(captured_shape_name(&name).map_or(name, str::to_owned)) } /// `module:qualname` of a callable or class, as Python's `qualified_name` writes it. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs index b8bdd2f925..d81e6923b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs @@ -8,12 +8,10 @@ use crate::bindings::_helpers::analysis::explicit_references::{ GateRow, HookRow, native_execution_gate_edges, }; use crate::bindings::_helpers::graph::keys::KeyCache; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const GRAPH_MODELS_MODULE: &str = "sqlbuild._native"; -const GRAPH_TYPES_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; /// The model config keys whose entries are hooks, in lifecycle order. const MODEL_HOOK_KEYS: [&str; 2] = ["pre_hooks", "post_hooks"]; @@ -38,9 +36,7 @@ fn attached_audit_gate_edges<'py>( ) -> PyResult> { let (gates, _): (Vec, Vec) = native_execution_gate_edges(project)?; let cache: KeyCache<'py> = KeyCache::new(py)?; - let edge_type: Bound<'py, PyAny> = py - .import(GRAPH_MODELS_MODULE)? - .getattr("AttachedAuditGateEdge")?; + let edge_type: Bound<'py, PyAny> = registry::class(py, RecordId::AttachedAuditGateEdge)?; let mut edges: Vec> = Vec::with_capacity(gates.len()); for (audit_name, target, gated, read) in gates { edges.push(keyword_call( @@ -65,8 +61,8 @@ fn hook_read_edges<'py>( ) -> PyResult> { let (_, hooks): (Vec, Vec) = native_execution_gate_edges(project)?; let cache: KeyCache<'py> = KeyCache::new(py)?; - let edge_type: Bound<'py, PyAny> = py.import(GRAPH_MODELS_MODULE)?.getattr("HookReadEdge")?; - let hook_type: Bound<'py, PyAny> = py.import(GRAPH_TYPES_MODULE)?.getattr("HookReadType")?; + let edge_type: Bound<'py, PyAny> = registry::class(py, RecordId::HookReadEdge)?; + let hook_type: Bound<'py, PyAny> = registry::class(py, RecordId::HookReadType)?; let path_type: Bound<'py, PyAny> = py.import(PATHLIB_MODULE)?.getattr("Path")?; let mut edges: Vec> = Vec::with_capacity(hooks.len()); for (hook_name, gated, read, kind, path) in hooks { @@ -95,9 +91,7 @@ fn model_python_hook_names<'py>( py: Python<'py>, model: &Bound<'py, PyAny>, ) -> PyResult> { - let entry_type: Bound<'_, PyAny> = py - .import(DISCOVERY_MODELS_MODULE)? - .getattr("PythonHookEntry")?; + let entry_type: Bound<'_, PyAny> = registry::class(py, RecordId::PythonHookEntry)?; let values: Bound<'_, PyAny> = model.getattr("config")?.getattr("values")?; let mut names: Vec = Vec::new(); for hook_key in MODEL_HOOK_KEYS { @@ -130,8 +124,7 @@ fn sql_ref_key<'py>(py: Python<'py>, reference: &Bound<'py, PyAny>) -> PyResult< .getattr("CompiledResourceType")? .call1((reference.getattr("kind")?.getattr("value")?,))?; keyword_call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ("resource_type", resource_type), ("name", reference.getattr("name")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs index 87705c188c..2f4b5e27eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs @@ -1,9 +1,10 @@ //! `CompiledObjectKey`s for native `(resource type, name)` pairs, converted once per pair. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; /// One `CompiledObjectKey` per native pair, so shared keys convert once. @@ -16,9 +17,7 @@ pub(crate) struct KeyCache<'py> { impl<'py> KeyCache<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - key_type: py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompiledObjectKey")?, + key_type: registry::class(py, RecordId::CompiledObjectKey)?, resource_type: py .import(COMPILE_TYPES_MODULE)? .getattr("CompiledResourceType")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs index 1acdd6cbbc..dc4c16371c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs @@ -2,13 +2,14 @@ use std::collections::{BTreeSet, HashMap}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::python_plan::main::run_lifecycle::run_lifecycle; use sqlbuild_analysis::python_plan::models::{LifecycleNode, RunLifecycle}; -const PYTHON_NODE_MODELS_MODULE: &str = "sqlbuild._native"; const EXTERNAL_CONNECTION_MODE: &str = "external"; /// `build_python_sql_run_lifecycle(*, selection, python_graph)`: a `PythonSqlRunLifecyclePlan`. @@ -60,9 +61,7 @@ fn build_python_sql_run_lifecycle<'py>( "read_side_python_node_names", PyFrozenSet::new(py, lifecycle.read_side_names)?, )?; - py.import(PYTHON_NODE_MODELS_MODULE)? - .getattr("PythonSqlRunLifecyclePlan")? - .call((), Some(&arguments)) + registry::class(py, RecordId::PythonSqlRunLifecyclePlan)?.call((), Some(&arguments)) } fn lifecycle_node(node: &Bound<'_, PyAny>) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs index c5cf6c65d9..e011134253 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs @@ -1,6 +1,5 @@ //! The executable Python-node graph built from discovered project inputs. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -8,17 +7,15 @@ use sqlbuild_analysis::python_selection::main::python_node_graph_indexes::python use sqlbuild_analysis::python_selection::models::{InventoryIndexes, InventoryNode}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::imported_modules::provider_usages; use crate::bindings::_helpers::graph::python_selection::resolve_python_sql_run_selection; use crate::bindings::_helpers::graph::views::compiled_graph_keys; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const USER_INVENTORY_MODULE: &str = "sqlbuild.compiler.python_nodes._helpers.user_inventory"; -const NODE_MODELS_MODULE: &str = "sqlbuild._native"; -const NODE_TYPES_MODULE: &str = "sqlbuild.compiler.python_nodes.types"; -const SQL_REF_MODULE: &str = "sqlbuild.python_nodes.models"; const NAME_KEY: &str = "name"; /// One discovered function collection, its node kind and whether it carries documentation. @@ -57,12 +54,12 @@ fn kind_parts<'py>( ) -> PyResult<(Bound<'py, PyDict>, &'static str, Bound<'py, PyAny>)> { let config: Bound<'py, PyDict> = PyDict::new(py); let get = |name: &str| attribute_value(discovered, name); - let metadata = |class: &str, fields: &[&str]| -> PyResult> { + let metadata = |class: RecordId, fields: &[&str]| -> PyResult> { let pairs: Vec<(&str, Bound<'py, PyAny>)> = fields .iter() .map(|field| Ok((*field, get(field)?))) .collect::>()?; - call_with(&shape_class_at(py, NODE_MODELS_MODULE, class)?, &pairs) + call_with(®istry::class(py, class)?, &pairs) }; Ok(match kind { "LOADER" => { @@ -73,7 +70,7 @@ fn kind_parts<'py>( config.set_item("unique_key", get("unique_key")?)?; config.set_item("write_strategy", optional_value(&get("write_strategy")?)?)?; let record = metadata( - "DiscoveredPythonLoaderMetadata", + RecordId::DiscoveredPythonLoaderMetadata, &[ "destination", "write_strategy", @@ -89,20 +86,20 @@ fn kind_parts<'py>( "TASK" => ( config, "task", - metadata("DiscoveredPythonTaskMetadata", &["retry"])?, + metadata(RecordId::DiscoveredPythonTaskMetadata, &["retry"])?, ), "ASSET" => { config.set_item("columns", get("columns")?)?; config.set_item("column_lineage", get("column_lineage")?)?; let record = metadata( - "DiscoveredPythonAssetMetadata", + RecordId::DiscoveredPythonAssetMetadata, &["columns", "column_lineage", "retry"], )?; (config, "asset", record) } _ => { config.set_item("severity", get("severity")?.getattr("value")?)?; - let record = metadata("DiscoveredPythonCheckMetadata", &["severity"])?; + let record = metadata(RecordId::DiscoveredPythonCheckMetadata, &["severity"])?; (config, "check", record) } }) @@ -180,8 +177,7 @@ fn upstream_names( name, )?; } - let dependency_key: Bound<'_, PyAny> = - imported(py, USER_INVENTORY_MODULE, "python_node_dependency_key")?; + let dependency_key: Bound<'_, PyAny> = host::entry(py, UserCodeHost::PythonNodeDependencyKey)?; nodes .iter() .map(|node| { @@ -224,10 +220,10 @@ pub(crate) fn python_node_graph<'py>( providers.set_item(provider.getattr("name")?, provider)?; } let vocabulary: Vocabulary<'py> = Vocabulary { - node: imported(py, NODE_MODELS_MODULE, "DiscoveredPythonNode")?, - kinds: imported(py, NODE_TYPES_MODULE, "PythonNodeKind")?, - sql_ref: imported(py, SQL_REF_MODULE, "SqlResourceRef")?, - identity: imported(py, USER_INVENTORY_MODULE, "python_node_identity")?, + node: registry::class(py, RecordId::DiscoveredPythonNode)?, + kinds: host::entry(py, UserCodeHost::PythonNodeKind)?, + sql_ref: host::entry(py, UserCodeHost::SqlResourceRef)?, + identity: host::entry(py, UserCodeHost::PythonNodeIdentity)?, providers, }; let mut nodes: Vec> = Vec::new(); @@ -258,8 +254,7 @@ fn graph_record<'py>( tags, paths, } = indexes; - let edge_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "PythonNodeDependencyEdge")?; + let edge_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonNodeDependencyEdge)?; let dependency_edges: Vec> = edges .into_iter() .map(|(upstream_name, downstream_name)| { @@ -302,7 +297,7 @@ fn graph_record<'py>( by_selector.set_item(format!("{kind}:{}", name.str()?), node)?; } call_with( - &imported(py, NODE_MODELS_MODULE, "PythonNodeGraph")?, + ®istry::class(py, RecordId::PythonNodeGraph)?, &[ ("nodes", PyTuple::new(py, nodes)?.into_any()), ( @@ -330,7 +325,7 @@ pub(crate) fn run_selection_from_inputs<'py>( let (sql_keys, python_node_names) = resolve_python_sql_run_selection(project_graph, &python_graph, select, exclude)?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ("sql_keys", compiled_graph_keys(py, sql_keys)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs index ef06bf1fa8..b8b1d112d7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs @@ -17,10 +17,9 @@ use sqlbuild_analysis::python_selection::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; - -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; type NameRows = Vec<(String, Vec)>; /// `(name, tags, POSIX relative path, upstream node names)` of one discovered node. @@ -40,8 +39,7 @@ fn planner_error(py: Python<'_>, error: SelectorError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", error.code)?; keywords.set_item("help", error.help)?; - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")? - .call((error.message,), Some(&keywords)) + registry::error(py, ErrorId::PlannerInput)?.call((error.message,), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs index 3ea882c339..b3d16357ad 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs @@ -12,8 +12,8 @@ use sqlbuild_analysis::graph::models::{GraphIndexes, ProjectGraph}; use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::graph::keys::{KeyCache, native_key}; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; - -const GRAPH_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// The native graph's `(resource type, name)` pair for `key`. #[pyfunction] @@ -84,9 +84,7 @@ fn graph_views<'py>(py: Python<'py>, graph: &ProjectGraph) -> PyResult = Bound<'py, PyAny>; @@ -20,8 +18,6 @@ pub(crate) fn compiled_model<'py>( macro_deps: &Object<'py>, ) -> PyResult> { let py: Python<'py> = model_input.py(); - let native = py.import(NATIVE_MODULE)?; - let models = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; let model_file: Object<'py> = model_input.getattr("model_file")?; let model_name: Object<'py> = model_file.getattr("file_path")?.getattr("stem")?; @@ -32,8 +28,7 @@ pub(crate) fn compiled_model<'py>( let mut star_resolved: Object<'py> = false_object(py); let mut diagnostics: Object<'py> = PyTuple::empty(py).into_any(); let mut validated: Object<'py> = false_object(py); - let mut proof: Object<'py> = native - .getattr("dynamic_contract_proof")? + let mut proof: Object<'py> = registry::native(py, NativeAttr::DynamicContractProof)? .call1((analysis.getattr("standalone_proof")?,))?; let session: Object<'py> = analysis.getattr("session")?; if !session.is_none() { @@ -42,9 +37,8 @@ pub(crate) fn compiled_model<'py>( arguments.set_item("session", session)?; arguments.set_item("position", position)?; arguments.set_item("placeholders", &none)?; - let outcome: Object<'py> = native - .getattr("session_outcome_analysis")? - .call((), Some(&arguments))?; + let outcome: Object<'py> = + registry::native(py, NativeAttr::SessionOutcomeAnalysis)?.call((), Some(&arguments))?; let result: Object<'py> = outcome.getattr("polyglot_analysis")?; if result.getattr("analysis_succeeded")?.is_truthy()? { inferred = result.getattr("columns")?; @@ -76,7 +70,7 @@ pub(crate) fn compiled_model<'py>( has_star = true_object(py); } let resource_type: Object<'py> = types.getattr("CompiledResourceType")?; - let key_class: Object<'py> = models.getattr("CompiledObjectKey")?; + let key_class: Object<'py> = registry::class(py, RecordId::CompiledObjectKey)?; let mut dependency_keys: Vec> = Vec::new(); for dependency in deps.try_iter()? { let (kind, name): (Object<'py>, Object<'py>) = dependency?.extract()?; @@ -97,7 +91,7 @@ pub(crate) fn compiled_model<'py>( ], )?; call( - &models.getattr("CompiledModel")?, + ®istry::class(py, RecordId::CompiledModel)?, &[ ("key", key), ("deps", PyTuple::new(py, dependency_keys)?.into_any()), @@ -161,8 +155,7 @@ fn destination<'py>(config: &Object<'py>, model_name: &Object<'py>) -> PyResult< }; let none: Object<'py> = py.None().into_bound(py); call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ( "database", @@ -183,9 +176,7 @@ fn fallback_columns<'py>(rows: &Object<'py>) -> PyResult> { if rows.is_none() { return Ok(rows.clone()); } - let column_class: Object<'py> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let column_class: Object<'py> = registry::class(py, RecordId::InferredColumn)?; let nullability: Object<'py> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; @@ -263,17 +254,13 @@ fn located<'py>( )? .into_any() }; - let located: Object<'py> = py - .import(NATIVE_MODULE)? - .getattr("locate_model_binding_diagnostics")? + let located: Object<'py> = registry::native(py, NativeAttr::LocateModelBindingDiagnostics)? .call1((PyList::new(py, rows)?, texts, expansion_row))?; - let models = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; - let diagnostic_class: Object<'py> = models.getattr("CompilerDiagnostic")?; - let location_class: Object<'py> = py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?; - let phase: Object<'py> = shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?; + let diagnostic_class: Object<'py> = registry::class(py, RecordId::CompilerDiagnostic)?; + let location_class: Object<'py> = registry::class(py, RecordId::SourceLocation)?; + let phase: Object<'py> = + registry::class(types.py(), RecordId::DiagnosticPhase)?.getattr("COMPILE")?; let model: Object<'py> = types.getattr("CompiledResourceType")?.getattr("MODEL")?; let name: Object<'py> = model_file.getattr("file_path")?.getattr("stem")?; let mut built: Vec> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs index 9b1c3c8348..2e3bc7a43a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs @@ -16,16 +16,16 @@ use sqlbuild_core::init_command::main::render_init_summary::render_init_summary; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::init_command::scaffold::scaffold_blank_project; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const EXISTS_MESSAGE: &str = "sqlbuild_project.toml already exists in this directory"; fn exists_error(py: Python<'_>) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", "C900")?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((EXISTS_MESSAGE,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((EXISTS_MESSAGE,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -52,7 +52,7 @@ fn run_init_command(py: Python<'_>, project_dir: Option>) -> Py let base_path: PathBuf = base_dir.extract()?; scaffold_blank_project(py, &base_path, &init_project_toml(&project_name))?; call_with( - &imported(py, NATIVE_MODULE, "update_sqlbuild_skills")?, + ®istry::native(py, NativeAttr::UpdateSqlbuildSkills)?, &[("project_dir", base_dir)], )?; let style: CliStyle = CliStyle { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs index 4e60e02c91..2e7bc07f8d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs @@ -24,6 +24,7 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ Folded, Key, SchemaKey, archive_timestamp, days, folded_schema, optional, record, relation_age_timestamp, sorted_schemas, text, timestamp_fields, tuple, utc_datetime, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::planner_scenario::artifacts::is_scenario_artifact_physical_name; use crate::bindings::_helpers::relation_naming::fitting::shared_error; @@ -53,7 +54,7 @@ impl<'py> Candidate<'py> { let py: Python<'py> = self.relation.py(); record( py, - "JanitorDeleteCandidate", + ExecutorHost::JanitorDeleteCandidate, &[ ("key", self.key.to_python(py)?), ("relation", self.relation.clone()), @@ -292,7 +293,7 @@ pub(crate) fn classify_target_schemas<'py>( } result.skipped_schemas.push(record( py, - "JanitorSkippedSchema", + ExecutorHost::JanitorSkippedSchema, &[ ("database", optional(py, schema_key.0.as_deref())?), ("schema", optional(py, schema_key.1.as_deref())?), @@ -344,7 +345,7 @@ fn archived_relation<'py>( if let Some(original_key) = original_key { pairs.push(("original_key", original_key.to_python(py)?)); } - record(py, "JanitorArchivedRelation", &pairs) + record(py, ExecutorHost::JanitorArchivedRelation, &pairs) } fn blocked_record<'py>( @@ -368,7 +369,7 @@ fn blocked_record<'py>( .unwrap_or_default(); record( py, - "JanitorBlockedSchema", + ExecutorHost::JanitorBlockedSchema, &[ ("database", optional(py, schema.schema.0.as_deref())?), ("schema", optional(py, schema.schema.1.as_deref())?), @@ -408,7 +409,7 @@ fn archive_candidate<'py>( }; let record = record( py, - "JanitorArchiveCandidate", + ExecutorHost::JanitorArchiveCandidate, &[ ("key", candidate.key.to_python(py)?), ("relation", candidate.relation.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs index d1f820dbb8..d573d76e06 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs @@ -27,11 +27,9 @@ use crate::bindings::_helpers::janitor::janitor_phases::{ }; use crate::bindings::_helpers::janitor::janitor_plan::{janitor_plan_text, janitor_result_counts}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const PREVIEW_TARGET_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; const COMMAND_NAME: &str = "janitor"; fn imported<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult> { @@ -53,7 +51,7 @@ fn settings_error(py: Python<'_>, error: JanitorSettingsError) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -79,7 +77,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; - let discovered: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "discover_project_inputs")? + let discovered: Bound<'py, PyAny> = registry::native(py, NativeAttr::DiscoverProjectInputs)? .call((), Some(&keywords(py, &[("project_dir", &project_dir)])?))?; let as_target: Bound<'py, PyAny> = request.getattr("as_target")?; let preview: Bound<'py, PyDict> = keywords( @@ -90,9 +88,9 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("selected_target")?; - let active_target: Bound<'py, PyAny> = imported(py, TARGET_NAME_MODULE, "resolve_target_name")? + let active_target: Bound<'py, PyAny> = registry::native(py, NativeAttr::ResolveTargetName)? .call( (), Some(&keywords( @@ -117,7 +115,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult( ), ], )?; - imported(py, NATIVE_MODULE, "JanitorSettings")?.call((), Some(&options)) + registry::class(py, RecordId::JanitorSettings)?.call((), Some(&options)) } /// Ask for the exact confirmation phrase; an interrupt cancels. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs index 2b6a791f75..338b9bc580 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs @@ -13,20 +13,16 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ sorted_schemas, text, tuple, }; -const QUERY_ARTIFACT_LIFECYCLE: (&str, &str) = ( - "sqlbuild.executor.diff.classes.query_artifact_lifecycle", - "QueryDiffArtifactLifecycle", -); -const RELATION_LOOKUP: (&str, &str) = ( - "sqlbuild.adapter.relations.main.relation_lookup", - "build_relation_lookup", -); -const LOG_DEBUG_EVENT: (&str, &str) = ( - "sqlbuild.diagnostics.main.log_debug_event", - "log_debug_event", -); -const READ_LATEST_FINGERPRINTS: (&str, &str) = ("sqlbuild._native", "read_latest_fingerprints"); +const QUERY_ARTIFACT_LIFECYCLE: ExecutorHost = ExecutorHost::QueryDiffArtifactLifecycle; +const RELATION_LOOKUP: AdapterHost = AdapterHost::BuildRelationLookup; +const LOG_DEBUG_EVENT: ExecutorHost = ExecutorHost::LogDebugEvent; +const READ_LATEST_FINGERPRINTS: NativeAttr = NativeAttr::ReadLatestFingerprints; const EXECUTION_LOGGER: &str = "sqlbuild.execution"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::FINGERPRINT_TABLE_NAME; use sqlbuild_core::constants::SOURCE_FRESHNESS_TABLE_NAME; @@ -245,7 +241,7 @@ fn relation_lookup<'py>( )?); } call_with( - &imported(py, RELATION_LOOKUP.0, RELATION_LOOKUP.1)?, + &host::entry(py, RELATION_LOOKUP)?, &[ ("adapter", warehouse.adapter.clone()), ("connection", warehouse.connection.clone()), @@ -300,7 +296,7 @@ fn tracked_relation_keys( (database.as_deref(), schema, FINGERPRINT_TABLE_NAME), )?; let read: PyResult> = call_with( - &imported(py, READ_LATEST_FINGERPRINTS.0, READ_LATEST_FINGERPRINTS.1)?, + ®istry::native(py, READ_LATEST_FINGERPRINTS)?, &[ ("connection", warehouse.connection.clone()), ("execute", warehouse.adapter.getattr("execute")?), @@ -326,7 +322,7 @@ fn tracked_relation_keys( Ok(set) => set, Err(error) if error.is_instance_of::(py) => { call_with( - &imported(py, LOG_DEBUG_EVENT.0, LOG_DEBUG_EVENT.1)?, + &host::entry(py, LOG_DEBUG_EVENT)?, &[ ( "logger", @@ -448,7 +444,7 @@ pub(crate) fn direct_state_prune_candidates<'py>( )?; candidates.push(record( py, - "JanitorDirectStatePruneCandidate", + ExecutorHost::JanitorDirectStatePruneCandidate, &[ ("database", optional(py, database.as_deref())?), ("schema", text(py, schema)), @@ -472,8 +468,7 @@ pub(crate) fn query_diff_artifact_candidates<'py>( now: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = warehouse.py(); - let lifecycle: Bound<'py, PyAny> = - imported(py, QUERY_ARTIFACT_LIFECYCLE.0, QUERY_ARTIFACT_LIFECYCLE.1)?; + let lifecycle: Bound<'py, PyAny> = host::entry(py, QUERY_ARTIFACT_LIFECYCLE)?; let mut sorted: Vec<(Option, String)> = schemas.to_vec(); sorted.sort_by(|left, right| { (left.0.as_deref().unwrap_or(""), left.1.as_str()) @@ -496,7 +491,7 @@ pub(crate) fn query_diff_artifact_candidates<'py>( let key: Key = Key::of(&artifact?)?; candidates.push(record( py, - "JanitorQueryDiffArtifactCandidate", + ExecutorHost::JanitorQueryDiffArtifactCandidate, &[("key", key.to_python(py)?)], )?); } @@ -521,7 +516,7 @@ pub(crate) fn skipped_relation<'py>( let py: Python<'py> = relation.py(); record( py, - "JanitorSkippedRelation", + ExecutorHost::JanitorSkippedRelation, &[ ("key", key.to_python(py)?), ("relation", relation.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs index 8f18cf46da..e60fdff466 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs @@ -7,8 +7,9 @@ use pyo3::types::{PyString, PyTuple}; use sqlbuild_analysis::janitor_planning::models::ArchiveTimestamp; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const JANITOR_MODELS: &str = "sqlbuild.executor.janitor.models"; const DATETIME_MODULE: &str = "datetime"; /// `(database, schema)` as spelled. @@ -58,7 +59,7 @@ impl Key { pub(crate) fn to_python<'py>(&self, py: Python<'py>) -> PyResult> { record( py, - "JanitorRelationKey", + ExecutorHost::JanitorRelationKey, &[ ("database", optional(py, self.database.as_deref())?), ("schema", optional(py, self.schema.as_deref())?), @@ -139,10 +140,10 @@ pub(crate) fn tuple<'py>( /// `sqlbuild.executor.janitor.models.(**pairs)`. pub(crate) fn record<'py>( py: Python<'py>, - class_name: &str, + class_name: ExecutorHost, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, JANITOR_MODELS, class_name)?, pairs) + call_with(&host::entry(py, class_name)?, pairs) } fn utc(py: Python<'_>) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs index 083173283f..031744d937 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs @@ -7,7 +7,7 @@ use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_analysis::janitor_planning::main::migration_origin_keys::migration_origin_keys; use sqlbuild_analysis::janitor_planning::main::old_name_request_matches::old_name_request_matches; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::janitor::janitor_facts::{ SchemaRelations, Warehouse, model_destinations, }; @@ -15,13 +15,13 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ Folded, Key, SchemaKey, optional, record, sorted_schemas, text, tuple, }; -const MIGRATION_TYPES: &str = "sqlbuild._native"; -const IS_COMPATIBILITY_VIEW: (&str, &str) = ( - "sqlbuild.executor.old_name_views._helpers.janitor", - "is_compatibility_view", -); -const READ_HISTORIES: (&str, &str) = ("sqlbuild._native", "read_old_name_view_histories"); -const STORED_COLUMNS: (&str, &str) = ("sqlbuild._native", "stored_old_name_view_columns"); +const IS_COMPATIBILITY_VIEW: ExecutorHost = ExecutorHost::IsCompatibilityView; +const READ_HISTORIES: NativeAttr = NativeAttr::ReadOldNameViewHistories; +const STORED_COLUMNS: NativeAttr = NativeAttr::StoredOldNameViewColumns; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::MIGRATION_TABLE_NAME; use sqlbuild_core::constants::OLD_NAME_VIEW_TABLE_NAME; const MIGRATE_FROM_CONFIG_KEY: &str = "migrate_from"; @@ -33,7 +33,7 @@ pub(crate) struct OldNamePlanning<'py> { } fn drop_reason<'py>(py: Python<'py>, member: &str) -> PyResult> { - imported(py, MIGRATION_TYPES, "OldNameViewDropReason")?.getattr(member) + registry::class(py, RecordId::OldNameViewDropReason)?.getattr(member) } fn status<'py>( @@ -44,7 +44,7 @@ fn status<'py>( } fn status_is(py: Python<'_>, value: &Bound<'_, PyAny>, member: &str) -> PyResult { - value.eq(imported(py, MIGRATION_TYPES, "OldNameViewStatus")?.getattr(member)?) + value.eq(registry::class(py, RecordId::OldNameViewStatus)?.getattr(member)?) } fn read_histories<'py>( @@ -74,7 +74,7 @@ fn read_histories<'py>( continue; } let stored_columns: Bound<'py, PyAny> = call_with( - &imported(py, STORED_COLUMNS.0, STORED_COLUMNS.1)?, + ®istry::native(py, STORED_COLUMNS)?, &[ ("adapter", warehouse.adapter.clone()), ("connection", warehouse.connection.clone()), @@ -83,7 +83,7 @@ fn read_histories<'py>( ], )?; let read: Bound<'py, PyAny> = call_with( - &imported(py, READ_HISTORIES.0, READ_HISTORIES.1)?, + ®istry::native(py, READ_HISTORIES)?, &[ ("connection", warehouse.connection.clone()), ("execute", warehouse.adapter.getattr("execute")?), @@ -220,7 +220,7 @@ fn view<'py>( )); pairs.push(("claimed_by", optional(py, claimed_by.as_deref())?)); } - record(py, "JanitorOldNameView", &pairs) + record(py, ExecutorHost::JanitorOldNameView, &pairs) } /// Each project model's destination mapped to the model's name. @@ -272,7 +272,7 @@ pub(crate) fn plan_old_name_views<'py>( } if histories.is_empty() && early_drops.is_empty() { return Ok(OldNamePlanning { - planning: record(py, "JanitorOldNameViewPlanning", &[])?, + planning: record(py, ExecutorHost::JanitorOldNameViewPlanning, &[])?, keys: HashSet::new(), }); } @@ -284,8 +284,7 @@ pub(crate) fn plan_old_name_views<'py>( requested.push((name.clone(), false)); } } - let is_compatibility_view: Bound<'py, PyAny> = - imported(py, IS_COMPATIBILITY_VIEW.0, IS_COMPATIBILITY_VIEW.1)?; + let is_compatibility_view: Bound<'py, PyAny> = host::entry(py, IS_COMPATIBILITY_VIEW)?; let (mut live, mut drops, mut missing) = (Vec::new(), Vec::new(), Vec::new()); let mut keys: HashSet = HashSet::new(); for history in &histories { @@ -334,7 +333,7 @@ pub(crate) fn plan_old_name_views<'py>( .collect(); let planning: Bound<'py, PyAny> = record( py, - "JanitorOldNameViewPlanning", + ExecutorHost::JanitorOldNameViewPlanning, &[ ("live", tuple(py, live)?), ("drops", tuple(py, drops)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs index a87458722b..1838d1a276 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs @@ -7,16 +7,16 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods}; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_connection_context; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; use crate::bindings::_helpers::janitor::janitor_facts::Warehouse; use crate::bindings::_helpers::janitor::janitor_planning::{ JanitorSettings, RelationScope, build_plan, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const JANITOR_EXECUTE_MODULE: &str = "sqlbuild.executor.janitor.main.execute"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const UNKNOWN_VIEW_CODE: &str = "C503"; const UNKNOWN_VIEW_HELP: &str = "Run sqb janitor without --drop-old-name-view to list compatibility views."; @@ -48,7 +48,7 @@ fn lifecycle<'py>(py: Python<'py>, (kind, name): (&str, &str)) -> PyResult = PyDict::new(py); options.set_item("operation_kind", kind)?; options.set_item("operation_name", name)?; - imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?.call((), Some(&options)) + host::entry(py, ExecutorHost::OperationLifecycle)?.call((), Some(&options)) } /// Compile for the janitor or `--as` target and resolve the active target connection. @@ -67,7 +67,7 @@ pub(crate) fn compile_janitor_project<'py>( )?; let started: Instant = Instant::now(); let status: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stdout(py)?), ( @@ -88,7 +88,7 @@ pub(crate) fn compile_janitor_project<'py>( within(&lifecycle(py, ("project", "project_compile"))?, |_| { status.call_method1("on_progress", ("Compiling project...",))?; let project: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ( "discovered_inputs", @@ -131,7 +131,7 @@ pub(crate) fn connect_janitor_warehouse<'py>( ) -> PyResult> { let py: Python<'py> = invocation.py(); let progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", compiled.adapter_name.clone()), ("stream", stdout(py)?), @@ -187,7 +187,7 @@ fn unknown_view_error(py: Python<'_>, requests: &Bound<'_, PyAny>) -> PyResult

= PyDict::new(py); options.set_item("code", UNKNOWN_VIEW_CODE)?; options.set_item("help", UNKNOWN_VIEW_HELP)?; - let error: Bound<'_, PyAny> = imported(py, ERRORS_MODULE, "CliUserError")?.call( + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::CliUser)?.call( (format!( "no compatibility view is recorded at {}", names.join(", ") @@ -277,11 +277,7 @@ pub(crate) fn execute_janitor_cleanup<'py>( plan: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported( - compiled.adapter.py(), - JANITOR_EXECUTE_MODULE, - "execute_janitor_plan", - )?, + &host::entry(compiled.adapter.py(), ExecutorHost::ExecuteJanitorPlan)?, &[ ("plan", plan.clone()), ("adapter", compiled.adapter.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs index b16318279e..7ce8973aa3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs @@ -10,7 +10,7 @@ use sqlbuild_analysis::janitor_planning::main::build_archive_name::build_archive use sqlbuild_analysis::janitor_planning::main::query_diff_artifact_name::query_diff_artifact_name; use sqlbuild_analysis::janitor_planning::main::query_diff_artifact_parts::query_diff_artifact_parts; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, within}; use crate::bindings::_helpers::janitor::janitor_classification::{ Candidate, Policy, Protection, SchemaClassification, classify_target_schemas, plan_archives, }; @@ -25,12 +25,11 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ use crate::bindings::_helpers::janitor::janitor_old_names::{ OldNamePlanning, pending_migration_origins, plan_old_name_views, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::relation_naming::fitting::shared_error; -const LIFECYCLE: (&str, &str) = ( - "sqlbuild.runtime.observability.classes.operation_lifecycle", - "OperationLifecycle", -); +const LIFECYCLE: ExecutorHost = ExecutorHost::OperationLifecycle; /// Settings of one janitor plan. pub(crate) struct JanitorSettings { @@ -128,7 +127,7 @@ fn artifact_only_plan<'py>( let py: Python<'py> = warehouse.py(); record( py, - "JanitorPlan", + ExecutorHost::JanitorPlan, &[ ("target_name", project.getattr("effective_target_name")?), ("retention_days", integer(py, settings.retention_days)?), @@ -191,7 +190,7 @@ pub(crate) fn build_plan<'py>( &now, ); } - let lifecycle: Bound<'py, PyAny> = imported(py, LIFECYCLE.0, LIFECYCLE.1)?; + let lifecycle: Bound<'py, PyAny> = host::entry(py, LIFECYCLE)?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("operation_kind", "janitor")?; options.set_item("operation_name", "janitor_warehouse_inspection")?; @@ -265,7 +264,7 @@ pub(crate) fn build_plan<'py>( ); record( py, - "JanitorPlan", + ExecutorHost::JanitorPlan, &[ ("target_name", project.getattr("effective_target_name")?), ("retention_days", integer(py, settings.retention_days)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs index 17c0d9c764..73b43f104c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs @@ -5,12 +5,12 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFloat, PyInt, PyList, PyStrin use sqlbuild_core::lifecycle_events::models::PayloadValue; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.observability.exceptions"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; /// `ObservabilityValidationError(message)`. pub(crate) fn validation_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "ObservabilityValidationError") + match host::entry(py, ExecutorHost::ObservabilityValidationError) .and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs index 24da1a8fc2..1e08cd4c4f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs @@ -4,6 +4,8 @@ use std::collections::{HashMap, HashSet}; use std::hash::Hash; use std::path::{Path, PathBuf}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyResult, Python}; use pyo3::types::{PyBytes, PyBytesMethods, PyDict, PyDictMethods, PyList, PyTuple}; use sqlbuild_analysis::lineage_command::models::{LineageIndex, LineageKey, LineageNode}; @@ -138,8 +140,7 @@ pub(crate) fn lineage_fingerprint( prefix.extend(sqlbuild_version(py)?.as_bytes()); prefix.extend(encoded.downcast::()?.as_bytes()); let uncacheable: bool = py.version_info() < FIRST_PYTHON_SKIPPING_INTERRUPTED_LISTINGS; - py.import("sqlbuild._native")? - .getattr("relation_lineage_fingerprint")? + registry::native(py, NativeAttr::RelationLineageFingerprint)? .call1((project_dir, PyBytes::new(py, &prefix), uncacheable))? .extract() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs index bb75f84d8b..ccbb57ac9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs @@ -38,19 +38,9 @@ use crate::bindings::_helpers::lineage::lineage_cache::{ }; use crate::bindings::_helpers::lineage::lineage_index::{graph_index, index_views, lineage_keys}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; -const SELECTION_MODULE: &str = "sqlbuild._native"; -const SELECTOR_TOKENS_MODULE: &str = "sqlbuild._native"; -const SELECTOR_PARSE_MODULE: &str = "sqlbuild._native"; -const SELECTOR_MATCH_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS: &str = "sqlbuild._native"; const UNKNOWN_SELECTOR_ERROR_CODE: &str = "S007"; const RICH_MODE: &str = "rich"; const ANALYSIS_HELP: &str = "enable SQL analysis or install SQLBuild with Polyglot support"; @@ -94,17 +84,13 @@ fn keywords<'py>( Ok(dict) } -fn imported<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult> { - py.import(module)?.getattr(name) -} - /// `CliUserError(message, code=..., help=...)`. fn user_error(py: Python<'_>, error: LineageError) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; options.set_item("help", error.help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -149,7 +135,7 @@ fn discover<'py>( options.set_item("project_dir", project_dir)?; options.set_item("sql_analysis_enabled_override", analysis_override)?; options.set_item("extract_output_column_locations", false)?; - imported(py, NATIVE_MODULE, "discover_project_inputs")?.call((), Some(&options)) + registry::native(py, NativeAttr::DiscoverProjectInputs)?.call((), Some(&options)) } fn reject_unit_tests( @@ -165,8 +151,7 @@ fn reject_unit_tests( ("discovered_inputs", discovered), ], )?; - imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")? - .call((), Some(&options))?; + registry::native(py, NativeAttr::RejectUnitTestSelectors)?.call((), Some(&options))?; Ok(()) } @@ -191,7 +176,7 @@ fn prepare_source<'py>( discover(py, project_dir, if compiled { None } else { Some(false) })?; reject_unit_tests(py, request, &discovered)?; let adapter_name: Bound<'py, PyAny> = - imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?.call( + registry::native(py, NativeAttr::ResolveEffectiveAdapterName)?.call( (), Some(&keywords( py, @@ -214,7 +199,7 @@ fn prepare_source<'py>( options.set_item("no_sql_validation", no_validation)?; options.set_item("skip_column_inference", !compiled)?; let graph: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "build_project_graph")?.call((), Some(&options))?; + registry::native(py, NativeAttr::BuildProjectGraph)?.call((), Some(&options))?; let index: LineageIndex = graph_index(&graph)?; if let Some(fingerprint) = &fingerprint { write_lineage_cache(py, &directory, fingerprint, &index)?; @@ -281,8 +266,7 @@ fn semantic_uses( ], )?; let keys: Vec = lineage_keys( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")? - .call((), Some(&options))?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?.call((), Some(&options))?, )?; keys.into_iter() .filter(|key| key.resource_type == MODEL_RESOURCE_TYPE) @@ -299,7 +283,7 @@ fn semantic_uses( .collect() }; let uses: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "build_direct_semantic_uses")?.call1(( + registry::native(py, NativeAttr::BuildDirectSemanticUses)?.call1(( graph.getattr("project")?, adapter.call_method0("sql_analysis_dialect")?, PyFrozenSet::new(py, model_names)?, @@ -389,7 +373,7 @@ fn column_lineage<'py>( py, (&message, rich && count >= RICH_STATUS_MODEL_THRESHOLD), || { - imported(py, NATIVE_MODULE, "build_project_column_lineage")?.call1(( + registry::native(py, NativeAttr::BuildProjectColumnLineage)?.call1(( graph.getattr("project")?, py.None(), mode, @@ -484,9 +468,9 @@ fn selector_anchors( select: &[String], require_clear_anchors: bool, ) -> PyResult { - let parse: Bound<'_, PyAny> = imported(py, SELECTOR_PARSE_MODULE, "parse_project_selector")?; - let matcher: Bound<'_, PyAny> = imported(py, SELECTOR_MATCH_MODULE, "match_project_selector")?; - let path_class: Bound<'_, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PathSelector")?; + let parse: Bound<'_, PyAny> = registry::native(py, NativeAttr::ParseProjectSelector)?; + let matcher: Bound<'_, PyAny> = registry::native(py, NativeAttr::MatchProjectSelector)?; + let path_class: Bound<'_, PyAny> = registry::class(py, RecordId::PathSelector)?; let names: HashMap<&str, &LineageKey> = name_lookup(index); let named = |name: String| -> PyResult { names @@ -564,7 +548,7 @@ fn selector_rendering( options.set_item("select", &select)?; options.set_item("exclude", &exclude)?; let selected: Vec = lineage_keys( - &imported(py, SELECTION_MODULE, "resolve_project_selectors")?.call((), Some(&options))?, + ®istry::native(py, NativeAttr::ResolveProjectSelectors)?.call((), Some(&options))?, )?; let index: &LineageIndex = source.index(); let direction: Option = request.getattr("direction")?.extract()?; @@ -573,12 +557,8 @@ fn selector_rendering( let options: Bound<'_, PyDict> = view_options(&views, &GRAPH_VIEW_NAMES)?; options.set_item("selectors", &exclude)?; let excluded: Vec = lineage_keys( - &imported( - py, - SELECTOR_TOKENS_MODULE, - "resolve_project_selector_tokens", - )? - .call((), Some(&options))?, + ®istry::native(py, NativeAttr::ResolveProjectSelectorTokens)? + .call((), Some(&options))?, )?; select_directional_lineage(index, (&selected, &excluded), (&direction, depth)) } else { @@ -597,8 +577,7 @@ fn selector_failure( error: PyErr, ) -> PyErr { let check = || -> PyResult<()> { - let planner_error: Bound<'_, PyAny> = - imported(py, PLANNER_EXCEPTIONS, "PlannerInputError")?; + let planner_error: Bound<'_, PyAny> = registry::error(py, ErrorId::PlannerInput)?; if !matches!(source, LineageSource::Cached(_)) || !error.is_instance(py, &planner_error) || error.value(py).getattr("code")?.extract::()? != UNKNOWN_SELECTOR_ERROR_CODE diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs index 5edcd89fd8..42a2a7e533 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs @@ -2,13 +2,12 @@ use std::collections::HashMap; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; use sqlbuild_analysis::lineage_command::models::{LineageIndex, LineageKey, LineageNode}; -const COMPILE_MODELS: &str = "sqlbuild._native"; -const GRAPH_MODELS: &str = "sqlbuild._native"; - /// A Python `CompiledObjectKey` as a lineage key. pub(crate) fn lineage_key(key: &Bound<'_, PyAny>) -> PyResult { Ok(LineageKey { @@ -31,7 +30,7 @@ pub(crate) struct PythonKeys<'py> { impl<'py> PythonKeys<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - class: py.import(COMPILE_MODELS)?.getattr("CompiledObjectKey")?, + class: registry::class(py, RecordId::CompiledObjectKey)?, made: HashMap::new(), }) } @@ -212,7 +211,5 @@ pub(crate) fn index_views<'py>( options.set_item("tag_index", tags)?; options.set_item("path_index", paths)?; options.set_item("all_keys", names)?; - py.import(GRAPH_MODELS)? - .getattr("LineageGraphViews")? - .call((), Some(&options)) + registry::class(py, RecordId::LineageGraphViews)?.call((), Some(&options)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs index a55df86374..59832a8c6a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs @@ -1,16 +1,16 @@ //! The Python lineage models and enums native lineage builds. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyResult, Python}; use pyo3::types::{PyDict, PyTuple}; use sqlbuild_analysis::lineage::models::{LineageColumn, LineageSource}; -const LINEAGE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const LOGGING_MODULE: &str = "logging"; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const LINEAGE_LOGGER: &str = "sqlbuild.lineage"; /// The lineage model classes and enums, imported once per call. @@ -35,26 +35,24 @@ pub(crate) struct LineageClasses<'py> { impl<'py> LineageClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let models: Bound<'py, PyModule> = py.import(LINEAGE_MODELS_MODULE)?; let types: Bound<'py, PyModule> = py.import(LINEAGE_TYPES_MODULE)?; - let compile_models: Bound<'py, PyModule> = py.import(COMPILE_MODELS_MODULE)?; let resource_type: Bound<'py, PyAny> = py .import(COMPILE_TYPES_MODULE)? .getattr("CompiledResourceType")?; let nullability: Bound<'py, PyAny> = types.getattr("InferredNullability")?; Ok(Self { - qualified_column: models.getattr("QualifiedLineageColumn")?, - lineage_source: models.getattr("ColumnLineageSource")?, - lineage_edge: models.getattr("ColumnLineageEdge")?, - column_lineage: models.getattr("ColumnLineage")?, - model_lineage: models.getattr("ModelColumnLineage")?, - semantic_use: models.getattr("DirectSemanticColumnUse")?, - column_fact: shape_class(compile_models.as_any(), "CompiledLineageColumnFact")?, - source_fact: compile_models.getattr("CompiledLineageSourceFact")?, - compact_facts: compile_models.getattr("CompactLineageFacts")?, - transform_kind: shape_class(types.as_any(), "ColumnTransformKind")?, - confidence: shape_class(types.as_any(), "ColumnLineageConfidence")?, - analysis_dialect: shape_class(types.as_any(), "PolyglotAnalysisDialect")?, + qualified_column: registry::class(py, RecordId::QualifiedLineageColumn)?, + lineage_source: registry::class(py, RecordId::ColumnLineageSource)?, + lineage_edge: registry::class(py, RecordId::ColumnLineageEdge)?, + column_lineage: registry::class(py, RecordId::ColumnLineage)?, + model_lineage: registry::class(py, RecordId::ModelColumnLineage)?, + semantic_use: registry::class(py, RecordId::DirectSemanticColumnUse)?, + column_fact: registry::class(py, RecordId::CompiledLineageColumnFact)?, + source_fact: registry::class(py, RecordId::CompiledLineageSourceFact)?, + compact_facts: registry::class(py, RecordId::CompactLineageFacts)?, + transform_kind: registry::class(types.py(), RecordId::ColumnTransformKind)?, + confidence: registry::class(types.py(), RecordId::ColumnLineageConfidence)?, + analysis_dialect: registry::class(types.py(), RecordId::PolyglotAnalysisDialect)?, model_resource: resource_type.getattr("MODEL")?, unknown_nullability: nullability.getattr("UNKNOWN")?, nullability, @@ -177,9 +175,6 @@ pub(crate) fn log_lineage_event( fields.set_item("message", message)?; fields.set_item("sqlbuild_model", model_name)?; fields.set_item("sqlbuild_error", error)?; - let _ = py - .import(DEBUG_EVENT_MODULE)? - .getattr("log_debug_event")? - .call((), Some(&fields))?; + let _ = host::entry(py, ExecutorHost::LogDebugEvent)?.call((), Some(&fields))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs index 470d1f1b80..8fdcbdc561 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs @@ -22,10 +22,11 @@ use crate::bindings::_helpers::lineage::lineage_objects::{LineageClasses, log_li use crate::bindings::_helpers::lineage::project_schema::{ fast_schema, inferred_names, rich_schema, }; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; use crate::bindings::types::CompilerDetach; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const FAST_MODE: &str = "fast"; const RICH_MODE: &str = "rich"; const GENERIC_DIALECT: &str = "generic"; @@ -266,9 +267,7 @@ fn output_columns(classes: &LineageClasses<'_>, facts: &Bound<'_, PyAny>) -> PyR } fn unknown_dialect_error(py: Python<'_>, dialect: &str) -> PyErr { - match py - .import(EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("UnknownLineageDialectError")) + match registry::error(py, ErrorId::UnknownLineageDialect) .and_then(|class| class.call1((format!("Unknown dialect: {dialect}"),))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs index 47c179dd7c..b76e0c358d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs @@ -14,11 +14,10 @@ use sqlbuild_core::loader_connection::models::LoaderConnection; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::load::ingestr_command::connection; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; -const DLT_ERROR: (&str, &str) = ( - "sqlbuild.integrations.dlt.exceptions", - "DltIntegrationError", -); +const DLT_ERROR: ThirdPartyHost = ThirdPartyHost::DltIntegrationError; /// `(factory, arguments, options, dataset_name)` of one dlt destination. type DestinationCall<'py> = ( @@ -30,7 +29,7 @@ type DestinationCall<'py> = ( /// A `DltIntegrationError`, or the failure to construct one. fn dlt_error(py: Python<'_>, message: String) -> PyErr { - imported(py, DLT_ERROR.0, DLT_ERROR.1) + host::entry(py, DLT_ERROR) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs index 11a990d48a..0a81034f77 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs @@ -16,21 +16,13 @@ use sqlbuild_core::loader_connection::models::LoaderConnection; use sqlbuild_model_config::templates::models::TemplateOptions; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::model_config::template_data::expanded_template_data; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; -const INGESTR_ERROR: (&str, &str) = ( - "sqlbuild.integrations.ingestr.exceptions", - "IngestrIntegrationError", -); -const INGESTR_SOURCE_CONFIG: (&str, &str) = ( - "sqlbuild.integrations.ingestr.models", - "IngestrSourceConfig", -); -const INGESTR_KIND: (&str, &str) = ( - "sqlbuild.integrations.ingestr.constants", - "INGESTR_INTEGRATION_KIND", -); +const INGESTR_ERROR: ThirdPartyHost = ThirdPartyHost::IngestrIntegrationError; +const INGESTR_SOURCE_CONFIG: ThirdPartyHost = ThirdPartyHost::IngestrSourceConfig; +const INGESTR_KIND: ThirdPartyHost = ThirdPartyHost::IngestrIntegrationKind; const TEMPLATE_LABEL: &str = "ingestr source config"; const TEMPLATE_FIELDS: [&str; 7] = [ "source_uri", @@ -45,7 +37,7 @@ const LIST_FIELDS: [&str; 2] = ["primary_key", "extra_args"]; /// An `IngestrIntegrationError`, or the failure to construct one. fn ingestr_error(py: Python<'_>, message: String) -> PyErr { - imported(py, INGESTR_ERROR.0, INGESTR_ERROR.1) + host::entry(py, INGESTR_ERROR) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value) } @@ -55,22 +47,14 @@ fn ingestr_config<'py>(source_entry: &Bound<'py, PyAny>) -> PyResult = source_entry.py(); let name: String = source_entry.getattr("name")?.str()?.extract()?; let loader: Bound<'py, PyAny> = source_entry.getattr("integration_loader")?; - if loader.is_none() - || !loader - .getattr("kind")? - .eq(imported(py, INGESTR_KIND.0, INGESTR_KIND.1)?)? - { + if loader.is_none() || !loader.getattr("kind")?.eq(host::entry(py, INGESTR_KIND)?)? { return Err(ingestr_error( py, format!("Source '{name}' does not define ingestr config"), )); } let config: Bound<'py, PyAny> = loader.getattr("config")?; - if !config.is_instance(&imported( - py, - INGESTR_SOURCE_CONFIG.0, - INGESTR_SOURCE_CONFIG.1, - )?)? { + if !config.is_instance(&host::entry(py, INGESTR_SOURCE_CONFIG)?)? { return Err(ingestr_error( py, format!("Source '{name}' defines invalid ingestr config"), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs index 5cffcea16c..8b3b0fb10c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs @@ -17,7 +17,7 @@ use sqlbuild_core::load_command::main::render_load_ready::render_load_ready; use sqlbuild_core::load_command::models::{LoadCounts, LoadResultText}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::overrides::{cursor_integer, cursor_timestamp}; use crate::bindings::_helpers::load::load_progress::{ LoadCallbackRelay, LoadProgressReporter, load_result_text, @@ -25,23 +25,14 @@ use crate::bindings::_helpers::load::load_progress::{ use crate::bindings::_helpers::load::load_selection::{ LoadProject, LoaderDefaults, loader_defaults, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_TARGET_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_RUNTIME_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_SETTINGS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; -const CURSOR_OVERRIDES_MODULE: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const PROVIDER_SESSION_MODULE: &str = "sqlbuild.provider.main.session"; -const PYTHON_NODE_MODELS: &str = "sqlbuild.python_nodes.models"; -const PYTHON_NODE_TYPES: &str = "sqlbuild.python_nodes.types"; -const LOAD_MODELS: &str = "sqlbuild.executor.load.models"; -const LOAD_RUN_MODULE: &str = "sqlbuild.executor.load.main.run"; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const EXECUTION_COMMAND: &str = "sqb load"; @@ -84,13 +75,13 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let select: Bound<'py, PyAny> = request.getattr("select")?; let exclude: Bound<'py, PyAny> = request.getattr("exclude")?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", select.clone()), ("exclude", exclude.clone()), @@ -99,7 +90,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("selected_target")?; let target_config: Bound<'py, PyAny> = call_with( - &imported(py, EFFECTIVE_TARGET_MODULE, "build_effective_target_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", selected_target.clone()), @@ -141,7 +132,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult, message: String) -> PyErr { - match imported(py, ERRORS_MODULE, "CliUserError").and_then(|class| class.call1((message,))) { + match registry::error(py, ErrorId::CliUser).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -187,7 +178,7 @@ fn check_reference_targets( }) .collect::>()?; let lookup: Bound<'_, PyAny> = call_with( - &imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")?, + &host::entry(py, AdapterHost::BuildRelationLookup)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -253,10 +244,10 @@ fn project_relations<'py>( sources.set_item(source.getattr("name")?, source)?; } } - let reference_class: Bound<'py, PyAny> = imported(py, PYTHON_NODE_MODELS, "SqlResourceRef")?; + let reference_class: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let source_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "SqlResourceRefKind")?.getattr("SOURCE")?; - let render: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "render_source_relation")?; + host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; + let render: Bound<'py, PyAny> = registry::native(py, NativeAttr::RenderSourceRelation)?; let relations: Bound<'py, PyDict> = PyDict::new(py); for (name, source) in sources.iter() { let key: Bound<'py, PyAny> = call_with( @@ -277,7 +268,7 @@ fn prepare<'py>( let py: Python<'py> = request.py(); let discovered: &Bound<'py, PyAny> = &invocation.discovered; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -285,7 +276,7 @@ fn prepare<'py>( )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, &invocation.project_dir)?; let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("project_dir", invocation.project_dir.clone()), @@ -295,11 +286,7 @@ fn prepare<'py>( )?; check_reference_targets(invocation, (&adapter, &connection_config))?; let runtime: (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = call_with( - &imported( - py, - EFFECTIVE_RUNTIME_MODULE, - "build_effective_runtime_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", request.getattr("selected_target")?), @@ -310,11 +297,7 @@ fn prepare<'py>( let requested: Bound<'py, PyAny> = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { call_with( - &imported( - py, - EFFECTIVE_SETTINGS_MODULE, - "build_effective_settings_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", discovered.clone())], )? .getattr("concurrency")? @@ -323,16 +306,16 @@ fn prepare<'py>( requested.extract()? }; let typed: Bound<'py, PyAny> = call_with( - &imported(py, CURSOR_OVERRIDES_MODULE, "type_cursor_overrides")?, + ®istry::native(py, NativeAttr::TypeCursorOverrides)?, &[("cursor_overrides", request.getattr("cursor_overrides")?)], )?; let cursor_overrides: Bound<'py, PyAny> = if typed.is_truthy()? { typed } else { - imported(py, PLANNER_MODELS, "CursorOverrides")?.call0()? + registry::class(py, RecordId::CursorOverrides)?.call0()? }; let provider_session: Bound<'py, PyAny> = call_with( - &imported(py, PROVIDER_SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[("discovered_providers", discovered.getattr("providers")?)], )?; Ok(Preparation { @@ -357,7 +340,7 @@ fn runtime_params<'py>( let (target_name, effective_vars, run_id) = &preparation.runtime; let overrides: &Bound<'py, PyAny> = &preparation.cursor_overrides; call_with( - &imported(py, LOAD_MODELS, "LoadRuntimeParams")?, + &host::entry(py, ExecutorHost::LoadRuntimeParams)?, &[ ("run_id", run_id.clone()), ( @@ -409,13 +392,13 @@ fn execute<'py>( let py: Python<'py> = request.py(); let started: Instant = Instant::now(); let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let order: Bound<'py, PyDict> = PyDict::new(py); for (index, source) in invocation.selected.iter().enumerate() { order.set_item(source.getattr("name")?, index + 1)?; } let reporter: Bound<'py, LoadProgressReporter> = - imported(py, NATIVE_MODULE, "LoadProgressReporter")? + registry::native(py, NativeAttr::LoadProgressReporter)? .call( (), Some(&{ @@ -429,7 +412,7 @@ fn execute<'py>( )? .downcast_into()?; let connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", text(py, &preparation.adapter_name)), ("stream", invocation.stream.clone()), @@ -455,7 +438,7 @@ fn execute<'py>( )?; let executed: PyResult>> = (|| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, LOAD_MODELS, "LoadCallbacks")?, + &host::entry(py, ExecutorHost::LoadCallbacks)?, &[ ("on_load_start", reporter.getattr("on_start")?), ("on_load_progress", reporter.getattr("on_progress")?), @@ -472,7 +455,7 @@ fn execute<'py>( ], )?; call_with( - &imported(py, LOAD_RUN_MODULE, "run_load_pipeline")?, + &host::entry(py, ExecutorHost::RunLoadPipeline)?, &[ ( "sources", @@ -508,11 +491,11 @@ fn execute<'py>( fn write_json(request: &Bound<'_, PyAny>, results: &[Bound<'_, PyAny>]) -> PyResult<()> { let py: Python<'_> = request.py(); let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_load_execution_json")?, + ®istry::native(py, NativeAttr::FormatLoadExecutionJson)?, &[("results", PyTuple::new(py, results)?.into_any())], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -566,7 +549,7 @@ fn run_load_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult options.set_item("target", py.None())?; options.set_item("concurrency", preparation.concurrency)?; options.set_item("use_color", invocation.style.use_color)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&options))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&options))?; execute(request, &invocation, &preparation) })(); let closed: PyResult> = preparation.provider_session.call_method0("close"); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs index 1ae444823e..703f54293a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs @@ -12,11 +12,12 @@ use sqlbuild_core::load_command::main::render_load_spinner_line::render_load_spi use sqlbuild_core::load_command::models::{LoadResultText, LoadSpinnerRow, LoadStatus}; use crate::bindings::_helpers::build_progress::terminal::start_spinner; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, within}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LINES_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_TYPES: &str = "sqlbuild.adapter.contract.types"; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const SPINNER_TICK_SECONDS: f64 = 0.1; const SPINNER_JOIN_SECONDS: f64 = 0.2; @@ -35,7 +36,7 @@ fn optional_text(value: Bound<'_, PyAny>) -> PyResult> { pub(crate) fn load_result_text(result: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = result.py(); let log_kind: Bound<'_, PyAny> = - imported(py, LIFECYCLE_TYPES, "LifeCycleEventKind")?.getattr("LOG")?; + host::entry(py, AdapterHost::LifeCycleEventKind)?.getattr("LOG")?; let mut log_messages: Vec = Vec::new(); for event in result.getattr("lifecycle_events")?.try_iter()? { let event: Bound<'_, PyAny> = event?; @@ -199,9 +200,9 @@ impl LoadProgressReporter { let source_order: HashMap = arguments.value("source_order")?.extract()?; let total_count: i64 = arguments.value("total_count")?.extract()?; let lines: Bound<'_, PyAny> = - imported(py, LINES_MODULE, "shared_transient_line_coordinator")?.call0()?; + registry::native(py, NativeAttr::SharedTransientLineCoordinator)?.call0()?; let projector: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("ordinals", source_order.clone())?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs index 9f46362752..e9ef6cf49d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs @@ -15,13 +15,11 @@ use sqlbuild_core::load_command::models::{LoadEntry, LoadGraph}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS: &str = "sqlbuild._native"; -const DESTINATION_MODULE: &str = "sqlbuild._native"; -const CONNECTION_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const ADAPTER_TYPES_MODULE: &str = "sqlbuild.adapter.contract.types"; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const LOADER_TABLE_PREFIX: &str = "__loader__"; const UNKNOWN_SELECTOR_CODE: &str = "C901"; @@ -125,7 +123,7 @@ impl<'py> LoadProject<'py> { destination }; let parts: Bound<'py, PyAny> = call_with( - &imported(py, DESTINATION_MODULE, "loader_destination_parts")?, + ®istry::native(py, NativeAttr::LoaderDestinationPartsFunction)?, &[ ("destination", destination), ("default_database", defaults.database.clone()), @@ -136,7 +134,7 @@ impl<'py> LoadProject<'py> { meta.set_item(LOADER_NODE_KEY, true)?; let name_value: Bound<'py, PyAny> = PyString::new(py, name).into_any(); call_with( - &imported(py, CONTRACT_MODELS, "SourceEntry")?, + ®istry::class(py, RecordId::SourceEntry)?, &[ ("name", name_value.clone()), ("database", parts.getattr("database")?), @@ -204,7 +202,7 @@ fn unknown_selector(py: Python<'_>, error: &UnknownLoadSelector) -> PyErr { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", UNKNOWN_SELECTOR_CODE)?; options.set_item("help", UNKNOWN_SELECTOR_HELP)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call( + registry::error(py, ErrorId::CliUser)?.call( (format!( "sqb load selector '{}' does not match any managed source or loader", error.name @@ -255,11 +253,7 @@ pub(crate) fn loader_defaults<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let connection: Bound<'py, PyDict> = call_with( - &imported( - py, - CONNECTION_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", selected_target.clone()), @@ -268,14 +262,14 @@ pub(crate) fn loader_defaults<'py>( )? .downcast_into()?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), ], )?; let duckdb: Bound<'py, PyAny> = - imported(py, ADAPTER_TYPES_MODULE, "BuiltinAdapter")?.getattr("DUCKDB")?; + host::entry(py, AdapterHost::BuiltinAdapter)?.getattr("DUCKDB")?; let connection_database: Option> = if adapter_name.eq(duckdb)? { None } else { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs b/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs index d78fbea497..4f88fb792e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs @@ -19,9 +19,11 @@ use crate::bindings::_helpers::graph::views::native_graph_from_edges; use crate::bindings::_helpers::manifest::rows::{ ProjectRows, plan_input, project_rows, python_hooks, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const FINGERPRINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; const DATETIME_MODULE: &str = "datetime"; /// A built manifest: its `manifest.json` bytes, or the error `json.dumps` raised encoding it. @@ -51,9 +53,7 @@ impl NativeManifest { fn python_error(error: ManifestError, py: Python<'_>) -> PyErr { match error { - ManifestError::QueryFingerprint(message) => py - .import(FINGERPRINT_EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("QueryFingerprintError")) + ManifestError::QueryFingerprint(message) => registry::error(py, ErrorId::QueryFingerprint) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value), ManifestError::MissingParameterType(name) => PyKeyError::new_err(name), @@ -89,9 +89,7 @@ pub(crate) fn build_manifest(options: Option<&Bound<'_, PyDict>>) -> PyResult = py - .import(HOOK_IDENTITIES_MODULE)? - .getattr("build_hook_identities")? + let hook_identities: Bound<'_, PyAny> = host::entry(py, UserCodeHost::BuildHookIdentities)? .call1((project.getattr("hook_functions")?,))?; compiler_guard(|| { let python_hooks = python_hooks(&project, &hook_identities)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs index d5c16fb94f..217ea3d27d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs @@ -13,8 +13,9 @@ use sqlbuild_analysis::manifest::models::{ use sqlbuild_analysis::python_values::models::PythonValue; use crate::bindings::_helpers::python_values::values::python_value; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const DECIMAL_KIND: &str = "decimal"; const MACRO_KIND: &str = "macro"; const DEFAULT_MATERIALIZATION: &str = "view"; @@ -147,9 +148,8 @@ fn hooks(value: &Bound<'_, PyAny>) -> PyResult>> { if !(value.is_instance_of::() || value.is_instance_of::()) { return Ok(None); } - let module: Bound<'_, PyAny> = value.py().import(DISCOVERY_MODELS_MODULE)?.into_any(); - let sql_class: Bound<'_, PyAny> = module.getattr("SqlHookEntry")?; - let python_class: Bound<'_, PyAny> = module.getattr("PythonHookEntry")?; + let sql_class: Bound<'_, PyAny> = registry::class(value.py(), RecordId::SqlHookEntry)?; + let python_class: Bound<'_, PyAny> = registry::class(value.py(), RecordId::PythonHookEntry)?; value .try_iter()? .map(|entry| hook(&entry?, (&sql_class, &python_class))) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs index dca974875b..e9181b2e41 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs @@ -8,18 +8,14 @@ use sqlbuild_cursor::algebra::main::cursor_sort_key::cursor_sort_key; use sqlbuild_cursor::algebra::models::{Scalar, SortKey}; use sqlbuild_state::microbatches::models::{BoundInterval, CursorBound}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::as_scalar; - -pub(crate) const MODELS_MODULE: &str = "sqlbuild.microbatches.models"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild.microbatches.types"; -pub(crate) const CONSTANTS_MODULE: &str = "sqlbuild.microbatches.constants"; -const EXCEPTIONS_MODULE: &str = "sqlbuild.microbatches.exceptions"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; /// `MicrobatchStateError(message)`. pub(crate) fn state_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "MicrobatchStateError") + match host::entry(py, ExecutorHost::MicrobatchStateError) .and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs index 10c5c32f7e..f4f62ee23b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs @@ -12,10 +12,12 @@ use sqlbuild_state::microbatches::models::{ }; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::microbatches::conversion::{ - BoundParser, MODELS_MODULE, TYPES_MODULE, enum_text, optional_text, timestamp, + BoundParser, enum_text, optional_text, timestamp, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const COMPLETION_RECORD_TYPES: [&str; 2] = ["partition_completion", "synthetic_completion"]; const ORDINARY_COMPLETION: &str = "partition_completion"; @@ -26,7 +28,7 @@ fn python_intervals<'py>( py: Python<'py>, intervals: &[TextInterval], ) -> PyResult> { - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "MicrobatchInterval")?; + let class: Bound<'py, PyAny> = host::entry(py, ExecutorHost::MicrobatchInterval)?; let built: Vec> = intervals .iter() .map(|interval| { @@ -48,7 +50,7 @@ fn python_coverage<'py>( events: &[Bound<'py, PyAny>], ) -> PyResult> { let projected_class: Bound<'py, PyAny> = - imported(py, MODELS_MODULE, "ProjectedMicrobatchInterval")?; + host::entry(py, ExecutorHost::ProjectedMicrobatchInterval)?; let mut projected: Vec> = Vec::with_capacity(projection.segments.len()); for segment in &projection.segments { let event: &Bound<'py, PyAny> = &events[segment.completion]; @@ -69,7 +71,7 @@ fn python_coverage<'py>( )?); } call_with( - &imported(py, MODELS_MODULE, "MicrobatchCoverageProjection")?, + &host::entry(py, ExecutorHost::MicrobatchCoverageProjection)?, &[ ("intervals", PyTuple::new(py, projected)?.into_any()), ( @@ -196,12 +198,12 @@ fn replay<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult) -> PyResult { - let constants: Bound<'_, PyModule> = py.import(CONSTANTS_MODULE)?; - let columns: Vec = constants.getattr("MICROBATCH_COLUMNS")?.extract()?; - let types: Bound<'_, PyAny> = constants.getattr("MICROBATCH_COLUMN_TYPES")?; + let columns: Vec = host::entry(py, ExecutorHost::MicrobatchColumns)?.extract()?; + let types: Bound<'_, PyAny> = host::entry(py, ExecutorHost::MicrobatchColumnTypes)?; let declared: Vec = columns .iter() .map(|column| types.get_item(column)?.str()?.extract()) @@ -56,8 +54,8 @@ impl Layout { /// One event's column literals in column order. fn literals(&self, event: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = event.py(); - let values: Bound<'_, PyAny> = - imported(py, CODEC_MODULE, "MicrobatchEventCodec")?.call_method1("values", (event,))?; + let values: Bound<'_, PyAny> = host::entry(py, ExecutorHost::MicrobatchEventCodec)? + .call_method1("values", (event,))?; values .try_iter()? .zip(&self.declared) @@ -76,7 +74,7 @@ fn qualified_table<'py>( (database, schema): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = render_qualified_name.py(); - let table_name: Bound<'py, PyAny> = imported(py, CONSTANTS_MODULE, "MICROBATCH_TABLE_NAME")?; + let table_name: Bound<'py, PyAny> = host::entry(py, ExecutorHost::MicrobatchTableName)?; optional_text(&call_with( render_qualified_name, &[ @@ -161,7 +159,7 @@ fn read_scope( let table: String = scope_table(render_qualified_name, scope)?; let layout: Layout = Layout::new(py)?; let wildcard: String = - imported(py, CONSTANTS_MODULE, "MICROBATCH_GENERATION_WILDCARD")?.extract()?; + host::entry(py, ExecutorHost::MicrobatchGenerationWildcard)?.extract()?; let generation: Bound<'_, PyAny> = scope.getattr("physical_generation_id")?; let generation: Option = if generation.extract::()? == wildcard { None @@ -193,7 +191,7 @@ fn create_table(request: Option<&Bound<'_, PyDict>>) -> PyResult { )? .ok_or_else(|| state_error(py, MISSING_SCHEMA))?; let render_framework_type: Bound<'_, PyAny> = request.get("render_framework_type")?; - let framework: Bound<'_, PyAny> = imported(py, ADAPTER_TYPES, "FrameworkType")?; + let framework: Bound<'_, PyAny> = host::entry(py, AdapterHost::FrameworkType)?; let text_type: String = render_framework_type .call1((framework.getattr("STRING")?,))? .extract()?; @@ -230,16 +228,13 @@ fn create_indexes<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = request.get("render_identifier")?; @@ -247,8 +242,7 @@ fn create_indexes<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult)> = Vec::new(); - let definitions: Bound<'_, PyAny> = - imported(py, CONSTANTS_MODULE, "MICROBATCH_DIRECT_INDEXES")?; + let definitions: Bound<'_, PyAny> = host::entry(py, ExecutorHost::MicrobatchDirectIndexes)?; for item in definitions.call_method0("items")?.try_iter()? { let (name, columns): (Bound<'_, PyAny>, Bound<'_, PyAny>) = item?.extract()?; let columns: Vec = columns diff --git a/crates/sqlbuild-python/src/bindings/_helpers/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/mod.rs index 9b7bb46cb6..1ad13fd9e6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/mod.rs @@ -56,6 +56,7 @@ pub(crate) mod macro_bridge; pub(crate) mod manifest; pub(crate) mod microbatches; pub(crate) mod model_config; +pub(crate) mod native_host; pub(crate) mod native_threads; pub(crate) mod output_capture; pub(crate) mod plan_command; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs index 681cf918ef..8b0f142fd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs @@ -5,13 +5,11 @@ use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{Py, PyErr, pymethods}; use sqlbuild_model_config::errors::{ConfigError, ErrorClass}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::model_config::config_values::config_value_type_error; use crate::bindings::_helpers::model_config::errors::NativeConfigError; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; const EXPECTED_STRING: &str = "a string"; const COMPILE_INPUT_CLASS: &str = "compile_input"; @@ -82,15 +80,15 @@ impl NativeConfigError { } let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("help", &self.help)?; - let (module, class) = match self.class_name { - RESOURCE_IDENTITY_CLASS => (IDENTITY_ERRORS_MODULE, "ResourceIdentityError"), - DISCOVERY_CONFLICT_CLASS => (DISCOVERY_ERRORS_MODULE, "DiscoveryConflictError"), + let class: ErrorId = match self.class_name { + RESOURCE_IDENTITY_CLASS => ErrorId::ResourceIdentity, + DISCOVERY_CONFLICT_CLASS => ErrorId::DiscoveryConflict, _ => { keywords.set_item("code", self.code)?; - (COMPILE_ERRORS_MODULE, "CompileInputError") + ErrorId::CompileInput } }; - imported(py, module, class)?.call((&self.message,), Some(&keywords)) + registry::error(py, class)?.call((&self.message,), Some(&keywords)) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs index e0375e37ef..4fba09abbf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs @@ -21,13 +21,13 @@ use sqlbuild_model_config::templates::types::TemplateHost; use crate::bindings::_helpers::model_config::config_errors::native_config_error; use crate::bindings::_helpers::model_config::errors::NativeConfigError; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const TEMPLATE_OPEN_TOKEN: &str = "${"; const ENVIRONMENT_READ: &str = "env"; const CONTEXT_READ: &str = "ctx"; const EFFECTIVE_VARS_LABEL: &str = "effective vars"; -const PROJECT_VAR_VALUES_MODULE: &str = "sqlbuild._native"; -const RENDER_PROJECT_VAR_TEXT: &str = "render_project_var_text"; const VALUE_PATH: &str = "its value"; const BUILTINS_MODULE: &str = "builtins"; const ASCII_FUNCTION: &str = "ascii"; @@ -241,11 +241,8 @@ impl<'py> TemplateHost for PythonHost<'py> { if value.is_exact_instance_of::() { return self.text_of(value); } - let rendered = self - .py - .import(PROJECT_VAR_VALUES_MODULE) - .and_then(|module| module.getattr(RENDER_PROJECT_VAR_TEXT)) - .and_then(|render| { + let rendered = + registry::native(self.py, NativeAttr::RenderProjectVarText).and_then(|render| { let arguments = PyDict::new(self.py); arguments.set_item("value", value)?; arguments.set_item("label", label)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs index f5ae424dd8..8b3caf15ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs @@ -5,8 +5,9 @@ use pyo3::types::{PyBool, PyInt, PyList, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATETIME_MODULE: &str = "datetime"; /// `values.get(key)`, or `None` when the value is absent or `None`. @@ -32,7 +33,7 @@ pub(crate) fn config_value_type_error<'py>( let py = actual_type.py(); let type_name: String = actual_type.getattr("__name__")?.extract()?; call_with( - &imported(py, EXCEPTIONS_MODULE, "ConfigValueTypeError")?, + ®istry::error(py, ErrorId::ConfigValueType)?, &[ ( "message", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs index 67869c37d5..a6dc231cfd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs @@ -5,96 +5,55 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{PyDict, PyDictMethods}; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const AUDITING_MODELS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; -const DLT_MODELS_MODULE: &str = "sqlbuild.integrations.dlt.models"; -const INGESTR_MODELS_MODULE: &str = "sqlbuild.integrations.ingestr.models"; -const CONTRACT_CLASSES: [(&str, &str, &str); 6] = [ - ("schema_column", CONTRACT_MODELS_MODULE, "SchemaColumn"), - ( - "schema_audit_instance", - CONTRACT_MODELS_MODULE, - "SchemaAuditInstance", - ), +const CONTRACT_CLASSES: [(&str, RecordId); 6] = [ + ("schema_column", RecordId::SchemaColumn), + ("schema_audit_instance", RecordId::SchemaAuditInstance), ( "schema_dynamic_column_family", - CONTRACT_MODELS_MODULE, - "SchemaDynamicColumnFamily", - ), - ("source_location", CONTRACT_MODELS_MODULE, "SourceLocation"), - ( - "measurement_thresholds", - AUDITING_MODELS_MODULE, - "MeasurementThresholds", + RecordId::SchemaDynamicColumnFamily, ), + ("source_location", RecordId::SourceLocation), + ("measurement_thresholds", RecordId::MeasurementThresholds), ( "measurement_threshold_bound", - AUDITING_MODELS_MODULE, - "MeasurementThresholdBound", + RecordId::MeasurementThresholdBound, ), ]; -const YAML_ENTRY_CLASSES: [(&str, &str, &str); 13] = [ - ( - "schema_seed_entry", - CONTRACT_MODELS_MODULE, - "SchemaSeedEntry", - ), - ( - "seed_csv_settings", - CONTRACT_MODELS_MODULE, - "SeedCsvSettings", - ), - ("source_entry", CONTRACT_MODELS_MODULE, "SourceEntry"), - ( - "source_column_entry", - CONTRACT_MODELS_MODULE, - "SourceColumnEntry", - ), - ( - "source_freshness_config", - CONTRACT_MODELS_MODULE, - "SourceFreshnessConfig", - ), +const YAML_ENTRY_CLASSES: [(&str, RecordId); 10] = [ + ("schema_seed_entry", RecordId::SchemaSeedEntry), + ("seed_csv_settings", RecordId::SeedCsvSettings), + ("source_entry", RecordId::SourceEntry), + ("source_column_entry", RecordId::SourceColumnEntry), + ("source_freshness_config", RecordId::SourceFreshnessConfig), ( "source_freshness_age_policy", - CONTRACT_MODELS_MODULE, - "SourceFreshnessAgePolicy", + RecordId::SourceFreshnessAgePolicy, ), ( "integration_loader_config", - CONTRACT_MODELS_MODULE, - "IntegrationLoaderConfig", - ), - ( - "ingestr_source_config", - INGESTR_MODELS_MODULE, - "IngestrSourceConfig", - ), - ("dlt_source_config", DLT_MODELS_MODULE, "DltSourceConfig"), - ( - "dlt_resource_config", - DLT_MODELS_MODULE, - "DltResourceConfig", - ), - ( - "source_write_strategy", - CONTRACT_TYPES_MODULE, - "SourceWriteStrategy", + RecordId::IntegrationLoaderConfig, ), + ("source_write_strategy", RecordId::SourceWriteStrategy), ( "source_freshness_strategy", - CONTRACT_TYPES_MODULE, - "SourceFreshnessStrategy", + RecordId::SourceFreshnessStrategy, ), ( "source_freshness_value_kind", - CONTRACT_TYPES_MODULE, - "SourceFreshnessValueKind", + RecordId::SourceFreshnessValueKind, ), ]; +const INTEGRATION_ENTRY_CLASSES: [(&str, ThirdPartyHost); 3] = [ + ("ingestr_source_config", ThirdPartyHost::IngestrSourceConfig), + ("dlt_source_config", ThirdPartyHost::DltSourceConfig), + ("dlt_resource_config", ThirdPartyHost::DltResourceConfig), +]; static CONTRACT_CLASS_TABLE: PyOnceLock> = PyOnceLock::new(); static YAML_ENTRY_CLASS_TABLE: PyOnceLock> = PyOnceLock::new(); @@ -115,8 +74,8 @@ pub(crate) fn yaml_entry_classes(py: Python<'_>) -> PyResult> fn built_contract_classes(py: Python<'_>) -> PyResult> { let classes: Bound<'_, PyDict> = PyDict::new(py); - for (key, module, name) in CONTRACT_CLASSES { - classes.set_item(key, imported(py, module, name)?)?; + for (key, class) in CONTRACT_CLASSES { + classes.set_item(key, registry::class(py, class)?)?; } classes.set_item( "severities", @@ -132,8 +91,11 @@ fn built_contract_classes(py: Python<'_>) -> PyResult> { fn built_yaml_entry_classes(py: Python<'_>) -> PyResult> { let classes: Bound<'_, PyDict> = PyDict::new(py); classes.set_item("contracts", contract_classes(py)?)?; - for (key, module, name) in YAML_ENTRY_CLASSES { - classes.set_item(key, imported(py, module, name)?)?; + for (key, class) in YAML_ENTRY_CLASSES { + classes.set_item(key, registry::class(py, class)?)?; + } + for (key, class) in INTEGRATION_ENTRY_CLASSES { + classes.set_item(key, host::entry(py, class)?)?; } Ok(classes) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs index 574276f280..815c84dfbb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs @@ -10,15 +10,13 @@ use sqlbuild_model_config::header_metadata::main::parse_schema_columns::parse_sc use sqlbuild_model_config::header_metadata::models::ColumnRequest; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::config_errors::config_exception; use crate::bindings::_helpers::model_config::config_errors::native_config_error; use crate::bindings::_helpers::model_config::contract_classes::contract_classes; use crate::bindings::_helpers::model_config::header_metadata::{ContractClasses, column_tuple}; - -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const DECLARATION_ERROR_CLASS: &str = "DeclarationParseError"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `(file_path, label)`: the path and label every message names. pub(crate) type ColumnSite<'a> = (&'a str, &'a str); @@ -62,8 +60,7 @@ fn parse_schema_columns<'py>( fn raised(py: Python<'_>, error: ConfigError, declaration_errors: bool) -> PyErr { let built = (|| { if declaration_errors && error.class == ErrorClass::CompileInput { - return imported(py, DISCOVERY_ERRORS_MODULE, DECLARATION_ERROR_CLASS)? - .call1((error.message,)); + return registry::error(py, ErrorId::DeclarationParse)?.call1((error.message,)); } let native = native_config_error(py, error)?.into_bound(py).into_any(); config_exception(&native, None) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs index b78e53cfda..a3b1ae296a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs @@ -19,8 +19,9 @@ use crate::bindings::_helpers::model_config::config_templates::{ resolve_effective_vars, }; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const UUID_MODULE: &str = "uuid"; const ENVIRONMENT_READ: &str = "env"; const RUN_ID_KEY: &str = "run.id"; @@ -82,27 +83,26 @@ pub(crate) fn expanded_effective_vars<'py>( ) -> PyResult> { let py: Python<'py> = raw_values.py(); if let Some((name, path, byte_offset)) = lone_surrogate_site(raw_values)? { - let error: Bound<'py, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call( - (format!( - "Variable '{name}' holds a lone surrogate in {path} at UTF-8 byte \ + let error: Bound<'py, PyAny> = registry::error(py, ErrorId::CompileInput)?.call( + (format!( + "Variable '{name}' holds a lone surrogate in {path} at UTF-8 byte \ {byte_offset}, which is not valid Unicode text" - ),), - Some(&keywords( - py, - &[( - "help", - PyString::new( - py, - &format!( - "Set '{name}' to valid Unicode text in --vars or the [vars] \ + ),), + Some(&keywords( + py, + &[( + "help", + PyString::new( + py, + &format!( + "Set '{name}' to valid Unicode text in --vars or the [vars] \ table; the value is not shown because it may be a secret" - ), - ) - .into_any(), - )], - )?), - )?; + ), + ) + .into_any(), + )], + )?), + )?; return Err(PyErr::from_value(error)); } applied( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs index 64069fdec1..2d3195aea4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs @@ -7,8 +7,8 @@ use pyo3::types::{PyBytes, PyBytesMethods, PyDict, PyDictMethods, PyString}; use pyo3::{pyclass, pymethods}; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `os.environ`, raising a clear error for a value holding undecodable bytes. #[pyclass(module = "sqlbuild._native", frozen, mapping)] @@ -46,7 +46,7 @@ impl UnicodeEnvironment { ), )?; Err(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call( + registry::error(py, ErrorId::CompileInput)?.call( (format!( "Environment variable '{name}' is not valid UTF-8 text: byte {byte_offset} \ starts an invalid UTF-8 sequence" diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs new file mode 100644 index 0000000000..bd79305828 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs @@ -0,0 +1,262 @@ +//! Adapter runtime entry points: adapter discovery, relation inspection, type system and the shapes adapters consume. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One adapter entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum AdapterHost { + BaseAdapter, + AdapterUserError, + ColumnInfo, + ExpressionInferenceProfile, + RelationInfo, + RelationLookup, + RetentionRequest, + RowDiffSampling, + RowDiffTolerance, + RowDiffTolerances, + TableFreshnessMetadata, + TableFreshnessRequest, + BuiltinAdapter, + FrameworkType, + LifeCycleEventKind, + MigrationTransfer, + RetentionScope, + TableFreshnessStatus, + AdapterSupportsSessionWarehouse, + BuiltinAdapterClasses, + DiscoverProjectAdapters, + ResolveAdapter, + GetColumnsForInspection, + ListRelationsForInspection, + ActiveInspectionCatalog, + InspectionQuerySink, + OpenInspectionCatalog, + RecordInspectionQuery, + BuildRelationLookup, + ResolveRelationLocationQualifiedName, + RunBoundedInspections, + RunRecordedInspectionQuery, + InspectionQueryRecord, + StatementMetadataEffect, + ConditionalResultNullability, + FirstArgNullability, + NormalizeRelationType, + NormalizeType, + SafeCastNullability, +} + +/// Every adapter entry, in `AdapterHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "BaseAdapter", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "AdapterUserError", + kind: HostKind::Exception, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "ColumnInfo", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "ExpressionInferenceProfile", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RelationInfo", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RelationLookup", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RetentionRequest", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RowDiffSampling", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RowDiffTolerance", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RowDiffTolerances", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "TableFreshnessMetadata", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "TableFreshnessRequest", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "BuiltinAdapter", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "FrameworkType", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "LifeCycleEventKind", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "MigrationTransfer", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RetentionScope", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "TableFreshnessStatus", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "adapter_supports_session_warehouse", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "builtin_adapter_classes", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "discover_project_adapters", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "resolve_adapter", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "get_columns_for_inspection", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "list_relations_for_inspection", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "active_inspection_catalog", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "inspection_query_sink", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "open_inspection_catalog", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "record_inspection_query", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "build_relation_lookup", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "resolve_relation_location_qualified_name", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "run_bounded_inspections", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "run_recorded_inspection_query", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "InspectionQueryRecord", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "StatementMetadataEffect", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "conditional_result_nullability", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "first_arg_nullability", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "normalize_relation_type", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "normalize_type", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "safe_cast_nullability", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, +]; + +impl AdapterHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::Adapter, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: AdapterHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs new file mode 100644 index 0000000000..aa58559825 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs @@ -0,0 +1,20 @@ +//! The host module and the one table of every entry native code may reach through it. + +use crate::bindings::_helpers::native_host::models::HostEntry; +use crate::bindings::_helpers::native_host::{adapter, executor, facade, third_party, user_code}; + +/// The only `sqlbuild` Python module native code imports by path. +pub(crate) const HOST_MODULE: &str = "sqlbuild._native_host"; +/// The module a custom-rule host process runs with `python -m`, a user-code edge reached by path. +pub(crate) const CUSTOM_HOST_LAUNCH_MODULE: &str = + "sqlbuild.rule_engine._helpers.host.custom_host_launch"; +/// The host module function naming each entry's target module and attribute. +pub(crate) const HOST_TARGETS_FUNCTION: &str = "host_targets"; +/// Every host entry, one group per edge in `HostEdge` order. +pub(crate) const HOST_ENTRIES: [&[HostEntry]; 5] = [ + adapter::ENTRIES, + user_code::ENTRIES, + executor::ENTRIES, + third_party::ENTRIES, + facade::ENTRIES, +]; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs new file mode 100644 index 0000000000..d9daaee8a8 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs @@ -0,0 +1,970 @@ +//! Executor hand-off and results, and the runtime services commands run inside. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One executor entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ExecutorHost { + CostResourceContext, + RunCostSummary, + CostCapability, + CostStatus, + BoundSentinel, + ReadProcessResources, + SqlPrivateRecordMarker, + DiagnosticsContext, + LogDebugEvent, + LogSql, + ProcessResourceReporting, + ProcessResourceUsage, + ExecutorInputError, + CurrentAuditResultProjection, + AuditExecutionResult, + BuildCallbacks, + BuildCustomizations, + BuildExecutionResult, + BuildInitialState, + BuildRuntimeParams, + FunctionExecutionResult, + SeedExecutionResult, + BuildStatus, + BuildDestinationTransientModels, + BuildDestinationRetentionRequests, + CopyCloneFingerprints, + RunPrephaseCloneStream, + ExecuteClone, + CloneExecutionInput, + CloneItemResult, + CloneSourceEntries, + CloneStatus, + QueryDiffArtifactLifecycle, + FullDiffSizeGuardError, + ExecuteQueryDiff, + ExecuteDiff, + DiffExecutionOptions, + DiffExecutionResult, + FullDiffSizeLimits, + RowDiffSamplingOverride, + ExecuteJanitorPlan, + JanitorArchiveCandidate, + JanitorArchivedRelation, + JanitorBlockedSchema, + JanitorDeleteCandidate, + JanitorDirectStatePruneCandidate, + JanitorOldNameView, + JanitorOldNameViewPlanning, + JanitorPlan, + JanitorQueryDiffArtifactCandidate, + JanitorRelationKey, + JanitorSkippedRelation, + JanitorSkippedSchema, + RunLoadPipeline, + LoadCallbacks, + LoadExecutionResult, + LoadRuntimeParams, + MigrationRelationForLocation, + IsCompatibilityView, + AuditExecutionError, + AuditPipelineCallbacks, + RunAuditPipelineWithCallbacks, + RunBuildPipeline, + RunScenarioCapturePipeline, + RunScenarioLocalTestPipeline, + RunScenarioTestPipeline, + RunSeedPipeline, + RunTestPipeline, + SelectScenarioSnapshotCaptureCandidates, + TestPipelineCallbacks, + ExecutePythonChecks, + ExecuteIngressPythonLoaderNodes, + RunIngressPythonLoaderNodes, + CreateReadSidePythonExecutionTracker, + IngressCallbacks, + PythonCheckCallbacks, + PythonCheckExecutionResult, + PythonNodeExecutionResult, + PythonNodeRunState, + PythonNodeRuntime, + ModelExecutionResult, + ScenarioCaptureSettings, + ScenarioLocalReplaySource, + ScenarioSnapshotCaptureLimits, + ExecutionStatus, + SqlTestExecutionResult, + MicrobatchEventCodec, + MicrobatchColumns, + MicrobatchColumnTypes, + MicrobatchDirectIndexes, + MicrobatchGenerationWildcard, + MicrobatchTableName, + MicrobatchStateError, + MicrobatchCoverageProjection, + MicrobatchInterval, + ProjectedMicrobatchInterval, + ReplayRequirementProjection, + ReplayRequirementState, + TextComputeLogTee, + CaptureAlreadyExistsError, + CaptureNotFoundError, + CaptureStateError, + ComputeLogMetadataError, + ComputeLogPathError, + InvalidCaptureIdError, + InvalidComputeLogCursorError, + InvalidComputeLogLimitError, + CaptureByteCounts, + CaptureInventory, + CaptureInventoryItem, + CaptureMetadata, + ComputeLogReadChunk, + FinalCaptureMetadata, + PruneResult, + ComputeLogStream, + ConnectionHooks, + ExecutionResourceKind, + ExecutionHistoryStorageError, + IntegrityConflictError, + InvalidCursorError, + InvalidEventError, + InvalidFilterError, + InvalidLimitError, + ProjectionConsistencyError, + UnsupportedSchemaVersionError, + EventPage, + RunPage, + RunRecord, + StoredEvent, + RunStatus, + ExecutionDurationLimitError, + ExecutionDeadlineScope, + EventDispatcher, + OperationLifecycle, + ObservabilityValidationError, + CreateLifecycleEvent, + CurrentExecutionIdentity, + DiagnosticLogToJson, + DispatcherScope, + InvocationExternalContextScope, + InvocationScope, + LifecycleEventFromJson, + LifecycleEventToJson, + RunScope, + DiagnosticLog, + LifecycleEvent, + OperationAttributes, + CommandOutputValidationError, + OutputCaptureInputError, + ConfiguredOutputCaptureScope, + BoundCommandOutputSink, + CommandOutputCaptureSummary, + CommandOutputRecord, + OutputCaptureContext, + CommandOutputStream, + OutputRecordPriority, + SqlValueRenderingError, +} + +/// Every executor entry, in `ExecutorHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "CostResourceContext", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunCostSummary", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CostCapability", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CostStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BoundSentinel", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "read_process_resources", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SQL_PRIVATE_RECORD_MARKER", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "diagnostics_context", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "log_debug_event", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "log_sql", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "process_resource_reporting", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ProcessResourceUsage", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutorInputError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "current_audit_result_projection", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "AuditExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildCustomizations", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildInitialState", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildRuntimeParams", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FunctionExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SeedExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "build_destination_transient_models", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "build_destination_retention_requests", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "copy_clone_fingerprints", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_prephase_clone_stream", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_clone", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneExecutionInput", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneItemResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneSourceEntries", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "QueryDiffArtifactLifecycle", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FullDiffSizeGuardError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_query_diff", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_diff", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "DiffExecutionOptions", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "DiffExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FullDiffSizeLimits", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RowDiffSamplingOverride", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_janitor_plan", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorArchiveCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorArchivedRelation", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorBlockedSchema", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorDeleteCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorDirectStatePruneCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorOldNameView", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorOldNameViewPlanning", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorPlan", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorQueryDiffArtifactCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorRelationKey", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorSkippedRelation", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorSkippedSchema", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_load_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LoadCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LoadExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LoadRuntimeParams", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "migration_relation_for_location", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "is_compatibility_view", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "AuditExecutionError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "AuditPipelineCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_audit_pipeline_with_callbacks", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_build_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scenario_capture_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scenario_local_test_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scenario_test_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_seed_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_test_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "select_scenario_snapshot_capture_candidates", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "TestPipelineCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_python_checks", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_ingress_python_loader_nodes", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_ingress_python_loader_nodes", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "create_read_side_python_execution_tracker", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "IngressCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonCheckCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonCheckExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonNodeExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonNodeRunState", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonNodeRuntime", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ModelExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ScenarioCaptureSettings", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ScenarioLocalReplaySource", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ScenarioSnapshotCaptureLimits", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SqlTestExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchEventCodec", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_COLUMNS", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_COLUMN_TYPES", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_DIRECT_INDEXES", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_GENERATION_WILDCARD", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_TABLE_NAME", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchStateError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchCoverageProjection", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchInterval", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ProjectedMicrobatchInterval", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ReplayRequirementProjection", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ReplayRequirementState", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "TextComputeLogTee", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureAlreadyExistsError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureNotFoundError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureStateError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogMetadataError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogPathError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidCaptureIdError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidComputeLogCursorError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidComputeLogLimitError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureByteCounts", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureInventory", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureInventoryItem", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureMetadata", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogReadChunk", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FinalCaptureMetadata", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PruneResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogStream", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ConnectionHooks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionResourceKind", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionHistoryStorageError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "IntegrityConflictError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidCursorError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidEventError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidFilterError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidLimitError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ProjectionConsistencyError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "UnsupportedSchemaVersionError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "EventPage", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunPage", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunRecord", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "StoredEvent", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionDurationLimitError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execution_deadline_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "EventDispatcher", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OperationLifecycle", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ObservabilityValidationError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "create_lifecycle_event", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "current_execution_identity", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "diagnostic_log_to_json", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "dispatcher_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "invocation_external_context_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "invocation_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "lifecycle_event_from_json", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "lifecycle_event_to_json", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "DiagnosticLog", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LifecycleEvent", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OperationAttributes", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputValidationError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OutputCaptureInputError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "configured_output_capture_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BoundCommandOutputSink", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputCaptureSummary", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputRecord", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OutputCaptureContext", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputStream", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OutputRecordPriority", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SqlValueRenderingError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, +]; + +impl ExecutorHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::Executor, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: ExecutorHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs new file mode 100644 index 0000000000..09f2323f5e --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs @@ -0,0 +1,58 @@ +//! Public facade entry points: the CLI entry point, compile reuse and the package itself. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One facade entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum FacadeHost { + PackageFile, + DispatchWithComputeLogs, + ProviderSettingsInputs, + StartupInputs, + CompileReuseRequest, +} + +/// Every facade entry, in `FacadeHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "package_file", + kind: HostKind::Value, + edge: HostEdge::Facade, + }, + HostEntry { + name: "dispatch_with_compute_logs", + kind: HostKind::Function, + edge: HostEdge::Facade, + }, + HostEntry { + name: "provider_settings_inputs", + kind: HostKind::Function, + edge: HostEdge::Facade, + }, + HostEntry { + name: "startup_inputs", + kind: HostKind::Function, + edge: HostEdge::Facade, + }, + HostEntry { + name: "CompileReuseRequest", + kind: HostKind::Class, + edge: HostEdge::Facade, + }, +]; + +impl FacadeHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::Facade, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: FacadeHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs new file mode 100644 index 0000000000..29b9e8ec96 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs @@ -0,0 +1,54 @@ +//! Resolve host entries: targets read once from `sqlbuild._native_host`, attributes read live. + +use pyo3::exceptions::PyImportError; +use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; +use pyo3::sync::PyOnceLock; +use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple, PyTupleMethods}; + +use crate::bindings::_helpers::native_host::constants::{ + HOST_ENTRIES, HOST_MODULE, HOST_TARGETS_FUNCTION, +}; +use crate::bindings::_helpers::native_host::models::HostId; + +/// One entry's target module path and attribute name. +type Target = (Py, Py); + +/// Every entry's target, grouped and ordered as `HOST_ENTRIES`. +static TARGETS: PyOnceLock>> = PyOnceLock::new(); + +fn missing(name: &str) -> pyo3::PyErr { + PyImportError::new_err(format!("{HOST_MODULE} does not define host entry '{name}'")) +} + +/// Read every entry's target from the host module, failing on the first entry it lacks. +fn host_targets(py: Python<'_>) -> PyResult>> { + let declared: Bound<'_, PyDict> = py + .import(HOST_MODULE)? + .getattr(HOST_TARGETS_FUNCTION)? + .call0()? + .cast_into::()?; + let mut groups: Vec> = Vec::with_capacity(HOST_ENTRIES.len()); + for group in HOST_ENTRIES { + let mut targets: Vec = Vec::with_capacity(group.len()); + for entry in group { + let target: Bound<'_, PyTuple> = declared + .get_item(entry.name)? + .ok_or_else(|| missing(entry.name))? + .cast_into::()?; + targets.push(( + target.get_item(0)?.cast_into::()?.unbind(), + target.get_item(1)?.cast_into::()?.unbind(), + )); + } + groups.push(targets); + } + Ok(groups) +} + +/// The host entry `id`: its target module imported and the attribute read on every call. +pub(crate) fn entry<'py>(py: Python<'py>, id: impl Into) -> PyResult> { + let id: HostId = id.into(); + let targets: &Vec> = TARGETS.get_or_try_init(py, || host_targets(py))?; + let (module, attribute): &Target = &targets[id.edge as usize][id.index]; + py.import(module.bind(py))?.getattr(attribute.bind(py)) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs new file mode 100644 index 0000000000..c9c575a440 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs @@ -0,0 +1,11 @@ +//! The one Python module native code imports by path, and the staying-Python entries it names. + +pub(crate) mod adapter; +pub(crate) mod constants; +pub(crate) mod executor; +pub(crate) mod facade; +pub(crate) mod host; +pub(crate) mod models; +pub(crate) mod registration; +pub(crate) mod third_party; +pub(crate) mod user_code; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs new file mode 100644 index 0000000000..a8bc406038 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs @@ -0,0 +1,60 @@ +//! Host entries: the staying-Python names native code reaches through `sqlbuild._native_host`. + +/// What a host entry resolves to. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum HostKind { + Function, + Class, + Exception, + Value, +} + +impl HostKind { + /// The kind as the import test and `_native.HOST_ENTRIES` spell it. + pub(crate) const fn label(self) -> &'static str { + match self { + Self::Function => "function", + Self::Class => "class", + Self::Exception => "exception", + Self::Value => "value", + } + } +} + +/// Which boundary a host entry crosses; also its group in `HOST_ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum HostEdge { + Adapter, + UserCode, + Executor, + ThirdParty, + Facade, +} + +impl HostEdge { + /// The edge as `_native.HOST_ENTRIES` spells it. + pub(crate) const fn label(self) -> &'static str { + match self { + Self::Adapter => "adapter", + Self::UserCode => "user_code", + Self::Executor => "executor", + Self::ThirdParty => "third_party", + Self::Facade => "facade", + } + } +} + +/// One host entry: its `sqlbuild._native_host` name, kind and edge. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct HostEntry { + pub(crate) name: &'static str, + pub(crate) kind: HostKind, + pub(crate) edge: HostEdge, +} + +/// A host entry by edge and position, from any edge's entry enum. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct HostId { + pub(crate) edge: HostEdge, + pub(crate) index: usize, +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs new file mode 100644 index 0000000000..d6ae0baed6 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs @@ -0,0 +1,15 @@ +//! Register `_native.HOST_ENTRIES`, the host table the Python import test checks against. + +use pyo3::prelude::{Bound, PyModule, PyModuleMethods, PyResult}; +use pyo3::types::PyTuple; + +use crate::bindings::_helpers::native_host::constants::HOST_ENTRIES; + +pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { + let entries: Vec<(&str, &str, &str)> = HOST_ENTRIES + .iter() + .flat_map(|group| group.iter()) + .map(|entry| (entry.name, entry.kind.label(), entry.edge.label())) + .collect(); + module.add("HOST_ENTRIES", PyTuple::new(module.py(), entries)?) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs new file mode 100644 index 0000000000..8c65bee3db --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs @@ -0,0 +1,250 @@ +//! Glue around Python-only third-party integrations: dbt, dlt, ingestr, Dagster and rivers. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One third-party entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ThirdPartyHost { + AssetSelectionCommands, + CheckMetadataExcludedKeys, + CloneCommand, + CompletedExecutionStatuses, + DagsterAssetEnabledField, + DagsterAssetNodeKinds, + DagsterCheckEnabledField, + DagsterCheckNameField, + DagsterDirectKindNodeKinds, + DefaultSelectableNodeKinds, + ExplicitSelectionFlags, + InsufficientCheckStatus, + LiveEventCommands, + LoaderNodeKind, + LoadSelectableNodeKinds, + MaterializableNodeKinds, + ModelNodeKind, + ScenarioCheckKind, + SourceNodeKind, + StandaloneCheckCommands, + SuccessExecutionStatus, + ViewMaterializationType, + WarningCheckSeverity, + DagsterDagInputError, + LoadDbtYaml, + RenderSelectedDbtProfileOutput, + DbtProfileError, + DltIntegrationKind, + DltIntegrationError, + DltLoaderFunction, + DltResourceConfig, + DltSourceConfig, + IngestrIntegrationKind, + IngestrIntegrationError, + IngestrLoaderFunction, + IngestrSourceConfig, + RiversAssetNodeKinds, +} + +/// Every third-party entry, in `ThirdPartyHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "ASSET_SELECTION_COMMANDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "CHECK_METADATA_EXCLUDED_KEYS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "CLONE_COMMAND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "COMPLETED_EXECUTION_STATUSES", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_ASSET_ENABLED_FIELD", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_ASSET_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_CHECK_ENABLED_FIELD", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_CHECK_NAME_FIELD", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_DIRECT_KIND_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DEFAULT_SELECTABLE_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "EXPLICIT_SELECTION_FLAGS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "INSUFFICIENT_CHECK_STATUS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "LIVE_EVENT_COMMANDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "LOADER_NODE_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "LOAD_SELECTABLE_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "MATERIALIZABLE_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "MODEL_NODE_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "SCENARIO_CHECK_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "SOURCE_NODE_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "STANDALONE_CHECK_COMMANDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "SUCCESS_EXECUTION_STATUS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "VIEW_MATERIALIZATION_TYPE", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "WARNING_CHECK_SEVERITY", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DagsterDagInputError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "load_dbt_yaml", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "render_selected_dbt_profile_output", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DbtProfileError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DLT_INTEGRATION_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DltIntegrationError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "dlt_loader_function", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DltResourceConfig", + kind: HostKind::Class, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DltSourceConfig", + kind: HostKind::Class, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "INGESTR_INTEGRATION_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "IngestrIntegrationError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "ingestr_loader_function", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "IngestrSourceConfig", + kind: HostKind::Class, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "RIVERS_ASSET_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, +]; + +impl ThirdPartyHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::ThirdParty, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: ThirdPartyHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs new file mode 100644 index 0000000000..12156b2506 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs @@ -0,0 +1,370 @@ +//! Entry points that run user Python: macros, Python nodes, providers, custom rules and sinks. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One user-code entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum UserCodeHost { + PythonResourceRows, + TargetContextWithMacroCallStore, + InventoryProjectMacros, + LoadProjectMacros, + MacroInvocationContext, + DiscoverCommandOutputSinkFunctions, + ImportPythonRootModule, + InstallPythonRootPackage, + LoadMaterializeFunction, + LoadProviderModule, + LoadPythonNodeModule, + LoadSinkModule, + ModuleFunctions, + ModuleProviderClasses, + ParameterAnnotations, + ProviderUsages, + RequiresArguments, + RestorePythonRootModules, + SourceLine, + ValidateEventExporterSignature, + ValidateDiscoveredInputs, + DiscoverRuntimeExtensions, + RunWithMacroBridge, + MacroStoreModulePaths, + ActiveMacroBridge, + LoadCustomMaterializations, + PythonNodeDependencyKey, + PythonNodeIdentity, + BuildHookIdentities, + PythonNodeKind, + PythonNodeStatus, + ProviderInputError, + BuildProviderSession, + ReadAssetDefinition, + ReadAuditFactoryDefinition, + ReadCheckDefinition, + ReadFactoryDefinition, + ReadHookDefinition, + ReadLoaderDefinition, + ReadTaskDefinition, + AuditCase, + RelationRenderer, + SqlResourceRef, + PythonCheckSeverity, + SqlResourceRefKind, + LoadCustomRules, + CustomRulePayloads, + FactOutcomeDigest, + FullFactKeys, + IsTreeFact, + SharedFactEncoder, + BuildRuleFactViews, + EventExporterInputError, + EventExporterStateError, + InvokeLifecycleEventSink, + RunCommandOutputSinks, + GetLifecycleEventSinkDefinition, +} + +/// Every user-code entry, in `UserCodeHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "python_resource_rows", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "target_context_with_macro_call_store", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "inventory_project_macros", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_project_macros", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "macro_invocation_context", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "discover_command_output_sink_functions", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "import_python_root_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "install_python_root_package", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_materialize_function", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_provider_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_python_node_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_sink_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "module_functions", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "module_provider_classes", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "parameter_annotations", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "provider_usages", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "requires_arguments", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "restore_python_root_modules", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "source_line", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "validate_event_exporter_signature", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "validate_discovered_inputs", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "discover_runtime_extensions", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "run_with_macro_bridge", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "macro_store_module_paths", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "active_macro_bridge", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_custom_materializations", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "python_node_dependency_key", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "python_node_identity", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "build_hook_identities", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "PythonNodeKind", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "PythonNodeStatus", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "ProviderInputError", + kind: HostKind::Exception, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "build_provider_session", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_asset_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_audit_factory_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_check_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_factory_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_hook_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_loader_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_task_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "AuditCase", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "RELATION_RENDERER", + kind: HostKind::Value, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "SqlResourceRef", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "PythonCheckSeverity", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "SqlResourceRefKind", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_custom_rules", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "custom_rule_payloads", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "fact_outcome_digest", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "full_fact_keys", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "is_tree_fact", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "shared_fact_encoder", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "build_rule_fact_views", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "EventExporterInputError", + kind: HostKind::Exception, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "EventExporterStateError", + kind: HostKind::Exception, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "invoke_lifecycle_event_sink", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "run_command_output_sinks", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "get_lifecycle_event_sink_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, +]; + +impl UserCodeHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::UserCode, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: UserCodeHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs index de6696dc5b..2334c9d65f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs @@ -13,7 +13,8 @@ use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{ call_with, context_variable, imported, }; -use crate::bindings::_helpers::output_capture::records::MODELS_MODULE; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const CONTEXT_VARIABLE: &str = "sqlbuild_output_capture_context"; static CURRENT_CONTEXT: PyOnceLock> = PyOnceLock::new(); @@ -29,7 +30,7 @@ pub(crate) struct CommandOutputContext { impl CommandOutputContext { fn __enter__(&self, py: Python<'_>) -> PyResult<()> { let context = call_with( - &imported(py, MODELS_MODULE, "OutputCaptureContext")?, + &host::entry(py, ExecutorHost::OutputCaptureContext)?, &[( "external_context", imported(py, "builtins", "dict")?.call1((self.external_context.bind(py),))?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs index 37ae8d21a6..9d0ce25de1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs @@ -23,9 +23,10 @@ use sqlbuild_core::command_output::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_threads::constants::OUTPUT_SUPPRESSED_ATTRIBUTE; use crate::bindings::_helpers::native_threads::exit_signal::signal_interpreter_exit; -use crate::bindings::_helpers::output_capture::records::{MODELS_MODULE, TYPES_MODULE}; use crate::bindings::_helpers::output_capture::values::{ freeze, object, output_error, pass_text, python_text, unencodable, }; @@ -188,7 +189,7 @@ impl Shared { ("occurred_at", self.occurred_at(py)?), ( "stream", - imported(py, TYPES_MODULE, "CommandOutputStream")? + host::entry(py, ExecutorHost::CommandOutputStream)? .call1((stream_value(draft.stream),))?, ), ("message", python_text(py, &draft.message)?), @@ -197,7 +198,7 @@ impl Shared { ("chunk_count", object(py, draft.chunk_count)?), ( "priority", - imported(py, TYPES_MODULE, "OutputRecordPriority")? + host::entry(py, ExecutorHost::OutputRecordPriority)? .call1((priority_value(draft.priority),))?, ), ]; @@ -206,7 +207,7 @@ impl Shared { fields.push(("dropped_records", object(py, draft.dropped_records)?)); } call_with( - &imported(py, MODELS_MODULE, "CommandOutputRecord")?, + &host::entry(py, ExecutorHost::CommandOutputRecord)?, &fields, ) } @@ -337,7 +338,7 @@ impl Shared { } fn input_error(py: Python<'_>, message: &str) -> PyErr { - output_error(py, "OutputCaptureInputError", message) + output_error(py, ExecutorHost::OutputCaptureInputError, message) } #[pymethods] @@ -547,7 +548,7 @@ impl OutputCaptureDispatcher { } let (accepted, delivered, failed, dropped, depth, capacity, flush_complete) = counts; call_with( - &imported(py, MODELS_MODULE, "CommandOutputCaptureSummary")?, + &host::entry(py, ExecutorHost::CommandOutputCaptureSummary)?, &[ ("accepted", object(py, accepted)?), ("delivered", object(py, delivered)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs index 33943740ea..bb6c15c7bb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs @@ -16,12 +16,12 @@ use sqlbuild_core::json::main::dumps::dumps; use sqlbuild_core::json::models::{JsonDialect, JsonValue, StdlibJsonOptions}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::output_capture::values::{ freeze, json_value, object, output_error, plain_int, validation_error, }; -pub(crate) const MODELS_MODULE: &str = "sqlbuild.runtime.output_capture.models"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild.runtime.output_capture.types"; const ENVELOPE_FIELDS: [&str; 16] = [ "record_id", "record_type", @@ -96,20 +96,14 @@ fn record_shape(record: &Bound<'_, PyAny>) -> PyResult { }, sequence: plain_int(&field("sequence")?), occurred_at_is_utc: utc_aware(&field("occurred_at")?)?, - stream_is_known: field("stream")?.is_instance(&imported( - py, - TYPES_MODULE, - "CommandOutputStream", - )?)?, + stream_is_known: field("stream")? + .is_instance(&host::entry(py, ExecutorHost::CommandOutputStream)?)?, message_is_text: field("message")?.is_instance_of::(), chunk_index: plain_int(&field("chunk_index")?), chunk_count: plain_int(&field("chunk_count")?), dropped_records: plain_int(&field("dropped_records")?), - priority_is_known: field("priority")?.is_instance(&imported( - py, - TYPES_MODULE, - "OutputRecordPriority", - )?)?, + priority_is_known: field("priority")? + .is_instance(&host::entry(py, ExecutorHost::OutputRecordPriority)?)?, record_type: optional_text(&field("record_type")?)?, schema_version: plain_int(&field("schema_version")?), }) @@ -186,17 +180,17 @@ fn record_from_fields<'py>(data: &Bound<'py, PyDict>) -> PyResult occurred_at(&value(name)?)?, "stream" => { - imported(py, TYPES_MODULE, "CommandOutputStream")?.call1((value(name)?,))? + host::entry(py, ExecutorHost::CommandOutputStream)?.call1((value(name)?,))? } "priority" => { - imported(py, TYPES_MODULE, "OutputRecordPriority")?.call1((value(name)?,))? + host::entry(py, ExecutorHost::OutputRecordPriority)?.call1((value(name)?,))? } _ => value(name)?, }; fields.push((name, converted)); } call_with( - &imported(py, MODELS_MODULE, "CommandOutputRecord")?, + &host::entry(py, ExecutorHost::CommandOutputRecord)?, &fields, ) } @@ -256,11 +250,7 @@ pub(crate) fn command_output_from_json<'py>( "command output external_context must be an object", )); } - let validation_class = imported( - py, - crate::bindings::_helpers::output_capture::values::EXCEPTIONS_MODULE, - "CommandOutputValidationError", - )?; + let validation_class = host::entry(py, ExecutorHost::CommandOutputValidationError)?; let record = record_from_fields(data).map_err(|error| { let typed = error.is_instance_of::(py) || error.is_instance_of::(py); @@ -283,7 +273,7 @@ fn validate_depth(value: &Bound<'_, PyAny>, depth: usize) -> Result<(), PyErr> { if depth > MAX_INVOCATION_CONTEXT_DEPTH { return Err(output_error( value.py(), - "OutputCaptureInputError", + ExecutorHost::OutputCaptureInputError, &format!( "{INVOCATION_CONTEXT_ENV} must not exceed {MAX_INVOCATION_CONTEXT_DEPTH} levels" ), @@ -302,7 +292,7 @@ fn validate_depth(value: &Bound<'_, PyAny>, depth: usize) -> Result<(), PyErr> { } fn input_error(py: Python<'_>, message: &str) -> PyErr { - output_error(py, "OutputCaptureInputError", message) + output_error(py, ExecutorHost::OutputCaptureInputError, message) } /// Validated opaque invocation metadata supplied by an integration through the environment. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs index d78dee5f8e..3060ce4e39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs @@ -13,14 +13,15 @@ use sqlbuild_core::json::models::{JsonInteger, JsonValue}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::macro_bridge::store_keys::text; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.output_capture.exceptions"; const SURROGATE_PASS: &str = "surrogatepass"; const UTF8: &str = "utf-8"; /// The exception class `name` of the output capture exceptions module, raised with `message`. -pub(crate) fn output_error(py: Python<'_>, name: &str, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, name).and_then(|class| class.call1((message,))) { +pub(crate) fn output_error(py: Python<'_>, name: ExecutorHost, message: &str) -> PyErr { + match host::entry(py, name).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -28,7 +29,7 @@ pub(crate) fn output_error(py: Python<'_>, name: &str, message: &str) -> PyErr { /// `CommandOutputValidationError(message)`. pub(crate) fn validation_error(py: Python<'_>, message: &str) -> PyErr { - output_error(py, "CommandOutputValidationError", message) + output_error(py, ExecutorHost::CommandOutputValidationError, message) } /// A Python `str` as `surrogatepass` UTF-8 bytes. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs index bd714e7a32..2db6d6e932 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs @@ -10,14 +10,12 @@ use crate::bindings::_helpers::build_command::support::keyword_relay; use crate::bindings::_helpers::cli::progress_text::with_verbose_inspection; use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_connection_context; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; -const PRESENTATION_MODELS: &str = "sqlbuild._native"; const COMMAND_NAME: &str = "plan"; const DEFAULT_SECTION_ENTRIES: i64 = 50; const CONNECTION_TIMING: [&str; 2] = ["connection_count", "elapsed_seconds"]; @@ -66,7 +64,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("extract_output_column_locations", flag(py, false)), @@ -76,7 +74,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = resolve_adapter_connection_context( &discovered, &project_dir, @@ -93,7 +91,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.getattr("adapter_name")?), ("stream", stream.clone()), @@ -101,7 +99,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", flag(py, use_color)), @@ -110,7 +108,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = request.getattr("load_sources")?; let load_sources: Bound<'py, PyAny> = if requested_load.is_none() { call_with( - &imported(py, NATIVE_MODULE, "build_effective_settings_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", discovered.clone())], )? .getattr("auto_load_sources")? @@ -134,7 +132,7 @@ fn connection_hooks<'py>(invocation: &PlanInvocation<'py>) -> PyResult = invocation.stream.py(); let connection: &Bound<'py, PyAny> = &invocation.connection_progress; call_with( - &imported(py, CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_progress", @@ -168,11 +166,7 @@ fn compile_pipeline<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.project_dir.clone()), ("discovered_inputs", invocation.discovered.clone()), @@ -213,9 +207,9 @@ fn compile_pipeline<'py>( )?; options.set_item("record_migration_fingerprints", false)?; let pipeline_options: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?.call((), Some(&options))?; + registry::class(py, RecordId::CompilePipelineOptions)?.call((), Some(&options))?; call_with( - &imported(py, NATIVE_MODULE, "run_compile_pipeline")?, + ®istry::native(py, NativeAttr::RunCompilePipeline)?, &[ ("discovered_inputs", invocation.discovered.clone()), ("adapter", invocation.context.getattr("adapter")?), @@ -235,7 +229,7 @@ fn write_output( let python_entries: Bound<'_, PyAny> = pipeline.getattr("python_plan_entries")?; if request.getattr("json_output")?.is_truthy()? { let text: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_plan_json")?, + ®istry::native(py, NativeAttr::FormatPlanJson)?, &[("plan", plan), ("python_plan_entries", python_entries)], )?; imported(py, "builtins", "print")?.call1((text,))?; @@ -251,14 +245,14 @@ fn write_output( }, )?; let text: String = call_with( - &imported(py, NATIVE_MODULE, "format_plan")?, + ®istry::native(py, NativeAttr::FormatPlan)?, &[ ("plan", plan), ("full_refresh", request.getattr("full_refresh")?), ("use_color", flag(py, invocation.use_color)), ( "display_options", - imported(py, PRESENTATION_MODELS, "DisplayOptions")?.call((), Some(&display))?, + registry::class(py, RecordId::DisplayOptions)?.call((), Some(&display))?, ), ("python_plan_entries", python_entries), ("include_direct_freshness_diagnostics", flag(py, false)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs index d26831217b..3057151148 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs @@ -8,11 +8,9 @@ use pyo3::wrap_pyfunction; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULE_MODELS: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; const LIST_ACTION: &str = "list"; const SHOW_ACTION: &str = "show"; const SKILLS_ACTION: &str = "skills"; @@ -52,7 +50,7 @@ fn run_skills( let py: Python<'_> = request.py(); let check: bool = request.getattr("skills_check")?.is_truthy()?; let fresh: bool = call_with( - &imported(py, NATIVE_MODULE, "install_rules_skills")?, + ®istry::native(py, NativeAttr::InstallRulesSkills)?, &[ ("config", config.clone()), ("project_dir", project_dir.clone()), @@ -130,7 +128,7 @@ fn show_rule( let rule: Bound<'_, PyAny> = rule?; if rule.getattr("code")?.extract::()? == selector { let text: String = call_with( - &imported(py, NATIVE_MODULE, "format_rule")?, + ®istry::native(py, NativeAttr::FormatRule)?, &[("rule", rule), ("config", config.clone())], )? .extract()?; @@ -156,13 +154,13 @@ fn rule_inputs<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let select: Bound<'py, PyAny> = request.getattr("select")?; let exclude: Bound<'py, PyAny> = request.getattr("exclude")?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", select.clone()), ("exclude", exclude.clone()), @@ -170,7 +168,7 @@ fn rule_inputs<'py>( ], )?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -178,14 +176,14 @@ fn rule_inputs<'py>( )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; let graph: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_graph")?, + ®istry::native(py, NativeAttr::BuildProjectGraph)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), ], )?; let selected: Bound<'py, PyAny> = call_with( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", select), @@ -232,7 +230,7 @@ fn run_rule_selection( replaced.set_item("ignore", PyTuple::empty(py))?; let dialect: Bound<'_, PyAny> = adapter.call_method0("sql_analysis_dialect")?; let result: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "run_rules")?, + ®istry::native(py, NativeAttr::RunRules)?, &[ ("graph", graph), ("discovered_inputs", discovered), @@ -260,7 +258,7 @@ fn run_rule_selection( ], )?; let note: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_skipped_type_proof_rules")?, + ®istry::native(py, NativeAttr::FormatSkippedTypeProofRules)?, &[("codes", result.getattr("skipped_type_proof_rules")?)], )?; if !note.is_none() { @@ -271,11 +269,11 @@ fn run_rule_selection( fields.set_item(field, result.getattr(field)?)?; } let text: String = call_with( - &imported(py, NATIVE_MODULE, "format_rules_result")?, + ®istry::native(py, NativeAttr::FormatRulesResult)?, &[ ( "result", - imported(py, RULE_MODELS, "RulesResult")?.call((), Some(&fields))?, + registry::class(py, RecordId::RulesResult)?.call((), Some(&fields))?, ), ("json_output", request.getattr("json_output")?), ], @@ -308,7 +306,7 @@ fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { } .call_method0("resolve")?; let config: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "load_rules_config")?, + ®istry::native(py, NativeAttr::LoadRulesConfig)?, &[("project_dir", project_dir.clone())], )?; let action: String = request.getattr("action")?.extract()?; @@ -316,7 +314,7 @@ fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { return run_skills(request, &config, &project_dir); } let catalogue: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_rule_catalogue")?, + ®istry::native(py, NativeAttr::BuildRuleCatalogue)?, &[ ("config", config.clone()), ("project_dir", project_dir.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs index fbf071b610..f3d929fdb1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs @@ -2,15 +2,16 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::conversion::GRAIN_NAMES; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::display::{ aligned_timestamp_bounds, resolve_effective_microbatch_batch_size, }; use crate::bindings::_helpers::plan_output::values::{optional_text, text}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const BOUND_TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; -const CURSOR_MODELS_MODULE: &str = "sqlbuild._native"; const MICROBATCH_MODE: &str = "microbatch"; const TIMESTAMP_CURSOR: &str = "timestamp"; const INTEGER_CURSOR: &str = "integer"; @@ -84,14 +85,14 @@ fn count_batches( let (start, end) = (bounds.getattr("start")?, bounds.getattr("end")?); if cursor_type == TIMESTAMP_CURSOR { let duration: Bound<'_, PyAny> = - imported(py, CURSOR_MODELS_MODULE, "Duration")?.call_method1("parse", (batch_size,))?; + registry::class(py, RecordId::Duration)?.call_method1("parse", (batch_size,))?; if duration.is_none() { return Ok(None); } let temporal = |value: &Bound<'_, PyAny>| -> PyResult { Ok( - value.is_instance(&imported(py, CURSOR_MODELS_MODULE, "DateValue")?)? - || value.is_instance(&imported(py, CURSOR_MODELS_MODULE, "TimestampValue")?)?, + value.is_instance(®istry::class(py, RecordId::DateValue)?)? + || value.is_instance(®istry::class(py, RecordId::TimestampValue)?)?, ) }; if !temporal(&start)? || !temporal(&end)? { @@ -110,7 +111,7 @@ fn count_batches( if cursor_type != INTEGER_CURSOR { return Ok(None); } - let integer: Bound<'_, PyAny> = imported(py, CURSOR_MODELS_MODULE, "IntegerValue")?; + let integer: Bound<'_, PyAny> = registry::class(py, RecordId::IntegerValue)?; if !start.is_instance(&integer)? || !end.is_instance(&integer)? { return Ok(None); } @@ -152,7 +153,7 @@ pub(crate) fn cursor_details<'py>( let bounds: Bound<'py, PyAny> = entry.getattr("cursor_bounds")?; (!bounds.is_none()).then_some(bounds) }; - let sentinel: Bound<'py, PyAny> = imported(py, BOUND_TYPES_MODULE, "BoundSentinel")?; + let sentinel: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BoundSentinel)?; if let Some(bounds) = &resolved_bounds && (bounds.getattr("start")?.is_instance(&sentinel)? || bounds.getattr("end")?.is_instance(&sentinel)?) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs index f7eb47e474..0d964e8bab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs @@ -23,10 +23,9 @@ use crate::bindings::_helpers::cursor::scalars::{ render_value, to_exclusive, to_inclusive, try_parse, }; use crate::bindings::_helpers::plan_output::values::{optional_text, text}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const PLANNER_TYPES: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const CURSOR_MODELS: &str = "sqlbuild._native"; const DATETIME_MODULE: &str = "datetime"; fn label_facts(entry: &Bound<'_, PyAny>) -> PyResult { @@ -70,7 +69,7 @@ fn cursor_typing<'py>( } else { PyString::new(py, DEFAULT_CURSOR_GRAIN).into_any() }; - let grain: Bound<'py, PyAny> = imported(py, PLANNER_TYPES, "CursorGrain")?.call1((grain,))?; + let grain: Bound<'py, PyAny> = registry::class(py, RecordId::CursorGrain)?.call1((grain,))?; Ok((effective, grain)) } @@ -110,7 +109,7 @@ fn resolve_bounded_cursor_override<'py>( return Ok(None); }; call_with( - &imported(py, PLANNER_MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[("start", start), ("end", to_exclusive(&end, &grain)?)], ) .map(Some) @@ -216,7 +215,7 @@ pub(crate) fn resolve_effective_microbatch_batch_size( effective_grain: &str, ) -> Result { effective_microbatch_batch_size(batch_size, effective_grain).map_err(|error| { - imported(py, PLANNER_TYPES, "CursorGrain") + registry::class(py, RecordId::CursorGrain) .and_then(|grain| grain.call1((error.0,))) .err() .unwrap_or_else(|| PyValueError::new_err("unknown cursor grain")) @@ -226,7 +225,7 @@ pub(crate) fn resolve_effective_microbatch_batch_size( fn as_datetime<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); let inner: Bound<'py, PyAny> = value.getattr("value")?; - if value.is_instance(&imported(py, CURSOR_MODELS, "TimestampValue")?)? { + if value.is_instance(®istry::class(py, RecordId::TimestampValue)?)? { return Ok(inner); } let datetime: Bound<'py, PyAny> = imported(py, DATETIME_MODULE, "datetime")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs index 30084fa8d7..930041c4ef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs @@ -12,7 +12,7 @@ use sqlbuild_core::cli_text::models::{ }; use sqlbuild_core::plan_display::main::format_query_diff::format_query_diff; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; use crate::bindings::_helpers::plan_output::cursor_details::{ CursorDetails, DEFERRED, RESOLVED, cursor_details, runtime_inputs_clause, @@ -27,8 +27,9 @@ use crate::bindings::_helpers::plan_output::python_identity::{ definition_source_text, dependency_source_text, }; use crate::bindings::_helpers::plan_output::values::{items, optional_text, text, texts}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DISPLAY_MODELS_MODULE: &str = "sqlbuild._native"; const SKIP_ACTION: &str = "skip"; const QUERY_CHANGED: &str = "query_changed"; const CONFIG_CHANGED: &str = "config_changed"; @@ -404,7 +405,7 @@ fn report(arguments: &KeywordArguments<'_>) -> PyResult { let display_options: Bound<'_, PyAny> = { let given: Bound<'_, PyAny> = arguments.value("display_options")?; if given.is_none() { - imported(py, DISPLAY_MODELS_MODULE, "DisplayOptions")?.call0()? + registry::class(py, RecordId::DisplayOptions)?.call0()? } else { given } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs index baad948897..052731e1ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs @@ -5,6 +5,8 @@ use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const INCREMENTAL_MATERIALIZATION: &str = "incremental"; @@ -20,7 +22,7 @@ fn resolve_model_actions<'py>( let py: Python<'py> = scope.py(); let model_kind: Bound<'py, PyAny> = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.getattr("MODEL")?; - let action_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "ResolvedModelAction")?; + let action_class: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedModelAction)?; let selected: Bound<'py, PyAny> = scope.getattr("selected_keys")?; let models_by_name: Bound<'py, PyAny> = scope.getattr("models_by_name")?; let model_changes: Bound<'py, PyAny> = changes.getattr("models")?; @@ -42,7 +44,7 @@ fn resolve_model_actions<'py>( )?; } call_with( - &imported(py, "sqlbuild._native", "PlannerResolvedActions")?, + ®istry::class(py, RecordId::PlannerResolvedActions)?, &[("models", resolved.into_any())], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs index 7fd07624a1..e7b772f5f6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs @@ -24,6 +24,9 @@ use sqlbuild_analysis::planner_changes::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_changes::renames::origin_names; use crate::bindings::_helpers::planner_changes::results::{ backfill_result, change_kind, planner_enum, replay_policy_error, schema_findings, @@ -35,13 +38,10 @@ use crate::bindings::_helpers::planner_identity::metadata::{ compiled_function_fingerprint_sql, function_local_hashes, model_declared_columns_hash, model_version_identity_metadata_json, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; -const DYNAMIC_FAMILIES_MODULE: &str = "sqlbuild._native"; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; -const LOG_SQL_MODULE: &str = "sqlbuild.diagnostics.main.log_sql"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const CHANGES_LOGGER: &str = "sqlbuild.planner.changes"; const REPLAY_CONFIG_KEY: &str = "replay_on_change"; @@ -87,7 +87,7 @@ fn detect_changes<'py>( ), )?; let functions: Bound<'py, PyDict> = PyDict::new(py); - let result_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "FunctionChangeResult")?; + let result_class: Bound<'py, PyAny> = registry::class(py, RecordId::FunctionChangeResult)?; let selected: Bound<'py, PyAny> = scope.getattr("selected_keys")?; let recorded: Bound<'py, PyAny> = snapshot.getattr("fingerprints")?.getattr("functions")?; let query_change_tracking: bool = project @@ -128,12 +128,12 @@ fn detect_changes<'py>( "no_change" } }; - arguments.push(("reason", planner_enum(py, "PlanReason", reason)?)); + arguments.push(("reason", planner_enum(py, RecordId::PlanReason, reason)?)); } functions.set_item(name, call_with(&result_class, &arguments)?)?; } call_with( - &imported(py, "sqlbuild._native", "PlannerChangeResults")?, + ®istry::class(py, RecordId::PlannerChangeResults)?, &[ ("models", models.into_any()), ("functions", functions.into_any()), @@ -241,9 +241,8 @@ fn scope_model_changes<'py>( )? .into_any(); let hook_version_hashes: Bound<'py, PyDict> = PyDict::new(py); - let identities: Bound<'py, PyAny> = - imported(py, HOOK_IDENTITIES_MODULE, "build_hook_identities")? - .call1((project.getattr("hook_functions")?,))?; + let identities: Bound<'py, PyAny> = host::entry(py, UserCodeHost::BuildHookIdentities)? + .call1((project.getattr("hook_functions")?,))?; for item in identities.call_method0("items")?.try_iter()? { let item: Bound<'py, PyAny> = item?; hook_version_hashes.set_item( @@ -429,7 +428,7 @@ pub(crate) fn model_change<'py>( fn replay_policy(model: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = model.py(); let raw: Option = call_with( - &imported(py, NATIVE_MODULE, "get_config_str")?, + ®istry::native(py, NativeAttr::GetConfigStr)?, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", PyString::new(py, REPLAY_CONFIG_KEY).into_any()), @@ -467,7 +466,7 @@ fn log_query_check( imported(py, "logging", "getLogger")?.call1((CHANGES_LOGGER,))?; let text = |value: &str| PyString::new(py, value).into_any(); call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, + &host::entry(py, ExecutorHost::LogDebugEvent)?, &[ ("logger", logger.clone()), ( @@ -492,7 +491,7 @@ fn log_query_check( ), ], )?; - let log_sql: Bound<'_, PyAny> = imported(py, LOG_SQL_MODULE, "log_sql")?; + let log_sql: Bound<'_, PyAny> = host::entry(py, ExecutorHost::LogSql)?; call_with( &log_sql, &[ @@ -597,7 +596,7 @@ fn schema_findings_of<'py>( let mut types_equal = |left: &str, right: &str| -> PyResult { native_types_equal(dialect, left, right) }; let matching_function: Bound<'py, PyAny> = - imported(py, DYNAMIC_FAMILIES_MODULE, "matching_dynamic_families")?; + registry::native(py, NativeAttr::MatchingDynamicFamilies)?; let mut single_family_type = |name: &str| -> PyResult> { let Some(families) = &dynamic else { return Ok(None); @@ -733,7 +732,7 @@ impl<'py> ChangeResult<'py> { } fn finish(self, backfill: Backfill, kind: &str) -> PyResult> { - let kind: Bound<'py, PyAny> = native_enum(self.py, "ChangeKind", kind)?; + let kind: Bound<'py, PyAny> = native_enum(self.py, RecordId::ChangeKind, kind)?; self.finish_kind(backfill, kind) } @@ -749,7 +748,7 @@ impl<'py> ChangeResult<'py> { .map(|flag| flag.to_owned().into_any()) }; call_with( - &imported(py, "sqlbuild._native", "ChangeDetectionResult")?, + ®istry::class(py, RecordId::ChangeDetectionResult)?, &[ ("model_name", self.model_name), ("change_kind", kind), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs index b6ec6ecca0..dc1bbb01b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs @@ -15,9 +15,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::planner_identity::hashing::model_query_hash; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const FINAL_NEWLINE: &str = "\n"; @@ -46,7 +46,7 @@ pub(crate) fn origin_names( .filter(|name| !name.is_empty()) .unwrap_or_else(|| GENERIC_DIALECT.to_owned()); let mut spans = |names: &BTreeSet| ref_name_spans(&query_sql, &dialect_name, names); - let compute_hash: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "compute_query_hash")?; + let compute_hash: Bound<'_, PyAny> = registry::native(py, NativeAttr::ComputeQueryHash)?; let mut query_hash = |text: &str| -> PyResult { call_with( &compute_hash, @@ -186,7 +186,7 @@ fn plan_reference_renames<'py>( let target_name: Bound<'py, PyAny> = runtime .getattr("project")? .getattr("effective_target_name")?; - let rename_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "ReferenceRename")?; + let rename_class: Bound<'py, PyAny> = registry::class(py, RecordId::ReferenceRename)?; let mut renames: Vec> = Vec::new(); for item in planned.call_method0("items")?.try_iter()? { let item: Bound<'py, PyAny> = item?; @@ -236,7 +236,7 @@ fn changed_referencing_query( } let recorded_models: Bound<'_, PyAny> = snapshot.getattr("fingerprints")?.getattr("models")?; let ref_kind: Bound<'_, PyAny> = - imported(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?.getattr("REF")?; + registry::class(py, RecordId::SqlReferenceKind)?.getattr("REF")?; let dialect: Option = snapshot.getattr("column_dialect")?.extract()?; for model in selected { let recorded: Bound<'_, PyAny> = diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs index 9151fe572d..bd2d0137ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs @@ -9,15 +9,13 @@ use sqlbuild_analysis::planner_changes::models::{ Backfill, BackfillAction, ChangeKind, SchemaChangeKind, SchemaColumnSource, SchemaFindingRow, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -pub(crate) const MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild._native"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; /// `PlannerInputError(message)` for an unknown replay policy. pub(crate) fn replay_policy_error(py: Python<'_>, error: UnknownReplayPolicy) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "PlannerInputError") + match registry::error(py, ErrorId::PlannerInput) .and_then(|class| class.call1((error.message(),))) { Ok(value) => PyErr::from_value(value), @@ -28,19 +26,19 @@ pub(crate) fn replay_policy_error(py: Python<'_>, error: UnknownReplayPolicy) -> /// `TYPES_MODULE.(value)`. pub(crate) fn planner_enum<'py>( py: Python<'py>, - name: &str, + name: RecordId, value: &str, ) -> PyResult> { - imported(py, TYPES_MODULE, name)?.call1((value,)) + registry::class(py, name)?.call1((value,)) } /// A member of a natively declared planner enumeration, from its value. pub(crate) fn native_enum<'py>( py: Python<'py>, - name: &str, + name: RecordId, value: &str, ) -> PyResult> { - imported(py, "sqlbuild._native", name)?.call1((value,)) + registry::class(py, name)?.call1((value,)) } /// `BackfillResult(action=..., duration=...)`. @@ -54,9 +52,12 @@ pub(crate) fn backfill_result<'py>( BackfillAction::ForwardOnly => "forward", }; call_with( - &imported(py, "sqlbuild._native", "BackfillResult")?, + ®istry::class(py, RecordId::BackfillResult)?, &[ - ("action", planner_enum(py, "BackfillAction", action)?), + ( + "action", + planner_enum(py, RecordId::BackfillAction, action)?, + ), ( "duration", backfill.duration.clone().into_pyobject(py)?.into_any(), @@ -74,7 +75,7 @@ pub(crate) fn change_kind<'py>(py: Python<'py>, kind: ChangeKind) -> PyResult "config_changed", ChangeKind::NoChange => "no_change", }; - native_enum(py, "ChangeKind", value) + native_enum(py, RecordId::ChangeKind, value) } /// A tuple of `SchemaFinding` values. @@ -82,7 +83,7 @@ pub(crate) fn schema_findings<'py>( py: Python<'py>, findings: &[SchemaFindingRow], ) -> PyResult> { - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "SchemaFinding")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SchemaFinding)?; let mut items: Vec> = Vec::with_capacity(findings.len()); for finding in findings { let kind: &str = match finding.kind { @@ -97,12 +98,15 @@ pub(crate) fn schema_findings<'py>( items.push(call_with( &class, &[ - ("kind", native_enum(py, "SchemaChangeKind", kind)?), + ("kind", native_enum(py, RecordId::SchemaChangeKind, kind)?), ( "column_name", finding.column_name.clone().into_pyobject(py)?.into_any(), ), - ("source", native_enum(py, "SchemaColumnSource", source)?), + ( + "source", + native_enum(py, RecordId::SchemaColumnSource, source)?, + ), ( "expected_type", finding.expected_type.clone().into_pyobject(py)?.into_any(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs index c7327b2447..d8f8bdae34 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs @@ -1,9 +1,11 @@ //! Clone planning: destination and origin entries, and deferred-clone graph traversal. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::values::{ - COMPILE_MODELS, native, planner_model, planner_type, string, + native, planner_model, planner_type, string, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -45,12 +47,11 @@ fn clone_plan_output<'py>( ) -> PyResult> { let py = project.py(); let upstream = - imported(py, "sqlbuild._native", "build_execution_upstream_deps")?.call1((project,))?; - let downstream = - imported(py, "sqlbuild._native", "build_downstream_deps")?.call1((&upstream,))?; + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((project,))?; + let downstream = registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; let selected = native( py, - "resolve_graph_selection", + NativeAttr::ResolveGraphSelection, &[ ("graph", project.getattr("lineage_graph")?), ("select", select.clone()), @@ -59,13 +60,13 @@ fn clone_plan_output<'py>( )?; planner_model( py, - "PlanOutput", + RecordId::PlanOutput, &[ ( "execution_order", native( py, - "topologically_order_keys", + NativeAttr::TopologicallyOrderKeys, &[("upstream", upstream.clone())], )?, ), @@ -93,17 +94,17 @@ fn clone_resolution<'py>( let py = project.py(); let model_locations = native( py, - "build_model_locations", + NativeAttr::BuildModelLocations, &[("models", project.getattr("models")?)], )?; let seed_locations = native( py, - "build_seed_locations", + NativeAttr::BuildSeedLocations, &[("seeds", project.getattr("seeds")?)], )?; let function_locations = native( py, - "build_function_locations", + NativeAttr::BuildFunctionLocations, &[("functions", project.getattr("functions")?)], )?; let source_map = PyDict::new(py); @@ -113,7 +114,7 @@ fn clone_resolution<'py>( } let source_warehouse_columns = native( py, - "gather_source_columns", + NativeAttr::GatherSourceColumns, &[ ("project", source_project.clone()), ("adapter", adapter.clone()), @@ -142,14 +143,17 @@ fn model_entry<'py>( }; planner_model( py, - "ModelPlanEntry", + RecordId::ModelPlanEntry, &[ ("key", model.getattr("key")?), ("name", model.getattr("name")?), ("relative_path", model.getattr("relative_path")?), ("materialization_type", materialization_type), - ("action", planner_type(py, "PlanAction", action)?), - ("reason", planner_type(py, "PlanReason", "NO_CHANGE")?), + ("action", planner_type(py, RecordId::PlanAction, action)?), + ( + "reason", + planner_type(py, RecordId::PlanReason, "NO_CHANGE")?, + ), ("destination", model.getattr("destination")?), ("fingerprint_query_sql", string(py, "")), ("resolved_sql", resolved_sql), @@ -172,8 +176,11 @@ fn clone_model_entries<'py>( if !selected.contains(model.getattr("key")?)? || disabled_model(&model)? { continue; } - let materialization_type = - native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; let resolved_sql = if materialization_type.eq(VIEW_MATERIALIZATION)? { let functions = PyDict::new(py); for function in project.getattr("functions")?.try_iter()? { @@ -182,7 +189,7 @@ fn clone_model_entries<'py>( } let context = native( py, - "ModelPlanContext", + RecordId::ModelPlanContext, &[ ("model_locations", resolution.model_locations.clone()), ("models_by_name", PyDict::new(py).into_any()), @@ -202,25 +209,28 @@ fn clone_model_entries<'py>( )?; native( py, - "resolve_model_sql", + NativeAttr::ResolveModelSql, &[ ("adapter", adapter.clone()), ("model", model.clone()), - ("snapshot", native(py, "WarehouseSnapshot", &[])?), + ("snapshot", native(py, RecordId::WarehouseSnapshot, &[])?), ("context", context), ( "backfill", native( py, - "BackfillResult", + RecordId::BackfillResult, &[( "action", - planner_type(py, "BackfillAction", "FORWARD_ONLY")?, + planner_type(py, RecordId::BackfillAction, "FORWARD_ONLY")?, )], )?, ), ("full_refresh", PyBool::new(py, false).to_owned().into_any()), - ("cursor_overrides", native(py, "CursorOverridePair", &[])?), + ( + "cursor_overrides", + native(py, RecordId::CursorOverridePair, &[])?, + ), ], )? } else { @@ -264,7 +274,7 @@ fn clone_source_entries<'py>( let qualified = adapter .getattr("render_qualified_name")? .call((), Some(&kwargs))?; - let destination = imported(py, COMPILE_MODELS, "CompiledRelationLocation")?.call( + let destination = registry::class(py, RecordId::CompiledRelationLocation)?.call( (), Some(&{ let location = PyDict::new(py); @@ -279,7 +289,7 @@ fn clone_source_entries<'py>( source.getattr("key")?, planner_model( py, - "CloneSourcePlanEntry", + RecordId::CloneSourcePlanEntry, &[ ("key", source.getattr("key")?), ("name", source.getattr("name")?), @@ -307,7 +317,7 @@ fn clone_function_entries<'py>( } let body_sql = native( py, - "resolve_function_sql", + NativeAttr::ResolveFunctionSql, &[ ("adapter", adapter.clone()), ("function", function.clone()), @@ -328,7 +338,7 @@ fn clone_function_entries<'py>( let field = |name: &str| function.getattr(name); let entry = planner_model( py, - "FunctionPlanEntry", + RecordId::FunctionPlanEntry, &[ ("key", field("key")?), ("name", field("name")?), @@ -341,7 +351,7 @@ fn clone_function_entries<'py>( "fingerprint_query_sql", native( py, - "build_compiled_function_fingerprint_sql", + NativeAttr::BuildCompiledFunctionFingerprintSql, &[("function", function.clone())], )?, ), @@ -363,7 +373,7 @@ fn seed_entry<'py>(seed: &Bound<'py, PyAny>) -> PyResult> { let py = seed.py(); planner_model( py, - "SeedPlanEntry", + RecordId::SeedPlanEntry, &[ ("key", seed.getattr("key")?), ("name", seed.getattr("name")?), @@ -408,8 +418,11 @@ fn origin_model_entries<'py>( if !selected_names.contains(model.getattr("name")?)? || disabled_model(&model)? { continue; } - let materialization_type = - native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; entries.push(model_entry(&model, materialization_type, string(py, ""))?); } tuple_of(py, entries) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs index c3402cd2e6..d8d83ae37b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs @@ -8,6 +8,7 @@ use crate::bindings::_helpers::planner_entries::relations::planner_relations_con use crate::bindings::_helpers::planner_entries::values::{ boolean, native, none, planner_model, planner_type, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyList, PyListMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -67,7 +68,7 @@ fn base_plan_output<'py>( project.getattr("enforce_explicit_references")?, ), ]); - planner_model(py, "PlanOutput", &fields) + planner_model(py, RecordId::PlanOutput, &fields) } /// Resolve command selection through the canonical build selector phase. @@ -81,7 +82,7 @@ fn resolve_static_command_scope<'py>( let py = project.py(); native( py, - "resolve_planner_scopes", + NativeAttr::ResolvePlannerScopes, &[ ("project", project.clone()), ("selection", selection.clone()), @@ -89,7 +90,7 @@ fn resolve_static_command_scope<'py>( "policies", planner_model( py, - "PlannerPolicies", + RecordId::PlannerPolicies, &[("auto_load_sources", boolean(py, auto_load_sources)?)], )?, ), @@ -155,13 +156,17 @@ fn build_audit_command_plan<'py>( let model = model?; materializations.set_item( model.getattr("name")?, - native(py, "get_materialization_type", &[("model", model.clone())])? - .getattr("value")?, + native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )? + .getattr("value")?, )?; } let audits = native( py, - "build_selected_audit_entries", + NativeAttr::BuildSelectedAuditEntries, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -238,7 +243,7 @@ fn build_test_command_plan<'py>( } let fingerprint_sql = native( py, - "build_compiled_function_fingerprint_sql", + NativeAttr::BuildCompiledFunctionFingerprintSql, &[("function", function.clone())], )?; functions.push(function_entry( @@ -248,7 +253,7 @@ fn build_test_command_plan<'py>( ( fingerprint_sql, none(py), - planner_type(py, "PlanReason", "NO_CHANGE")?, + planner_type(py, RecordId::PlanReason, "NO_CHANGE")?, ), )?); } @@ -300,7 +305,7 @@ fn build_seed_command_plan<'py>( if !selected_keys.contains(seed.getattr("key")?)? { continue; } - let identity = native(py, "build_seed_identity", &[("seed", seed.clone())])?; + let identity = native(py, NativeAttr::BuildSeedIdentity, &[("seed", seed.clone())])?; let (version_hash, metadata_json) = (identity.get_item(0)?, identity.get_item(1)?); let previous = existing.call_method1("get", (seed.getattr("name")?,))?; let reason = if previous.is_none() { @@ -312,7 +317,7 @@ fn build_seed_command_plan<'py>( }; entries.push(planner_model( py, - "SeedPlanEntry", + RecordId::SeedPlanEntry, &[ ("key", seed.getattr("key")?), ("name", seed.getattr("name")?), @@ -323,7 +328,11 @@ fn build_seed_command_plan<'py>( ), ( "columns", - native(py, "extract_seed_columns", &[("seed", seed.clone())])?, + native( + py, + NativeAttr::ExtractSeedColumns, + &[("seed", seed.clone())], + )?, ), ( "csv_settings", @@ -332,7 +341,7 @@ fn build_seed_command_plan<'py>( ("fingerprint_definition", metadata_json.clone()), ("fingerprint_version_hash", version_hash), ("fingerprint_metadata_json", metadata_json), - ("reason", planner_type(py, "PlanReason", reason)?), + ("reason", planner_type(py, RecordId::PlanReason, reason)?), ], )?); } @@ -414,7 +423,7 @@ fn read_selected_seed_fingerprints<'py>( let node_names = PyTuple::new(py, names.iter())?; let latest = native( py, - "read_latest_fingerprints", + NativeAttr::ReadLatestFingerprints, &[ ("connection", connection.clone()), ("execute", adapter.getattr("execute")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs index c938ed6c98..889c8ea83b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs @@ -1,9 +1,10 @@ //! Display-only plans for external plan surfaces when warehouse planning cannot resolve. -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::planner_entries::values::{ - NATIVE_MODULE, boolean, native, none, planner_model, planner_type, string, + boolean, native, none, planner_model, planner_type, string, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyFrozenSet, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -34,7 +35,7 @@ fn display_action( fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(model.py(), key)), @@ -47,10 +48,14 @@ fn display_entry<'py>( full_refresh: bool, ) -> PyResult> { let py = model.py(); - let materialization_type = native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; let effective = native( py, - "resolve_model_full_refresh", + NativeAttr::ResolveModelFullRefresh, &[ ("model", model.clone()), ("cli_full_refresh", boolean(py, full_refresh)?), @@ -65,7 +70,7 @@ fn display_entry<'py>( }; planner_model( py, - "ModelPlanEntry", + RecordId::ModelPlanEntry, &[ ("key", model.getattr("key")?), ("name", model.getattr("name")?), @@ -74,7 +79,7 @@ fn display_entry<'py>( "action", planner_type( py, - "PlanAction", + RecordId::PlanAction, display_action(&materialization_type, effective)?, )?, ), @@ -83,7 +88,7 @@ fn display_entry<'py>( "reason", planner_type( py, - "PlanReason", + RecordId::PlanReason, if effective { "FULL_REFRESH" } else { @@ -106,12 +111,12 @@ fn display_entry<'py>( "backfill", native( py, - "BackfillResult", + RecordId::BackfillResult, &[( "action", planner_type( py, - "BackfillAction", + RecordId::BackfillAction, if effective { "FULL" } else { "FORWARD_ONLY" }, )?, )], @@ -149,11 +154,11 @@ fn build_display_only_sqlbuild_plan<'py>( .map(|entry| entry.getattr("key")) .collect::>()?; let upstream = - imported(py, NATIVE_MODULE, "build_execution_upstream_deps")?.call1((project,))?; - let downstream = imported(py, NATIVE_MODULE, "build_downstream_deps")?.call1((&upstream,))?; + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((project,))?; + let downstream = registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; planner_model( py, - "PlanOutput", + RecordId::PlanOutput, &[ ("execution_order", PyTuple::new(py, &keys)?.into_any()), ("model_entries", PyTuple::new(py, entries)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs index ee974e5a4e..6a8888bc25 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs @@ -3,19 +3,20 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_threads::exit_signal::signal_interpreter_exit; use crate::bindings::_helpers::planner_entries::values::{ COMPILE_TYPES, boolean, native, replaced, string, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use std::time::Instant; -const INSPECTION_CATALOG: (&str, &str) = ( - "sqlbuild.adapter.relations.main.open_inspection_catalog", - "open_inspection_catalog", -); +const INSPECTION_CATALOG: AdapterHost = AdapterHost::OpenInspectionCatalog; const TEST_PLANNING_THREAD: &str = "sqlbuild-test-planning"; /// How long interpreter exit waits for each SQL test planning thread still running native code. const PLANNING_EXIT_JOIN_SECONDS: f64 = 5.0; @@ -62,7 +63,7 @@ fn sql_test_planning<'py>( "set_result", (native( py, - "PlannedSqlTests", + RecordId::PlannedSqlTests, &[ ("selected_keys", selected_keys.clone()), ("sql_test_selection", sql_test_selection.clone()), @@ -82,7 +83,7 @@ fn sql_test_planning<'py>( &[ ( "target", - imported(py, "sqlbuild._native", "plan_selected_sql_tests_into")?, + registry::native(py, NativeAttr::PlanSelectedSqlTestsInto)?, ), ("kwargs", kwargs.into_any()), ("name", string(py, TEST_PLANNING_THREAD)), @@ -101,7 +102,7 @@ fn scopes_and_cursor_overrides<'py>( let py = project.py(); let scopes = native( py, - "resolve_planner_scopes", + NativeAttr::ResolvePlannerScopes, &[ ("project", project.clone()), ("selection", args.value("selection")?), @@ -122,7 +123,7 @@ fn scopes_and_cursor_overrides<'py>( let overrides = args.value("overrides")?; let typed = native( py, - "type_cursor_overrides", + NativeAttr::TypeCursorOverrides, &[ ("cursor_overrides", overrides.getattr("cursor_overrides")?), ("selected_models", PyTuple::new(py, models)?.into_any()), @@ -145,7 +146,7 @@ fn change_results<'py>( let versions = identities.getattr("version_identities")?; let changes = native( py, - "detect_changes", + NativeAttr::DetectChanges, &[ ("project", project.clone()), ("scope", scopes.getattr("inspection_scope")?), @@ -164,7 +165,7 @@ fn change_results<'py>( if !policies.getattr("selection_diagnostics")?.is_truthy()? { let empty = native( py, - "PlannerChangeResults", + RecordId::PlannerChangeResults, &[ ("models", PyDict::new(py).into_any()), ("functions", PyDict::new(py).into_any()), @@ -174,7 +175,7 @@ fn change_results<'py>( } let stale = native( py, - "detect_stale_warning_changes", + NativeAttr::DetectStaleWarningChanges, &[ ("project", project.clone()), ("scopes", scopes.clone()), @@ -199,7 +200,7 @@ fn planned_output<'py>( let policies = args.value("policies")?; let warehouse = native( py, - "gather_planner_warehouse_state", + NativeAttr::GatherPlannerWarehouseState, &[ ("runtime", runtime.clone()), ("scopes", scopes.clone()), @@ -210,7 +211,7 @@ fn planned_output<'py>( let started = Instant::now(); let identities = native( py, - "build_planner_identity_context", + NativeAttr::BuildPlannerIdentityContext, &[ ("project", project.clone()), ("scopes", scopes.clone()), @@ -222,7 +223,7 @@ fn planned_output<'py>( )?; native( py, - "check_selected_scope_buildability", + NativeAttr::CheckSelectedScopeBuildability, &[ ("runtime", runtime.clone()), ("scopes", scopes.clone()), @@ -240,7 +241,7 @@ fn planned_output<'py>( let inspection_scope = scopes.getattr("inspection_scope")?; let resolved_actions = native( py, - "resolve_model_actions", + NativeAttr::ResolveModelActions, &[ ("scope", inspection_scope.clone()), ("changes", changes.clone()), @@ -248,7 +249,7 @@ fn planned_output<'py>( )?; let source_freshness = native( py, - "build_planner_source_freshness_result", + NativeAttr::BuildPlannerSourceFreshnessResult, &[ ("project", project.clone()), ("adapter", args.value("adapter")?), @@ -263,7 +264,7 @@ fn planned_output<'py>( )?; let pruning = native( py, - "prune_planner_execution_scope", + NativeAttr::PrunePlannerExecutionScope, &[ ("scopes", scopes.clone()), ("resolved_actions", resolved_actions), @@ -271,7 +272,7 @@ fn planned_output<'py>( )?; let reconciliation = native( py, - "reconcile_execution_changes", + NativeAttr::ReconcileExecutionChanges, &[ ("warehouse", warehouse.clone()), ("identities", identities.clone()), @@ -281,7 +282,7 @@ fn planned_output<'py>( )?; let entries = native( py, - "build_planner_entry_results", + NativeAttr::BuildPlannerEntryResults, &[ ("runtime", runtime.clone()), ("warehouse", warehouse.clone()), @@ -297,7 +298,7 @@ fn planned_output<'py>( let planned_sql_tests = test_planning.call_method0("result")?; let mut plan_output = native( py, - "assemble_base_plan_output", + NativeAttr::AssembleBasePlanOutput, &[ ("runtime", runtime.clone()), ("warehouse", warehouse.clone()), @@ -312,7 +313,7 @@ fn planned_output<'py>( )?; plan_output = native( py, - "with_storage_policies", + NativeAttr::WithStoragePolicies, &[ ("plan_output", plan_output), ("runtime", runtime.clone()), @@ -323,7 +324,7 @@ fn planned_output<'py>( )?; plan_output = native( py, - "with_plan_warnings", + NativeAttr::WithPlanWarnings, &[ ("runtime", runtime.clone()), ("scopes", scopes.clone()), @@ -338,7 +339,7 @@ fn planned_output<'py>( )?; plan_output = native( py, - "with_plan_metadata", + NativeAttr::WithPlanMetadata, &[ ("plan_output", plan_output), ("pruning", pruning), @@ -385,7 +386,7 @@ fn build_execution_plan<'py>( let connection = args.value("connection")?; let runtime = native( py, - "PlannerRuntime", + RecordId::PlannerRuntime, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -409,7 +410,7 @@ fn build_execution_plan<'py>( )?; let planned = (|| { let catalog = call_with( - &imported(py, INSPECTION_CATALOG.0, INSPECTION_CATALOG.1)?, + &host::entry(py, INSPECTION_CATALOG)?, &[("adapter", adapter), ("connection", connection)], )?; within(&catalog, |_| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs index 2b7025516e..65a5a8a122 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs @@ -2,14 +2,16 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::planner_entries::values::{ - COMPILE_EXCEPTIONS, COMPILE_TYPES, MICROBATCH_CONCURRENCY_MESSAGE, PLANNER_EXCEPTIONS, - PLANNER_TYPES, boolean, error_of, native, none, planner_type, replaced, string, + COMPILE_TYPES, MICROBATCH_CONCURRENCY_MESSAGE, boolean, error_of, native, none, planner_type, + replaced, string, }; use crate::bindings::_helpers::planner_resolution::cursor_ranges::{ CursorWindowInputs, config_str, cursor_safety_configs, empty_cursor_inputs, microbatch_range, model_cursor_overrides, plan_cursor_bounds, }; use crate::bindings::_helpers::planner_resolution::cursor_warnings::with_cursor_safety_warnings; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -41,8 +43,11 @@ fn plan_model_from_change<'py>( let backfill = if request.full_refresh { native( py, - "BackfillResult", - &[("action", planner_type(py, "BackfillAction", "FULL")?)], + RecordId::BackfillResult, + &[( + "action", + planner_type(py, RecordId::BackfillAction, "FULL")?, + )], )? } else { request.change_result.getattr("backfill")? @@ -50,7 +55,7 @@ fn plan_model_from_change<'py>( let cursor_column = config_str(model, "cursor")?; native( py, - "validate_source_cursor_input_columns", + NativeAttr::ValidateSourceCursorInputColumns, &[ ("model", model.clone()), ("cursor_column", cursor_column.clone()), @@ -65,7 +70,7 @@ fn plan_model_from_change<'py>( )?; let resolved = native( py, - "resolve_model_plan_action", + NativeAttr::ResolveModelPlanAction, &[ ("model", model.clone()), ("change_result", request.change_result.clone()), @@ -73,10 +78,14 @@ fn plan_model_from_change<'py>( ], )?; let (action, reason) = (resolved.get_item(0)?, resolved.get_item(1)?); - let materialization_type = native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; let replaces_relation = native( py, - "replaces_incremental_relation", + NativeAttr::ReplacesIncrementalRelation, &[ ("materialization_type", materialization_type.clone()), ("action", action.clone()), @@ -86,7 +95,7 @@ fn plan_model_from_change<'py>( let full_rebuild_cause = if replaces_relation { native( py, - "describe_full_rebuild_cause", + NativeAttr::DescribeFullRebuildCause, &[ ("change_result", request.change_result.clone()), ("full_refresh", full_refresh.clone()), @@ -98,7 +107,7 @@ fn plan_model_from_change<'py>( if !full_rebuild_cause.is_none() && request.protect_full_refresh && !request.full_refresh { native( py, - "check_full_rebuild_allowed", + NativeAttr::CheckFullRebuildAllowed, &[ ("model", model.clone()), ("change_result", request.change_result.clone()), @@ -108,7 +117,7 @@ fn plan_model_from_change<'py>( } let pair = native( py, - "CursorOverridePair", + RecordId::CursorOverridePair, &[ ("start_cursor_override", request.overrides.0.clone()), ("end_cursor_override", request.overrides.1.clone()), @@ -116,7 +125,7 @@ fn plan_model_from_change<'py>( )?; let resolved_sql = native( py, - "resolve_model_sql", + NativeAttr::ResolveModelSql, &[ ("adapter", request.adapter.clone()), ("model", model.clone()), @@ -135,7 +144,7 @@ fn plan_model_from_change<'py>( )?; let hooks = native( py, - "resolve_model_hook_entries", + NativeAttr::ResolveModelHookEntries, &[ ("model", model.clone()), ("adapter", request.adapter.clone()), @@ -149,7 +158,7 @@ fn plan_model_from_change<'py>( let (future_config, start_config) = cursor_safety_configs(model, context)?; native( py, - "validate_capped_watermark_inputs", + NativeAttr::ValidateCappedWatermarkInputs, &[ ("model", model.clone()), ("models_by_name", context.getattr("models_by_name")?), @@ -158,7 +167,7 @@ fn plan_model_from_change<'py>( )?; let cursor_input_relations = native( py, - "build_cursor_input_relations", + NativeAttr::BuildCursorInputRelations, &[ ("model", model.clone()), ("adapter", request.adapter.clone()), @@ -199,7 +208,7 @@ fn plan_model_from_change<'py>( let range = microbatch_range(model, &window)?; let assembled = native( py, - "assemble_model_plan_entry", + NativeAttr::AssembleModelPlanEntry, &[ ("model", model.clone()), ("adapter", request.adapter.clone()), @@ -260,7 +269,7 @@ fn check_binding_diagnostics<'py>( } let bound = native( py, - "source_binding_diagnostics", + NativeAttr::SourceBindingDiagnostics, &[ ("project", project.clone()), ("columns", relations.getattr("source_warehouse_columns")?), @@ -301,7 +310,7 @@ fn check_binding_diagnostics<'py>( } Err(error_of( py, - (PLANNER_EXCEPTIONS, "PlannerInputError"), + ErrorId::PlannerInput, &lines.join("\n"), &[("code", first.getattr("code")?)], )) @@ -322,8 +331,8 @@ fn check_microbatch_concurrency<'py>( entries: &[Bound<'py, PyAny>], ) -> PyResult>> { let py = project.py(); - let skip = planner_type(py, "PlanAction", "SKIP")?; - let create = planner_type(py, "PlanAction", "CREATE_TABLE")?; + let skip = planner_type(py, RecordId::PlanAction, "SKIP")?; + let create = planner_type(py, RecordId::PlanAction, "CREATE_TABLE")?; let mut configured: Vec> = Vec::new(); for entry in entries { if entry.getattr("batch_concurrency")?.gt(1)? && entry.getattr("action")?.ne(&skip)? { @@ -332,11 +341,11 @@ fn check_microbatch_concurrency<'py>( } let settings = project.getattr("settings")?; if !configured.is_empty() && !settings.getattr("microbatch_concurrency")?.is_truthy()? { - let guidance = native(py, "microbatch_guidance", &[])?; + let guidance = native(py, NativeAttr::MicrobatchGuidance, &[])?; let prefix: &str = MICROBATCH_CONCURRENCY_MESSAGE; return Err(error_of( py, - (COMPILE_EXCEPTIONS, "CompileInputError"), + ErrorId::CompileInput, &format!( "{prefix}{}; {}", names_of(&configured)?, @@ -352,7 +361,7 @@ fn check_microbatch_concurrency<'py>( { return Err(error_of( py, - (COMPILE_EXCEPTIONS, "CompileInputError"), + ErrorId::CompileInput, &format!( "adapter '{}' does not support concurrent microbatch delete/insert DML; set \ batch_concurrency = 1", @@ -373,12 +382,12 @@ fn check_microbatch_concurrency<'py>( let count = concurrent.len(); native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", none(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, ), ("code", string(py, MICROBATCH_COMPLEXITY_CODE)), ( @@ -417,7 +426,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult let full_refresh = args.value("full_refresh")?; let inputs = args.value("build_inputs")?; let inputs = if inputs.is_none() { - native(py, "PlanEntryBuildInputs", &[])? + native(py, RecordId::PlanEntryBuildInputs, &[])? } else { inputs }; @@ -429,7 +438,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult let end_override = inputs.getattr("end_cursor_override")?; let producers = native( py, - "runtime_cursor_producer_names", + NativeAttr::RuntimeCursorProducerNames, &[ ("scope", scope.clone()), ("source_map", relations.getattr("source_map")?), @@ -444,7 +453,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult let models_by_name = scope.getattr("models_by_name")?; let context = native( py, - "ModelPlanContext", + RecordId::ModelPlanContext, &[ ("model_locations", relations.getattr("model_locations")?), ("models_by_name", models_by_name.clone()), @@ -475,8 +484,8 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult ("invocation_time", inputs.getattr("invocation_time")?), ], )?; - let skip = planner_type(py, "PlanAction", "SKIP")?; - let microbatch = imported(py, PLANNER_TYPES, "IncrementalMode")?.getattr("MICROBATCH")?; + let skip = planner_type(py, RecordId::PlanAction, "SKIP")?; + let microbatch = registry::class(py, RecordId::IncrementalMode)?.getattr("MICROBATCH")?; let mut entries: Vec> = Vec::new(); let mut warnings: Vec> = Vec::new(); for key in scope.getattr("execution_order")?.try_iter()? { @@ -497,7 +506,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult )?; let effective_full_refresh = native( py, - "resolve_model_full_refresh", + NativeAttr::ResolveModelFullRefresh, &[ ("model", model.clone()), ("cli_full_refresh", full_refresh.clone()), @@ -525,7 +534,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult ("action", skip.clone()), ( "reason", - planner_type(py, "PlanReason", "SOURCE_FRESHNESS_ERROR")?, + planner_type(py, RecordId::PlanReason, "SOURCE_FRESHNESS_ERROR")?, ), ], )?; @@ -537,7 +546,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult ("action", skip.clone()), ( "reason", - planner_type(py, "PlanReason", "EXTERNAL_UPSTREAM_FAILED")?, + planner_type(py, RecordId::PlanReason, "EXTERNAL_UPSTREAM_FAILED")?, ), ], )?; @@ -553,7 +562,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult if entry.getattr("action")?.ne(&skip)? { let limited = native( py, - "apply_configured_microbatch_limits", + NativeAttr::ApplyConfiguredMicrobatchLimits, &[("entry", entry), ("inputs", inputs.clone())], )?; entry = limited.get_item(0)?; @@ -572,7 +581,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult } native( py, - "PlannerModelEntryResults", + RecordId::PlannerModelEntryResults, &[ ("entries", PyTuple::new(py, entries)?.into_any()), ("warnings", PyTuple::new(py, warnings)?.into_any()), @@ -637,7 +646,7 @@ fn plan_model<'py>( let snapshot = args.value("snapshot")?; let change_result = native( py, - "detect_model_changes", + NativeAttr::DetectModelChanges, &[ ("model", model.clone()), ("snapshot", snapshot.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs index e8715b7dda..a308ef4b52 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs @@ -1,11 +1,12 @@ //! `PlanOutput` assembly from planned model entries, seeds, sources, functions, audits and tests. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::model_entries::scope_overlaps; use crate::bindings::_helpers::planner_entries::values::{ - DISCOVERY_MODELS, PLANNER_EXCEPTIONS, error_of, native, none, planner_model, planner_type, - string, + error_of, native, none, planner_model, planner_type, string, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyList, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -66,23 +67,23 @@ fn seed_plan_reason<'py>( .getattr("existing_relations")? .contains(seed_name)? { - return planner_type(py, "PlanReason", "FIRST_RUN"); + return planner_type(py, RecordId::PlanReason, "FIRST_RUN"); } let fingerprint = snapshot .getattr("fingerprints")? .getattr("seeds")? .call_method1("get", (seed_name,))?; if fingerprint.is_none() { - return planner_type(py, "PlanReason", "FIRST_RUN"); + return planner_type(py, RecordId::PlanReason, "FIRST_RUN"); } if !expected_version_hash.is_none() && fingerprint .getattr("version_hash")? .ne(expected_version_hash)? { - return planner_type(py, "PlanReason", "CONFIG_CHANGED"); + return planner_type(py, RecordId::PlanReason, "CONFIG_CHANGED"); } - planner_type(py, "PlanReason", "NO_CHANGE") + planner_type(py, RecordId::PlanReason, "NO_CHANGE") } fn seed_entries<'py>( @@ -104,7 +105,7 @@ fn seed_entries<'py>( let name = seed.getattr("name")?; entries.push(planner_model( py, - "SeedPlanEntry", + RecordId::SeedPlanEntry, &[ ("key", seed.getattr("key")?), ("name", name.clone()), @@ -115,7 +116,11 @@ fn seed_entries<'py>( ), ( "columns", - native(py, "extract_seed_columns", &[("seed", seed.clone())])?, + native( + py, + NativeAttr::ExtractSeedColumns, + &[("seed", seed.clone())], + )?, ), ( "csv_settings", @@ -159,7 +164,7 @@ fn provider_usages<'py>( let usage = usage?; usages.push(native( py, - "PlanProviderUsage", + RecordId::PlanProviderUsage, &[ ("provider_name", usage.getattr("provider_name")?), ("consumer_kind", string(py, consumer_kind)), @@ -182,7 +187,7 @@ fn hook_provider_usages<'py>( ) -> PyResult>> { let py = entry.py(); let mut usages: Vec> = Vec::new(); - let python_hook = imported(py, DISCOVERY_MODELS, "PythonHookEntry")?; + let python_hook = registry::class(py, RecordId::PythonHookEntry)?; let mut hooks: Vec> = Vec::new(); for field in ["pre_hooks", "post_hooks"] { let hook_entry = entry.getattr(field)?; @@ -282,7 +287,7 @@ fn require_intermediate_loader_targets<'py>( }; let dependencies = native( py, - "upstream_loader_dependency_names", + NativeAttr::UpstreamLoaderDependencyNames, &[ ("loader_function", loader_function), ("loader_functions", loader_functions.clone()), @@ -306,7 +311,7 @@ fn require_intermediate_loader_targets<'py>( let source_name = source_entry.getattr("name")?.str()?; return Err(error_of( py, - (PLANNER_EXCEPTIONS, "PlannerInputError"), + ErrorId::PlannerInput, &format!( "Source '{source_name}' requires intermediate loader '{}', but its target \ relation '{}' does not exist; use +source:{source_name} to refresh upstream \ @@ -335,7 +340,7 @@ pub(super) fn function_entry<'py>( let py = function.py(); let body_sql = native( py, - "resolve_function_sql", + NativeAttr::ResolveFunctionSql, &[ ("adapter", adapter.clone()), ("function", function.clone()), @@ -359,7 +364,7 @@ pub(super) fn function_entry<'py>( let field = |name: &str| function.getattr(name); planner_model( py, - "FunctionPlanEntry", + RecordId::FunctionPlanEntry, &[ ("key", field("key")?), ("name", field("name")?), @@ -471,7 +476,7 @@ pub(super) fn selected_test_entries<'py>( let fixture_context = if analysis.is_truthy()? { native( py, - "build_relation_fixture_context", + NativeAttr::BuildRelationFixtureContext, &[("project", project.clone())], )? } else { @@ -479,7 +484,7 @@ pub(super) fn selected_test_entries<'py>( }; let results = native( py, - "plan_sql_tests", + NativeAttr::PlanSqlTests, &[ ("tests", tuple_of(py, selected)?), ("project", project.clone()), @@ -510,7 +515,7 @@ pub(super) fn selected_test_entries<'py>( if !diagnostics.is_empty() { return Err(error_of( py, - (PLANNER_EXCEPTIONS, "PlannerInputError"), + ErrorId::PlannerInput, &format!("Invalid SQL test fixtures:\n- {}", diagnostics.join("\n- ")), &[], )); @@ -529,7 +534,7 @@ fn planned_sql_tests<'py>( let selected = selected_test_entries((project, adapter), selected_keys, selection, &none(py))?; native( py, - "PlannedSqlTests", + RecordId::PlannedSqlTests, &[ ("selected_keys", selected_keys.clone()), ("sql_test_selection", selection.clone()), @@ -549,7 +554,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> let model_entry_results = args.value("model_entry_results")?; let extras = args.value("extras")?; let extras = if extras.is_none() { - native(py, "PlanOutputExtras", &[])? + native(py, RecordId::PlanOutputExtras, &[])? } else { extras }; @@ -559,7 +564,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> let seeds = seed_entries(&project, &snapshot, &selected_keys, &extras)?; let source_load_entries = native( py, - "build_source_load_entries", + NativeAttr::BuildSourceLoadEntries, &[ ("execution_order", execution_order.clone()), ("selected_keys", selected_keys.clone()), @@ -585,7 +590,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> } let audit_entries = native( py, - "build_selected_audit_entries", + NativeAttr::BuildSelectedAuditEntries, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -624,7 +629,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> } planner_model( py, - "PlanOutput", + RecordId::PlanOutput, &[ ("execution_order", tuple_of(py, scoped_order)?), ("model_entries", model_entries.clone()), @@ -730,7 +735,7 @@ fn plan_function<'py>( let reason = if args.passed("reason")? { args.value("reason")? } else { - planner_type(py, "PlanReason", "NO_CHANGE")? + planner_type(py, RecordId::PlanReason, "NO_CHANGE")? }; function_entry( &args.value("function")?, @@ -781,7 +786,7 @@ fn plan_selected_sql_tests_into<'py>( let future = args.value("future")?; match native( py, - "plan_selected_sql_tests", + NativeAttr::PlanSelectedSqlTests, &[ ("project", args.value("project")?), ("adapter", args.value("adapter")?), @@ -805,7 +810,7 @@ fn sql_test_model_chain_names_by_key<'py>( let py = project.py(); let chains = native( py, - "resolve_sql_test_model_chains", + NativeAttr::ResolveSqlTestModelChains, &[("project", project.clone()), ("tests", tests.clone())], )?; let by_key = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs index fd974efa8a..a81b416a48 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs @@ -2,6 +2,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::values::{native, none, planner_model}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -17,7 +18,7 @@ fn selected_audit_relation_keys<'py>( let audit = audit?; if native( py, - "audit_is_selected", + NativeAttr::AuditIsSelected, &[ ("audit", audit.clone()), ("selected_keys", selected_keys.clone()), @@ -60,7 +61,7 @@ fn source_warehouse_columns<'py>( )?; let gathered = native( py, - "gather_source_inspection", + NativeAttr::GatherSourceInspection, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -82,7 +83,7 @@ pub(crate) fn planner_relations_context<'py>( let scope = args.value("scope")?; let deferral = args.value("deferral")?; let deferral = if deferral.is_none() { - planner_model(py, "DeferralInputs", &[])? + planner_model(py, RecordId::DeferralInputs, &[])? } else { deferral }; @@ -91,24 +92,24 @@ pub(crate) fn planner_relations_context<'py>( let selected_keys = scope.getattr("selected_keys")?; let mut model_locations = native( py, - "build_model_locations", + NativeAttr::BuildModelLocations, &[("models", project.getattr("models")?)], )?; let mut seed_locations = native( py, - "build_seed_locations", + NativeAttr::BuildSeedLocations, &[("seeds", project.getattr("seeds")?)], )?; let function_locations = native( py, - "build_function_locations", + NativeAttr::BuildFunctionLocations, &[("functions", project.getattr("functions")?)], )?; let deferred_locations = deferral.getattr("deferred_locations")?; if !deferred_locations.is_none() { let applied = native( py, - "apply_deferred_locations", + NativeAttr::ApplyDeferredLocations, &[ ("model_locations", model_locations), ("seed_locations", seed_locations), @@ -128,7 +129,7 @@ pub(crate) fn planner_relations_context<'py>( }; let source_map = native( py, - "build_source_load_map", + NativeAttr::BuildSourceLoadMap, &[ ("project", project.clone()), ("selected_keys", load_keys), @@ -158,12 +159,12 @@ pub(crate) fn planner_relations_context<'py>( if let Some(names) = names { pairs.push(("read_source_names", names)); } - native(py, "build_source_read_map", &pairs) + native(py, NativeAttr::BuildSourceReadMap, &pairs) }; let source_read_map = read_map(&source_map, None)?; let python_source_map = native( py, - "with_declared_source_reads", + NativeAttr::WithDeclaredSourceReads, &[ ("project", project.clone()), ("source_map", source_map.clone()), @@ -185,7 +186,7 @@ pub(crate) fn planner_relations_context<'py>( )?; let semantic_columns = native( py, - "get_semantic_source_columns", + NativeAttr::GetSemanticSourceColumns, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -197,7 +198,7 @@ pub(crate) fn planner_relations_context<'py>( )?; planner_model( py, - "PlannerRelationsContext", + RecordId::PlannerRelationsContext, &[ ("model_locations", model_locations), ("seed_locations", seed_locations), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs index f35a07d9ae..0bdb855ac3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs @@ -1,47 +1,41 @@ //! Python boundary values shared by plan entry and plan output assembly. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::PyString; -pub(super) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(super) const PLANNER_MODELS: &str = "sqlbuild._native"; -pub(super) const PLANNER_TYPES: &str = "sqlbuild._native"; -pub(super) const PLANNER_EXCEPTIONS: &str = "sqlbuild._native"; pub(super) const MICROBATCH_CONCURRENCY_MESSAGE: &str = "batch_concurrency > 1 requires concurrent microbatches; selected model(s): "; -pub(super) const COMPILE_EXCEPTIONS: &str = "sqlbuild._native"; pub(super) const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -pub(super) const COMPILE_MODELS: &str = "sqlbuild._native"; -pub(super) const DISCOVERY_MODELS: &str = "sqlbuild._native"; /// `sqlbuild._native.name(**pairs)`. pub(super) fn native<'py>( py: Python<'py>, - name: &str, + name: impl Into, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, NATIVE_MODULE, name)?, pairs) + call_with(®istry::item(py, name)?, pairs) } /// The planner record `name(**pairs)`. pub(super) fn planner_model<'py>( py: Python<'py>, - name: &str, + name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&shape_class_at(py, PLANNER_MODELS, name)?, pairs) + call_with(®istry::class(py, name)?, pairs) } /// One member of a planner enumeration. pub(super) fn planner_type<'py>( py: Python<'py>, - class: &str, + class: RecordId, member: &str, ) -> PyResult> { - imported(py, PLANNER_TYPES, class)?.getattr(member) + registry::member(py, class, member) } /// A Python string. @@ -71,11 +65,11 @@ pub(super) fn replaced<'py>( /// `class(message, **fields)` from `module`, as a raised error. pub(super) fn error_of( py: Python<'_>, - (module, class): (&str, &str), + class: ErrorId, message: &str, fields: &[(&str, Bound<'_, PyAny>)], ) -> PyErr { - match imported(py, module, class) + match registry::error(py, class) .and_then(|class| class.call((message,), Some(&keywords(py, fields)?))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs index bc34edda00..2b47455ce3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs @@ -22,14 +22,13 @@ use sqlbuild_analysis::planner_identity::models::{AvailableUpstreams, GraphKey, use crate::bindings::_helpers::analysis::explicit_references::{ GateRow, HookRow, native_execution_gate_edges, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::algorithms::{closure, inverted}; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::planner_identity::hashing::graph_key; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const TABLE_FN_TEST_MODE: &str = "table_fn"; const INLINE_SQL_HOOK: &str = "inline SQL"; @@ -241,9 +240,7 @@ fn build_downstream_deps<'py>(upstream: &Bound<'py, PyAny>) -> PyResult, message: String) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "PlannerInputError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::PlannerInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -369,7 +366,7 @@ fn check_buildability<'py>( Some(name_set(&deferred_relations)?) }, }; - let entry_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "MissingUpstream")?; + let entry_class: Bound<'py, PyAny> = registry::class(py, RecordId::MissingUpstream)?; let mut entries: Vec> = Vec::new(); for missing in missing_upstreams(&selected, &deps, &available) { entries.push(call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs index 65d24bc6d4..ca508ec8b1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs @@ -13,11 +13,11 @@ use sqlbuild_analysis::sql_tokens::main::definition_fingerprint::definition_fing use sqlbuild_core::panics::main::is_compiler_panic::is_compiler_panic; use crate::bindings::_helpers::boundary::panics::compiler_error; -use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; - /// The query fingerprint of a model, or the planner input error naming the model. pub(crate) fn model_query_hash( py: Python<'_>, @@ -43,8 +43,8 @@ fn fingerprint_error(py: Python<'_>, subject: &str, message: String) -> PyErr { return compiler_error(message); } let text = |value: &str| PyString::new(py, value).into_any(); - let raised: PyResult> = imported(py, EXCEPTIONS_MODULE, "PlannerInputError") - .and_then(|class| { + let raised: PyResult> = + registry::error(py, ErrorId::PlannerInput).and_then(|class| { class.call( (format!( "{subject} cannot be fingerprinted for change detection: {message}" diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs index 63bf9d947f..4b795d067c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs @@ -17,12 +17,15 @@ use sqlbuild_analysis::planner_identity::models::{GraphKey, IdentityGraph, Ident use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_identity::hashing::{graph_key, model_query_hash}; use crate::bindings::_helpers::planner_identity::metadata::{ function_local_hashes, model_version_identity_metadata_json, seed_identity, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; const DATACLASSES_MODULE: &str = "dataclasses"; fn key_of(kind: &str, name: &str) -> GraphKey { @@ -49,7 +52,7 @@ fn hook_version_hashes<'py>(hook_functions: &Bound<'py, PyAny>) -> PyResult = hook_functions.py(); let hashes: Bound<'py, PyDict> = PyDict::new(py); let identities: Bound<'py, PyAny> = - imported(py, HOOK_IDENTITIES_MODULE, "build_hook_identities")?.call1((hook_functions,))?; + host::entry(py, UserCodeHost::BuildHookIdentities)?.call1((hook_functions,))?; for item in identities.call_method0("items")?.try_iter()? { let (name, identity): (Bound<'py, PyAny>, Bound<'py, PyAny>) = item?.extract()?; hashes.set_item(name, identity.getattr("version_hash")?)?; @@ -182,7 +185,7 @@ fn build_direct_model_version_identities<'py>( } } call_with( - &imported(py, "sqlbuild._native", "DirectModelVersionIdentities")?, + ®istry::class(py, RecordId::DirectModelVersionIdentities)?, &[ ("function_local_hashes", function_hashes.into_any()), ("seed_version_hashes", seed_hashes.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs index f01cb48fc3..5559b2c1e6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs @@ -16,11 +16,11 @@ use sqlbuild_analysis::planner_identity::models::{ GraphKey, StaleWarning, StalenessGraph, StalenessNames, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_changes::detect::{ModelPass, model_change}; use crate::bindings::_helpers::planner_identity::hashing::graph_key; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Change kinds that mean the model's own identity changed. const OWN_CHANGE_KINDS: [&str; 5] = [ @@ -143,8 +143,8 @@ fn plan_warnings<'py>( py: Python<'py>, warnings: Vec, ) -> PyResult> { - let warning_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "PlanWarning")?; - let severity: Bound<'py, PyAny> = native_enum(py, "WarningSeverity", "warning")?; + let warning_class: Bound<'py, PyAny> = registry::class(py, RecordId::PlanWarning)?; + let severity: Bound<'py, PyAny> = native_enum(py, RecordId::WarningSeverity, "warning")?; let mut rendered: Vec> = Vec::with_capacity(warnings.len()); for warning in warnings { let message: String = stale_upstream_warning_message( @@ -258,7 +258,7 @@ fn classify_selection_staleness_warnings<'py>( }, }; let warning_class: Bound<'py, PyAny> = - imported(py, "sqlbuild._native", "SelectionStalenessWarning")?; + registry::class(py, RecordId::SelectionStalenessWarning)?; let mut warnings: Vec> = Vec::new(); for warning in classify_selection_staleness(&staleness) { warnings.push(call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs index 27a15d694e..b209ee41a1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs @@ -5,6 +5,7 @@ use crate::bindings::_helpers::planner_migrations::column_objects::{ config, definition_hash, lower, native, }; use crate::bindings::_helpers::planner_migrations::models::ColumnCandidate; +use crate::bindings::_helpers::records::ids::NativeAttr; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; @@ -72,7 +73,7 @@ pub(super) fn candidates<'py>( let previous = if changed { native( py, - "parse_query_shape", + NativeAttr::ParseQueryShape, &[ ("query_sql", fingerprint.getattr("definition")?), ("dialect", dialect.clone()), @@ -83,7 +84,7 @@ pub(super) fn candidates<'py>( }; let current = native( py, - "parse_query_shape", + NativeAttr::ParseQueryShape, &[ ("query_sql", model.getattr("query_sql")?), ("dialect", dialect.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs index 2a358d1938..9f5685f3ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs @@ -1,19 +1,21 @@ //! Materialize column decisions from native proofs and live evidence. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_candidates::declared_names; use crate::bindings::_helpers::planner_migrations::column_objects::{ lower, model, native, replace, }; -use crate::bindings::_helpers::planner_migrations::constants::{AUTOMATIC, DONE, MIGRATION_TYPES}; +use crate::bindings::_helpers::planner_migrations::constants::{AUTOMATIC, DONE}; use crate::bindings::_helpers::planner_migrations::models::{CheckedColumns, ColumnCandidate}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::migration_projections::newest_column; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple}; use sqlbuild_analysis::planner_migrations::main::column_decision::column_decision; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - pub(super) fn decide<'py>( runtime: &Bound<'py, PyAny>, candidate: &ColumnCandidate<'py>, @@ -30,7 +32,7 @@ pub(super) fn decide<'py>( if !candidate.previous.is_none() && !candidate.current.is_none() { let detected = native( py, - "identical_renames", + NativeAttr::IdenticalRenames, &[ ("previous", candidate.previous.clone()), ("current", candidate.current.clone()), @@ -57,12 +59,8 @@ pub(super) fn decide<'py>( } } } - let relation = imported( - py, - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((candidate.model.getattr("destination")?,))?; + let relation = host::entry(py, ExecutorHost::MigrationRelationForLocation)? + .call1((candidate.model.getattr("destination")?,))?; let mut entries: Vec> = Vec::new(); for (origin_name, destination, discovery) in pairs { let origin_key = lower(&origin_name)?; @@ -91,14 +89,14 @@ pub(super) fn decide<'py>( outputs.contains(&origin_key), ); let decision = - imported(py, MIGRATION_TYPES, "ColumnMigrationDecision")?.call1((decision_name,))?; + registry::class(py, RecordId::ColumnMigrationDecision)?.call1((decision_name,))?; if discovery == AUTOMATIC && decision.getattr("blocks_build")?.is_truthy()? { continue; } let done = decision_name == DONE && !newest.is_none(); entries.push(model( py, - "ColumnMigrationPlanEntry", + RecordId::ColumnMigrationPlanEntry, &[ ("model_name", candidate.model.getattr("name")?), ("destination", candidate.model.getattr("destination")?), @@ -113,8 +111,7 @@ pub(super) fn decide<'py>( ("destination_column", destination), ( "discovery", - imported(py, NATIVE_RECORDS_MODULE, "MigrationDiscovery")? - .call1((discovery,))?, + registry::class(py, RecordId::MigrationDiscovery)?.call1((discovery,))?, ), ("decision", decision), ( @@ -193,7 +190,7 @@ pub(super) fn checked<'py>( &[ ( "decision", - imported(py, MIGRATION_TYPES, "ColumnMigrationDecision")? + registry::class(py, RecordId::ColumnMigrationDecision)? .getattr("UNSUPPORTED")?, ), ("message", reason.clone()), @@ -228,7 +225,7 @@ pub(super) fn hints<'py>( } native( py, - "rename_hints", + NativeAttr::RenameHints, &[ ("model_name", candidate.model.getattr("name")?), ("previous", candidate.previous.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs index b70bde8ce7..8408fc47cf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs @@ -1,7 +1,8 @@ //! Boundary conversions for the column migration planner. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_migrations::constants::PLANNER_MODELS; +use crate::bindings::_helpers::records::ids::{NativeAttr, NativeItem, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; @@ -26,24 +27,24 @@ pub(super) fn replace<'py>( pub(super) fn model<'py>( py: Python<'py>, - name: &str, + name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, PLANNER_MODELS, name)?, fields) + call_with(®istry::class(py, name)?, fields) } pub(super) fn native<'py>( py: Python<'py>, - name: &str, + name: impl Into, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, "sqlbuild._native", name)?, fields) + call_with(®istry::item(py, name)?, fields) } pub(super) fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", text(model.py(), key)), @@ -57,7 +58,7 @@ pub(super) fn definition_hash<'py>( ) -> PyResult> { native( model.py(), - "model_definition_hash", + NativeAttr::ModelDefinitionHash, &[ ("model_name", model.getattr("name")?), ("query_sql", model.getattr("query_sql")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs index 2fb09ff0e0..0204ab771f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs @@ -7,6 +7,8 @@ use crate::bindings::_helpers::planner_migrations::column_objects::{ config, definition_hash, lower, native, replace, }; use crate::bindings::_helpers::planner_migrations::models::ColumnCandidate; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -66,7 +68,7 @@ pub(super) fn overlay<'py>( && !candidate.current.is_none() && native( py, - "renames_explain_change", + NativeAttr::RenamesExplainChange, &[ ("previous", candidate.previous.clone()), ("current", candidate.current.clone()), @@ -99,7 +101,7 @@ pub(super) fn overlay<'py>( "fingerprints", native( py, - "WarehouseFingerprints", + RecordId::WarehouseFingerprints, &[ ("models", fingerprints), ("functions", stored.getattr("functions")?), @@ -160,11 +162,7 @@ pub(super) fn cursors<'py>( targets.set_item(name, target)?; } let redirected = call_with( - &imported( - py, - "sqlbuild._native", - "migration_redirected_cursor_snapshots", - )?, + ®istry::native(py, NativeAttr::MigrationRedirectedCursorSnapshots)?, &[ ("runtime", args.value("runtime")?), ("scope", scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs index d3e566786d..902ac4cfec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs @@ -1,11 +1,11 @@ //! Host column migration planning while Python owns warehouse inspection. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::column_objects::replace; use crate::bindings::_helpers::planner_migrations::known_input_columns::KnownInputColumns; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -43,14 +43,18 @@ fn plan_column_migrations<'py>( &adapter.call_method0("sql_analysis_dialect")?, )?; if candidates.is_empty() { - return native(py, "ColumnMigrationPlanning", &[("snapshot", snapshot)]); + return native( + py, + RecordId::ColumnMigrationPlanning, + &[("snapshot", snapshot)], + ); } let database = call_with( - &imported(py, "sqlbuild._native", "migration_planning_database")?, + ®istry::native(py, NativeAttr::MigrationPlanningDatabase)?, &[("runtime", runtime.clone())], )?; let state = call_with( - &imported(py, "sqlbuild._native", "MigrationStateInspection")?, + ®istry::native(py, NativeAttr::MigrationStateInspection)?, &[ ("adapter", adapter), ("connection", runtime.getattr("connection")?), @@ -97,7 +101,7 @@ fn plan_column_migrations<'py>( warnings.extend(checked.warnings); } let policy = call_with( - &imported(py, "sqlbuild._native", "migration_missing_origin_policy")?, + ®istry::native(py, NativeAttr::MigrationMissingOriginPolicy)?, &[("runtime", runtime)], )?; for entry in &mut entries { @@ -110,7 +114,7 @@ fn plan_column_migrations<'py>( let snapshot = super::column_overlay::cursors(&args, overlaid, &entries)?; native( py, - "ColumnMigrationPlanning", + RecordId::ColumnMigrationPlanning, &[ ("snapshot", snapshot), ("entries", PyTuple::new(py, entries)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs index 352ed81e8a..15f6de2d0d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs @@ -1,12 +1,13 @@ //! Preserve column-migration diagnostic codes and public messages. -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::column_objects::text; use crate::bindings::_helpers::planner_migrations::constants::{ DONE, MISSING_ORIGIN_ALLOW_OUTCOME, MISSING_ORIGIN_CONFIRMATION_OUTCOME, MISSING_ORIGIN_DENY_OUTCOME, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyKeyError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use sqlbuild_analysis::planner_migrations::main::column_warning::column_warning; @@ -21,12 +22,12 @@ pub(super) fn warning<'py>( let py = entry.py(); native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", entry.getattr("model_name")?), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr(if error { + registry::class(py, RecordId::WarningSeverity)?.getattr(if error { "ERROR" } else { "WARNING" diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs index a3c0fe63e0..fe781f4777 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs @@ -11,9 +11,9 @@ use sqlbuild_analysis::planner_migrations::main::{ }; use sqlbuild_analysis::planner_migrations::models::Projection; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; fn projections(shape: &Bound<'_, PyAny>) -> PyResult> { let mut result: Vec = Vec::new(); @@ -119,7 +119,7 @@ fn rename_hints<'py>( let mut result: Vec> = Vec::new(); for hint in hints { result.push(call_with( - &imported(py, "sqlbuild._native", "ColumnRenameHint")?, + ®istry::class(py, RecordId::ColumnRenameHint)?, &[ ("model_name", arguments.value("model_name")?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs index 85904dedb9..86b6de5fd3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs @@ -1,8 +1,8 @@ //! Convert existing planner contracts for origin compatibility checks. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyTupleMethods}; @@ -10,8 +10,6 @@ use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_migrations::main::compatibility_findings::compatibility_findings; use sqlbuild_analysis::planner_migrations::models::CompatibilityFinding; -const TYPES_MODULE: &str = "sqlbuild._native"; - fn config(model: &Bound<'_, PyAny>, key: &str) -> PyResult> { model .getattr("config")? @@ -40,13 +38,12 @@ fn blocking<'py>( .filter(|value| !value.is_empty()) .unwrap_or_else(|| "append_new_columns".to_owned()) }; - imported( + registry::class( py, - TYPES_MODULE, if snapshot { - "SnapshotSchemaChangePolicy" + RecordId::SnapshotSchemaChangePolicy } else { - "OnSchemaChange" + RecordId::OnSchemaChange }, )? .call1((&policy,))?; @@ -142,7 +139,7 @@ fn check_migration_compatibility<'py>( let columns = PyDict::new(py); columns.set_item(model.getattr("name")?, arguments.value("origin_columns")?)?; let snapshot = call_with( - &imported(py, "sqlbuild._native", "WarehouseSnapshot")?, + ®istry::class(py, RecordId::WarehouseSnapshot)?, &[ ("existing_relations", relations.into_any()), ("existing_columns", columns.into_any()), @@ -150,7 +147,7 @@ fn check_migration_compatibility<'py>( ], )?; let change = call_with( - &imported(py, "sqlbuild._native", "detect_model_changes")?, + ®istry::native(py, NativeAttr::DetectModelChanges)?, &[ ("model", model.clone()), ("snapshot", snapshot), @@ -205,9 +202,9 @@ fn result<'py>( status: &str, findings: Bound<'py, PyTuple>, ) -> PyResult> { - let status = imported(py, "sqlbuild._native", "MigrationCompatibility")?.call1((status,))?; + let status = registry::class(py, RecordId::MigrationCompatibility)?.call1((status,))?; call_with( - &imported(py, "sqlbuild._native", "MigrationCompatibilityResult")?, + ®istry::class(py, RecordId::MigrationCompatibilityResult)?, &[("status", status), ("findings", findings.into_any())], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs index 5a37210972..7dbe08a7b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs @@ -1,18 +1,11 @@ -pub(super) const PLANNER_MODELS: &str = "sqlbuild._native"; -pub(super) const MIGRATION_TYPES: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; pub(super) const AUTOMATIC: &str = "automatic"; pub(super) const DONE: &str = "done"; pub(super) const PENDING_ARCHIVE: &str = "pending_archive"; pub(super) const PENDING_VIEW: &str = "pending_view"; -pub(super) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(super) const LIST_RELATIONS_FOR_INSPECTION: (&str, &str) = ( - "sqlbuild.adapter.relations.main._list_relations_for_inspection", - "list_relations_for_inspection", -); -pub(super) const GET_COLUMNS_FOR_INSPECTION: (&str, &str) = ( - "sqlbuild.adapter.relations.main._get_columns_for_inspection", - "get_columns_for_inspection", -); +pub(super) const LIST_RELATIONS_FOR_INSPECTION: AdapterHost = + AdapterHost::ListRelationsForInspection; +pub(super) const GET_COLUMNS_FOR_INSPECTION: AdapterHost = AdapterHost::GetColumnsForInspection; pub(super) const QUALIFIED_RELATION_MAX_PARTS: usize = 3; pub(super) const MIGRATION_MODEL_NAME_METADATA_KEY: &str = "model_name"; pub(super) const HIDDEN_ORIGIN_REMEDY: &str = "the connection may lack permission to see it, or it was dropped outside SQLBuild; restore access to it, or remove migrate_from to build the model fresh deliberately (missing_migration_origin does not apply)"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs index 7b4aa59682..24535fae12 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs @@ -5,6 +5,7 @@ use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::{ discovery_events, discovery_models, discovery_requests, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -46,7 +47,7 @@ fn discover_model_migrations<'py>( let triggered = discovery_models::triggered(&models, &scope, &args.value("snapshot")?, &declared_names)?; if !triggered && !manual.is_truthy()? { - return native(py, "ModelMigrationDiscovery", &[]); + return native(py, RecordId::ModelMigrationDiscovery, &[]); } let metadata = discovery_models::metadata(&runtime, &models)?; let dialect = runtime @@ -70,7 +71,7 @@ fn discover_model_migrations<'py>( if !triggered { let hashes = native( py, - "migration_destination_fingerprints", + NativeAttr::MigrationDestinationFingerprints, &[ ("requests", manual.clone()), ("variants", seeds.into_any()), @@ -81,7 +82,7 @@ fn discover_model_migrations<'py>( )?; return native( py, - "ModelMigrationDiscovery", + RecordId::ModelMigrationDiscovery, &[("requests", manual), ("destination_fingerprints", hashes)], ); } @@ -106,7 +107,7 @@ fn discover_model_migrations<'py>( } let matched = native( py, - "match_migration_fingerprints", + NativeAttr::MatchMigrationFingerprints, &[ ("models", PyTuple::new(py, &models)?.into_any()), ( @@ -131,7 +132,7 @@ fn discover_model_migrations<'py>( let warnings = discovery_requests::warnings(&models, &scope, &matched.get_item(2)?)?; let hashes = native( py, - "migration_destination_fingerprints", + NativeAttr::MigrationDestinationFingerprints, &[ ("requests", requests.clone().into_any()), ("variants", matched.get_item(0)?), @@ -142,7 +143,7 @@ fn discover_model_migrations<'py>( )?; native( py, - "ModelMigrationDiscovery", + RecordId::ModelMigrationDiscovery, &[ ("requests", requests.into_any()), ("warnings", warnings.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs index b4e58f3c62..07254edf57 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs @@ -1,6 +1,8 @@ //! Preserve event and declared-origin precedence at the inspection boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::config; use crate::bindings::_helpers::state_rows::migration_projections::newest_move; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; @@ -20,22 +22,13 @@ pub(super) fn newest<'py>( events: &Bound<'py, PyAny>, location: &Bound<'py, PyAny>, ) -> PyResult> { - let relation = imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,))?; + let relation = host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)? + .call1((location,))?; newest_move(events, &relation) } pub(super) fn relation<'py>(location: &Bound<'py, PyAny>) -> PyResult> { - imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,)) + host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)?.call1((location,)) } pub(super) fn declared<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs index 3542ad0358..a1c5aebf53 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs @@ -1,7 +1,11 @@ //! Model ordering, selection and metadata at the migration discovery boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_migrations::column_objects::native; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PySet, PySetMethods}; use sqlbuild_analysis::planner_migrations::main::discovery_order::discovery_order; @@ -75,18 +79,14 @@ pub(super) fn metadata<'py>( let project = runtime.getattr("project")?; let functions = native( py, - "build_function_local_hashes", + NativeAttr::BuildFunctionLocalHashes, &[ ("functions", project.getattr("functions")?), ("dialect", project.getattr("sql_analysis_dialect")?), ], )?; - let identities = imported( - py, - "sqlbuild.compiler.python_nodes.main._hook_identities", - "build_hook_identities", - )? - .call1((project.getattr("hook_functions")?,))?; + let identities = host::entry(py, UserCodeHost::BuildHookIdentities)? + .call1((project.getattr("hook_functions")?,))?; let hooks = PyDict::new(py); for pair in identities.call_method0("items")?.try_iter()? { let pair = pair?; @@ -100,11 +100,7 @@ pub(super) fn metadata<'py>( result.set_item( item.getattr("name")?, call_with( - &imported( - py, - "sqlbuild._native", - "build_model_version_identity_metadata_json", - )?, + ®istry::native(py, NativeAttr::BuildModelVersionIdentityMetadataJson)?, &[ ("model", item.clone()), ("function_local_hashes", functions.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs index 5ef0c9fcb1..205e96846b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs @@ -1,11 +1,13 @@ //! Live candidate inspection and automatic request materialization. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_migrations::column_objects::config; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::discovery_events::{ identity, key, newest, relation, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ PyBool, PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PySet, PySetMethods, PyString, @@ -140,13 +142,12 @@ pub(super) fn automatic<'py>( if let Some((location, origin_model)) = origin { requests.push(native( py, - "ModelMigrationRequest", + RecordId::ModelMigrationRequest, &[ ("model", item.clone()), ( "discovery", - imported(py, "sqlbuild._native", "MigrationDiscovery")? - .getattr("AUTOMATIC")?, + registry::class(py, RecordId::MigrationDiscovery)?.getattr("AUTOMATIC")?, ), ("origin_location", location), ("origin_model", origin_model), @@ -180,9 +181,9 @@ pub(super) fn warnings<'py>( let mut warnings: Vec> = Vec::new(); for (name, detail) in ambiguous { if selected.contains(&name)? { - warnings.push(native(py, "PlanWarning", &[ + warnings.push(native(py, RecordId::PlanWarning, &[ ("model_name", PyString::new(py, &name).into_any()), - ("severity", imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?), + ("severity", registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?), ("message", PyString::new(py, &format!("'{name}' matches {detail}; no automatic migration was inferred. Add migrate_from to choose one.")).into_any()), ("code", PyString::new(py, "M107").into_any()), ])?); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs index 86203c56d0..59a4408c60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs @@ -1,29 +1,23 @@ //! Assemble model migration decisions from inspected warehouse facts. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::{ lower, model, native, replace, text, }; -use crate::bindings::_helpers::planner_migrations::constants::MIGRATION_TYPES; use crate::bindings::_helpers::planner_migrations::model_handover::{ applies, empty_version, handover, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::migration_projections::newest_move; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyBool, PyTuple}; use sqlbuild_analysis::planner_migrations::main::model_decision::model_decision; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - pub(super) fn relation<'py>(location: &Bound<'py, PyAny>) -> PyResult> { - imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,)) + host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)?.call1((location,)) } fn key(location: &Bound<'_, PyAny>) -> PyResult<(String, String)> { @@ -43,7 +37,7 @@ pub(super) fn decision<'py>(entry: &Bound<'py, PyAny>, name: &str) -> PyResult(args: &KeywordArguments<'py>) -> PyResult(args: &KeywordArguments<'py>) -> PyResult(args: &KeywordArguments<'py>) -> PyResult(args: &KeywordArguments<'py>) -> PyResult( let py = model.py(); if handover.is_none() || !destinations.contains( - imported(py, "sqlbuild._native", "stored_migration_fingerprint")? - .call1((&handover,))?, + registry::native(py, NativeAttr::StoredMigrationFingerprint)?.call1((&handover,))?, )? { return Ok(handover); @@ -93,7 +94,7 @@ pub(super) fn equivalent<'py>( "definition_hash", native( py, - "compute_query_hash", + NativeAttr::ComputeQueryHash, &[ ("query_sql", model.getattr("query_sql")?), ("dialect", dialect.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs index 56eab7c306..a224f6ec86 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs @@ -1,10 +1,10 @@ //! Project inherited fingerprints and live relation facts before normal planning. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::column_objects::{config, replace}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods}; @@ -88,11 +88,7 @@ pub(super) fn overlay<'py>( .cast_into::()? .copy()?; let gathered = call_with( - &imported( - py, - "sqlbuild._native", - "migration_redirected_cursor_snapshots", - )?, + ®istry::native(py, NativeAttr::MigrationRedirectedCursorSnapshots)?, &[ ("runtime", runtime), ("scope", args.value("scope")?), @@ -112,7 +108,7 @@ pub(super) fn overlay<'py>( "fingerprints", native( py, - "WarehouseFingerprints", + RecordId::WarehouseFingerprints, &[ ("models", models.into_any()), ("functions", fingerprints.getattr("functions")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs index 99344dbef2..687f6d4239 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs @@ -1,17 +1,15 @@ //! Model migration orchestration over Python-owned live inspection. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::{config, native, replace}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{ PyBool, PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyString, PyTuple, }; use pyo3::{pyfunction, wrap_pyfunction}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - fn declared<'py>(item: &Bound<'py, PyAny>) -> PyResult>> { let py = item.py(); let values = item.getattr("config")?.getattr("values")?; @@ -22,12 +20,12 @@ fn declared<'py>(item: &Bound<'py, PyAny>) -> PyResult> let materialization = config(item, "materialized")?; Ok(Some(native( py, - "ModelMigrationRequest", + RecordId::ModelMigrationRequest, &[ ("model", item.clone()), ( "discovery", - imported(py, NATIVE_RECORDS_MODULE, "MigrationDiscovery")?.getattr("MANUAL")?, + registry::class(py, RecordId::MigrationDiscovery)?.getattr("MANUAL")?, ), ("raw_origin", origin.call_method0("strip")?), ( @@ -127,18 +125,18 @@ fn plan_model_migrations<'py>( let runtime = args.value("runtime")?; let snapshot = args.value("snapshot")?; let schemas = call_with( - &imported(py, "sqlbuild._native", "migration_project_schemas")?, + ®istry::native(py, NativeAttr::MigrationProjectSchemas)?, &[("runtime", runtime.clone())], )?; let state = call_with( - &imported(py, "sqlbuild._native", "MigrationStateInspection")?, + ®istry::native(py, NativeAttr::MigrationStateInspection)?, &[ ("adapter", runtime.getattr("adapter")?), ("connection", runtime.getattr("connection")?), ( "database", call_with( - &imported(py, "sqlbuild._native", "migration_planning_database")?, + ®istry::native(py, NativeAttr::MigrationPlanningDatabase)?, &[("runtime", runtime.clone())], )?, ), @@ -173,7 +171,7 @@ fn discover<'py>( let mut declarations: Vec> = Vec::new(); for request in declared_requests { let resolved = call_with( - &imported(py, "sqlbuild._native", "migration_resolve_origin")?, + ®istry::native(py, NativeAttr::MigrationResolveOrigin)?, &[ ("request", request.clone()), ("runtime", runtime.clone()), @@ -182,7 +180,7 @@ fn discover<'py>( )?; declarations.push(native( py, - "ModelMigrationDeclaration", + RecordId::ModelMigrationDeclaration, &[ ("model_name", request.getattr("model")?.getattr("name")?), ("origin_location", resolved.get_item(0)?), @@ -191,7 +189,7 @@ fn discover<'py>( )?); } call_with( - &imported(py, "sqlbuild._native", "discover_model_migrations")?, + ®istry::native(py, NativeAttr::DiscoverModelMigrations)?, &[ ("runtime", runtime.clone()), ("scope", scope.clone()), @@ -219,7 +217,7 @@ fn finish<'py>( for request in discovery.getattr("requests")?.try_iter()? { let request = request?; let origin = call_with( - &imported(py, "sqlbuild._native", "migration_resolve_origin")?, + ®istry::native(py, NativeAttr::MigrationResolveOrigin)?, &[ ("request", request.clone()), ("runtime", runtime.clone()), @@ -235,7 +233,7 @@ fn finish<'py>( let mut origins: Vec> = Vec::new(); for (_, origin, _) in &resolved { call_with( - &imported(py, "sqlbuild._native", "migration_require_same_database")?, + ®istry::native(py, NativeAttr::MigrationRequireSameDatabase)?, &[("state", state.clone()), ("location", origin.clone())], )?; let schema = origin.getattr("schema")?; @@ -254,12 +252,12 @@ fn finish<'py>( )?; let project_config = runtime.getattr("project_config")?; let snapshots_config = if project_config.is_none() { - imported(py, "sqlbuild._native", "SnapshotsConfig")?.call0()? + registry::class(py, RecordId::SnapshotsConfig)?.call0()? } else { project_config.getattr("snapshots")? }; let policy = call_with( - &imported(py, "sqlbuild._native", "migration_missing_origin_policy")?, + ®istry::native(py, NativeAttr::MigrationMissingOriginPolicy)?, &[("runtime", runtime.clone())], )?; for (request, origin, origin_model) in resolved { @@ -267,7 +265,7 @@ fn finish<'py>( let name = item.getattr("name")?; let decided = native( py, - "decide_model_migration", + NativeAttr::DecideModelMigration, &[ ("runtime", runtime.clone()), ("request", request), @@ -326,7 +324,7 @@ fn finish<'py>( }; let reference_renames = native( py, - "plan_reference_renames", + NativeAttr::PlanReferenceRenames, &[ ("runtime", runtime), ("scope", scope), @@ -342,7 +340,7 @@ fn finish<'py>( } native( py, - "ModelMigrationPlanning", + RecordId::ModelMigrationPlanning, &[ ("snapshot", replace(&overlaid, &fields)?), ("entries", PyTuple::new(py, entries)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs index 42506b327f..8265c05d0f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs @@ -9,6 +9,8 @@ use crate::bindings::_helpers::planner_migrations::models::OldNamePlanner; use crate::bindings::_helpers::planner_migrations::old_name_objects::{ expires, location, relation, retention, stores_history, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyBool, PyList, PyTuple}; @@ -23,7 +25,7 @@ impl<'py> OldNamePlanner<'py> { let py = runtime.py(); let project_config = runtime.getattr("project_config")?; let config = if project_config.is_none() { - imported(py, "sqlbuild._native", "MigrationsConfig")?.call0()? + registry::class(py, RecordId::MigrationsConfig)?.call0()? } else { project_config.getattr("migrations")? }; @@ -137,7 +139,7 @@ impl<'py> OldNamePlanner<'py> { ) -> PyResult> { native( self.runtime.py(), - "old_name_view_skip_reason", + NativeAttr::OldNameViewSkipReason, &[ ("origin", origin.clone()), ("origin_tracked", tracked), @@ -154,7 +156,7 @@ impl<'py> OldNamePlanner<'py> { fn aliases(&self, item: &Bound<'py, PyAny>) -> PyResult> { let py = self.runtime.py(); if !stores_history(item)? { - return imported(py, "sqlbuild._native", "declared_column_aliases")?.call1((item,)); + return registry::native(py, NativeAttr::DeclaredColumnAliases)?.call1((item,)); } let mut aliases: Vec> = Vec::new(); for entry in self.columns.try_iter()? { @@ -229,7 +231,7 @@ impl<'py> OldNamePlanner<'py> { ), ]); } - model(py, "OldNameViewPlanEntry", &fields) + model(py, RecordId::OldNameViewPlanEntry, &fields) } fn resume( @@ -255,7 +257,7 @@ impl<'py> OldNamePlanner<'py> { if !reason.is_none() { return model( py, - "OldNameViewPlanEntry", + RecordId::OldNameViewPlanEntry, &[ ( "model_name", @@ -282,7 +284,7 @@ impl<'py> OldNamePlanner<'py> { } } let retention = history.getattr("required")?.getattr("view_retention")?; - let duration = imported(py, "sqlbuild._native", "Duration")?.call_method1( + let duration = registry::class(py, RecordId::Duration)?.call_method1( "parse", (if retention.is_truthy()? { retention.clone() @@ -305,7 +307,7 @@ impl<'py> OldNamePlanner<'py> { }; model( py, - "OldNameViewPlanEntry", + RecordId::OldNameViewPlanEntry, &[ ("model_name", item.getattr("name")?), ("origin", location(&self.runtime, &history.getattr("old")?)?), @@ -363,5 +365,5 @@ impl<'py> OldNamePlanner<'py> { } fn action<'py>(runtime: &Bound<'py, PyAny>, name: &str) -> PyResult> { - imported(runtime.py(), "sqlbuild._native", "OldNameViewAction")?.getattr(name) + registry::class(runtime.py(), RecordId::OldNameViewAction)?.getattr(name) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs index 140befe160..282804b077 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs @@ -1,17 +1,16 @@ //! Old-name planner object conversions and existing native helper calls. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::{config, model}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::migration_projections::old_name_retention; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; pub(super) fn relation<'py>(location: &Bound<'py, PyAny>) -> PyResult> { - imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,)) + host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)?.call1((location,)) } pub(super) fn location<'py>( @@ -31,7 +30,7 @@ pub(super) fn location<'py>( )?; fields.push(("qualified_name", qualified)); call_with( - &imported(runtime.py(), "sqlbuild._native", "CompiledRelationLocation")?, + ®istry::class(runtime.py(), RecordId::CompiledRelationLocation)?, &fields, ) } @@ -53,7 +52,7 @@ pub(super) fn expires<'py>( now: &Bound<'py, PyAny>, ) -> PyResult> { let duration = - imported(now.py(), "sqlbuild._native", "Duration")?.call_method1("parse", (retention,))?; + registry::class(now.py(), RecordId::Duration)?.call_method1("parse", (retention,))?; if duration.is_none() { Ok(now.py().None().into_bound(now.py())) } else { @@ -81,7 +80,7 @@ pub(super) fn recorded_view<'py>( } model( py, - "OldNameView", + RecordId::OldNameView, &[ ("destination_model", movement.getattr("destination_model")?), ("old", location(runtime, &history.getattr("old")?)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs index eb1ea8fe69..3fe90cdd55 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs @@ -1,10 +1,12 @@ //! Old-name planning orchestration over existing live state inspection. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::old_name_objects::recorded_view; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -39,13 +41,9 @@ fn schemas<'py>( .getattr("existing_relations")? .call_method1("get", (item.getattr("name")?,))?; if !existing.is_none() - && imported( - py, - "sqlbuild.adapter.type_system.main.normalize_relation_type", - "normalize_relation_type", - )? - .call1((existing.getattr("relation_type")?,))? - .eq("view")? + && host::entry(py, AdapterHost::NormalizeRelationType)? + .call1((existing.getattr("relation_type")?,))? + .eq("view")? { include_project = true; break; @@ -53,14 +51,18 @@ fn schemas<'py>( } } if include_project { - let project = native(py, "migration_project_schemas", &[("runtime", runtime)])?; + let project = native( + py, + NativeAttr::MigrationProjectSchemas, + &[("runtime", runtime)], + )?; for schema in project.try_iter()? { needed.add(schema?)?; } } native( py, - "old_name_schemas_to_read", + NativeAttr::OldNameSchemasToRead, &[("snapshot", snapshot), ("needed", needed.into_any())], ) } @@ -108,7 +110,7 @@ fn plan_old_name_views<'py>( } } let state = call_with( - &imported(py, "sqlbuild._native", "MigrationStateInspection")?, + ®istry::native(py, NativeAttr::MigrationStateInspection)?, &[ ("adapter", runtime.getattr("adapter")?), ("connection", runtime.getattr("connection")?), @@ -116,7 +118,7 @@ fn plan_old_name_views<'py>( "database", native( py, - "migration_planning_database", + NativeAttr::MigrationPlanningDatabase, &[("runtime", runtime.clone())], )?, ), @@ -167,7 +169,7 @@ fn plan_old_name_views<'py>( let entries = PyTuple::new(py, entries)?.into_any(); let claims = native( py, - "old_name_claim_errors", + NativeAttr::OldNameClaimErrors, &[ ("selected", PyTuple::new(py, selected)?.into_any()), ("snapshot", args.value("snapshot")?), @@ -181,7 +183,7 @@ fn plan_old_name_views<'py>( )?); native( py, - "OldNameViewPlanning", + RecordId::OldNameViewPlanning, &[ ("entries", entries), ("views", views), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs index ee9fe726d2..994f90033c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs @@ -1,7 +1,11 @@ //! Diagnose literal references to compatibility-only old names. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_migrations::column_objects::{native, text}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyStringMethods, PyTuple}; @@ -11,12 +15,11 @@ fn old_relation<'py>( ) -> PyResult> { let py = old.py(); let reference = call_with( - &imported(py, "sqlbuild.python_nodes.models", "SqlResourceRef")?, + &host::entry(py, UserCodeHost::SqlResourceRef)?, &[ ( "kind", - imported(py, "sqlbuild.python_nodes.types", "SqlResourceRefKind")? - .getattr("MODEL")?, + host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("MODEL")?, ), ("name", name.clone()), ], @@ -82,7 +85,7 @@ pub(super) fn errors<'py>( let relation = literal.getattr("relation")?; let matched = native( py, - "match_project_relation", + NativeAttr::MatchProjectRelation, &[("index", index.clone()), ("relation", relation.clone())], )?; if matched.is_none() || matched.getattr("compatibility_for")?.is_none() { @@ -97,7 +100,7 @@ pub(super) fn errors<'py>( } let message: String = native( py, - "old_name_reference_message", + NativeAttr::OldNameReferenceMessage, &[ ("owner_label", literal.getattr("owner_label")?), ("written", text(py, &parts.join("."))), @@ -108,7 +111,7 @@ pub(super) fn errors<'py>( .extract()?; let remedy: String = native( py, - "hard_coded_relation_remedy", + NativeAttr::HardCodedRelationRemedy, &[ ("owner_kind", literal.getattr("owner_kind")?), ("ref", matched.getattr("ref")?), @@ -123,12 +126,12 @@ pub(super) fn errors<'py>( let line = literal.getattr("line")?.str()?.to_str()?.to_owned(); errors.push(native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", py.None().into_bound(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, ), ( "message", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs index 44af340e9b..f8116bae5b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs @@ -1,9 +1,11 @@ //! Existing old-name planner models at the native decision boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::old_name_objects::relation; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PySet, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -115,12 +117,8 @@ fn old_name_claim_errors<'py>( if existing.is_none() { continue; } - let kind = imported( - py, - "sqlbuild.adapter.type_system.main.normalize_relation_type", - "normalize_relation_type", - )? - .call1((existing.getattr("relation_type")?,))?; + let kind = host::entry(py, AdapterHost::NormalizeRelationType)? + .call1((existing.getattr("relation_type")?,))?; if !kind.eq("view")? { continue; } @@ -137,12 +135,12 @@ fn old_name_claim_errors<'py>( let message = claim_message(&name.extract::()?, &view, &arguments.value("now")?)?; errors.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", name.clone()), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, ), ("message", message.into_pyobject(py)?.into_any()), ("code", "M114".into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs index 0ad044005b..05ae35389a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs @@ -1,9 +1,9 @@ //! Assemble migration policy and redirected live cursor inspection requests. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::native; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; @@ -14,7 +14,7 @@ fn migration_missing_origin_policy<'py>( runtime: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(runtime.py(), "sqlbuild._native", "effective_target_config")?, + ®istry::native(runtime.py(), NativeAttr::EffectiveTargetConfig)?, &[("runtime", runtime.clone())], )? .getattr("missing_migration_origin") @@ -54,7 +54,7 @@ fn migration_redirected_cursor_snapshots<'py>( }; let cursor_scope = native( py, - "CursorSnapshotScope", + RecordId::CursorSnapshotScope, &[ ("model_keys", scope.getattr("selected_keys")?), ("runtime_producer_keys", scope.getattr("selected_keys")?), @@ -64,18 +64,14 @@ fn migration_redirected_cursor_snapshots<'py>( ], )?; let full_refresh = call_with( - &imported( - py, - "sqlbuild._native", - "effectively_full_refreshed_model_names", - )?, + ®istry::native(py, NativeAttr::EffectivelyFullRefreshedModelNames)?, &[ ("project", project.clone()), ("cli_full_refresh", overrides.getattr("full_refresh")?), ], )?; call_with( - &imported(py, "sqlbuild._native", "gather_redirected_cursor_snapshots")?, + ®istry::native(py, NativeAttr::GatherRedirectedCursorSnapshots)?, &[ ("project", project), ("adapter", runtime.getattr("adapter")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs index f0c0e34069..07f52831e7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs @@ -1,9 +1,11 @@ //! Preserve adapter overrides when describing migration execution. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_migrations::{ - column_objects::replace, constants::MIGRATION_TYPES, -}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_migrations::column_objects::replace; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; use pyo3::types::PyBool; use pyo3::{pyfunction, wrap_pyfunction}; @@ -24,7 +26,7 @@ fn migration_promotion<'py>( decision: &Bound<'py, PyAny>, ) -> PyResult> { let adapter = runtime.getattr("adapter")?; - let promotion = imported(runtime.py(), MIGRATION_TYPES, "MigrationPromotion")?; + let promotion = registry::class(runtime.py(), RecordId::MigrationPromotion)?; if adapter .call_method0("supports_transactional_ddl")? .is_truthy()? @@ -71,7 +73,7 @@ fn migration_with_execution<'py>( )?; let transfer = stage.getattr("transfer")?; let fallback = if stage.getattr("fallback_statements")?.is_truthy()? { - imported(py, "sqlbuild.adapter.contract.types", "MigrationTransfer")?.getattr("COPY")? + host::entry(py, AdapterHost::MigrationTransfer)?.getattr("COPY")? } else { py.None().into_bound(py) }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs index 975eaeb167..3a14bdae93 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs @@ -1,7 +1,9 @@ //! Resolve authored migration origins at the adapter and inspection boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_migrations::constants::QUALIFIED_RELATION_MAX_PARTS; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PySet, PySetMethods, PyTuple}; use pyo3::{PyErr, pyfunction, wrap_pyfunction}; @@ -19,7 +21,7 @@ pub(super) fn location<'py>( let mut fields = fields.to_vec(); fields.push(("qualified_name", qualified)); call_with( - &imported(runtime.py(), "sqlbuild._native", "CompiledRelationLocation")?, + ®istry::class(runtime.py(), RecordId::CompiledRelationLocation)?, &fields, ) } @@ -61,7 +63,7 @@ fn named<'py>( fn input_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - imported(py, "sqlbuild._native", "PlannerInputError")?.call1((message,))?, + registry::error(py, ErrorId::PlannerInput)?.call1((message,))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs index c16a6abc8f..cf135f6f12 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs @@ -1,9 +1,9 @@ //! Adapt existing Python query shape models to pure migration proofs. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::errors::ProofError; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyFrozenSet, PyTuple}; @@ -54,7 +54,7 @@ fn parse_query_shape<'py>( let mut projections: Vec> = Vec::new(); for item in shape.projections { projections.push(call_with( - &imported(py, "sqlbuild._native", "QueryProjection")?, + ®istry::class(py, RecordId::QueryProjection)?, &[ ("name", item.name.into_pyobject(py)?.into_any()), ("expression", item.expression.into_pyobject(py)?.into_any()), @@ -74,7 +74,7 @@ fn parse_query_shape<'py>( None => py.None().into_bound(py), }; call_with( - &imported(py, "sqlbuild._native", "QueryShape")?, + ®istry::class(py, RecordId::QueryShape)?, &[ ("projections", PyTuple::new(py, projections)?.into_any()), ("body", shape.body.into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs index ad53285986..ad6d16ca7e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs @@ -12,10 +12,13 @@ use sqlbuild_core::constants::OLD_NAME_VIEW_TABLE_NAME; use sqlbuild_state::events::main::newest_fingerprint::newest_fingerprint; use sqlbuild_state::events::models::{EventRelation, FingerprintKey, FingerprintOrder}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::constants::{ - GET_COLUMNS_FOR_INSPECTION, LIST_RELATIONS_FOR_INSPECTION, NATIVE_MODULE, + GET_COLUMNS_FOR_INSPECTION, LIST_RELATIONS_FOR_INSPECTION, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::constants::{COLUMN_KIND, MODEL_KIND, OLD_NAME_KIND}; use crate::bindings::_helpers::state_rows::migration_io::{read_events, stored_old_name_columns}; use crate::bindings::_helpers::state_rows::migration_objects::layout; @@ -145,9 +148,8 @@ impl MigrationStateInspection { schemas: Vec>, names: Bound<'py, PyAny>, ) -> PyResult>> { - let (module, name) = LIST_RELATIONS_FOR_INSPECTION; call_with( - &imported(py, module, name)?, + &host::entry(py, LIST_RELATIONS_FOR_INSPECTION)?, &[ ("adapter", self.adapter.bind(py).clone()), ("connection", self.connection.bind(py).clone()), @@ -173,7 +175,7 @@ impl MigrationStateInspection { let latest: Bound<'py, PyAny> = if known.is_none() { let adapter: &Bound<'py, PyAny> = self.adapter.bind(py); let fingerprint_set: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "read_latest_fingerprints")?, + ®istry::native(py, NativeAttr::ReadLatestFingerprints)?, &[ ("connection", self.connection.bind(py).clone()), ("execute", adapter.getattr("execute")?), @@ -479,13 +481,12 @@ impl MigrationStateInspection { let columns: Bound<'_, PyAny> = if matched.is_empty() { PyDict::new(py).into_any() } else { - let (module, name) = GET_COLUMNS_FOR_INSPECTION; let this: PyRef<'_, Self> = slf.borrow(); let adapter: Bound<'_, PyAny> = this.adapter.bind(py).clone(); let connection: Bound<'_, PyAny> = this.connection.bind(py).clone(); drop(this); call_with( - &imported(py, module, name)?, + &host::entry(py, GET_COLUMNS_FOR_INSPECTION)?, &[ ("adapter", adapter), ("connection", connection), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs index ae3394bacb..0f02cc0ce8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs @@ -3,6 +3,7 @@ use crate::bindings::_helpers::planner_phases::constants::MISSING_UPSTREAM_CODE; use crate::bindings::_helpers::planner_phases::objects::{frozenset, native, planner_error}; use crate::bindings::_helpers::planner_warehouse::source_tables::check_selected_source_tables_exist; +use crate::bindings::_helpers::records::ids::NativeAttr; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -26,7 +27,7 @@ pub(crate) fn check_selected_scope_buildability<'py>( } let missing = native( py, - "check_buildability", + NativeAttr::CheckBuildability, &[ ("selected_keys", selected_scope.getattr("selected_keys")?), ("upstream_deps", selected_scope.getattr("upstream_deps")?), @@ -41,14 +42,14 @@ pub(crate) fn check_selected_scope_buildability<'py>( if missing.is_truthy()? { let message: String = native( py, - "missing_upstream_message", + NativeAttr::MissingUpstreamMessage, &[ ("missing", missing), ( "edge_origins", native( py, - "build_execution_edge_origins", + NativeAttr::BuildExecutionEdgeOrigins, &[("project", project.clone())], )?, ), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs index 957bc533a0..5c45d15791 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs @@ -1,12 +1,5 @@ //! Python modules and names the planner phases exchange values with. -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const PLANNER_MODELS: &str = "sqlbuild._native"; -pub(crate) const PLANNER_TYPES: &str = "sqlbuild._native"; -pub(crate) const PLANNER_EXCEPTIONS: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_MODELS: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const ADAPTER_TYPES: &str = "sqlbuild.adapter.contract.types"; -pub(crate) const SPEC_MODELS: &str = "sqlbuild._native"; pub(crate) const SNOWFLAKE_ADAPTER: &str = "snowflake"; pub(crate) const BIGQUERY_ADAPTER: &str = "bigquery"; pub(crate) const TRANSIENT_TABLE_TYPE: &str = "transient"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs index df59a1c784..0b13e121f6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs @@ -1,9 +1,10 @@ //! Execution-scope pruning, write-hash reconciliation and plan entry construction. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_phases::constants::NATIVE_MODULE; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_phases::objects::native; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -20,7 +21,7 @@ pub(crate) fn prune_planner_execution_scope<'py>( let inspection = scopes.getattr("inspection_scope")?; native( py, - "PlannerScopePruningResult", + RecordId::PlannerScopePruningResult, &[ ("inspection_scope", inspection.clone()), ("execution_scope", inspection), @@ -45,7 +46,7 @@ pub(crate) fn reconcile_execution_changes<'py>( let py = warehouse.py(); let honest = native( py, - "with_honest_model_write_hashes", + NativeAttr::WithHonestModelWriteHashes, &[ ("scope", pruning.getattr("execution_scope")?), ("snapshot", warehouse.getattr("snapshot")?), @@ -58,7 +59,7 @@ pub(crate) fn reconcile_execution_changes<'py>( )?; let resolved = native( py, - "merge_recomputed_model_changes", + NativeAttr::MergeRecomputedModelChanges, &[ ("resolved_actions", pruning.getattr("resolved_actions")?), ("changes", honest.clone()), @@ -66,7 +67,7 @@ pub(crate) fn reconcile_execution_changes<'py>( )?; native( py, - "PlannerChangeReconciliation", + RecordId::PlannerChangeReconciliation, &[("changes", honest), ("resolved_actions", resolved)], ) } @@ -127,7 +128,7 @@ pub(crate) fn build_planner_entry_results<'py>( let config = runtime.getattr("project_config")?; let execution_scope = pruning.getattr("execution_scope")?; let relations = call_with( - &imported(py, NATIVE_MODULE, "build_planner_relations_context")?, + ®istry::native(py, NativeAttr::BuildPlannerRelationsContext)?, &[ ("project", project.clone()), ("adapter", runtime.getattr("adapter")?), @@ -158,7 +159,7 @@ pub(crate) fn build_planner_entry_results<'py>( max_override.clone() }; let limit_action = if config.is_none() { - native(py, "PlanEntryBuildInputs", &[])?.getattr("microbatch_limit_action")? + native(py, RecordId::PlanEntryBuildInputs, &[])?.getattr("microbatch_limit_action")? } else { config .getattr("microbatches")? @@ -167,7 +168,7 @@ pub(crate) fn build_planner_entry_results<'py>( }; let build_inputs = native( py, - "PlanEntryBuildInputs", + RecordId::PlanEntryBuildInputs, &[ ("source_freshness_blocked_model_names", blocked), ( @@ -202,7 +203,7 @@ pub(crate) fn build_planner_entry_results<'py>( ], )?; let entries = call_with( - &imported(py, NATIVE_MODULE, "build_plan_entries")?, + ®istry::native(py, NativeAttr::BuildPlanEntries)?, &[ ("project", project), ("adapter", runtime.getattr("adapter")?), @@ -220,7 +221,7 @@ pub(crate) fn build_planner_entry_results<'py>( )?; native( py, - "PlannerEntryResults", + RecordId::PlannerEntryResults, &[("model_entry_results", entries)], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs index 1eaffa57e3..be031788b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs @@ -2,6 +2,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_phases::objects::{frozenset, native, replace}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -15,7 +16,7 @@ fn version_identities<'py>( ) -> PyResult> { native( project.py(), - "build_direct_model_version_identities", + NativeAttr::BuildDirectModelVersionIdentities, &[ ("functions", project.getattr("functions")?), ("seeds", project.getattr("seeds")?), @@ -41,7 +42,7 @@ pub(crate) fn build_planner_identity_context<'py>( }; native( project.py(), - "PlannerIdentityContext", + RecordId::PlannerIdentityContext, &[ ("version_identities", identities), ("stale_warning_identities", stale), @@ -128,7 +129,7 @@ pub(crate) fn detect_stale_warning_changes<'py>( let stale_identities = identities.getattr("stale_warning_identities")?; let remaining_changes = native( py, - "detect_model_changes_in_scope", + NativeAttr::DetectModelChangesInScope, &[ ("project", project.clone()), ( @@ -168,7 +169,7 @@ pub(crate) fn detect_stale_warning_changes<'py>( } native( py, - "PlannerChangeResults", + RecordId::PlannerChangeResults, &[ ("models", models.into_any()), ("functions", PyDict::new(py).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs index cce89afb2c..dece55e662 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs @@ -1,9 +1,10 @@ //! Boundary helpers shared by the native planner phases. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; -use crate::bindings::_helpers::planner_phases::constants::{ - NATIVE_MODULE, PLANNER_EXCEPTIONS, PLANNER_MODELS, -}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::models::HostId; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, NativeItem, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyFrozenSet, PyString, PyTuple}; @@ -17,31 +18,31 @@ pub(crate) fn optional_text<'py>(py: Python<'py>, value: Option<&str>) -> Bound< value.map_or_else(|| py.None().into_bound(py), |value| text(py, value)) } -/// `module.name(**fields)`. +/// The staying-Python class `class(**fields)`. pub(crate) fn construct<'py>( py: Python<'py>, - (module, name): (&str, &str), + class: impl Into, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, module, name)?, fields) + call_with(&host::entry(py, class)?, fields) } /// A planner model dataclass. pub(crate) fn planner_model<'py>( py: Python<'py>, - name: &str, + name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - construct(py, (PLANNER_MODELS, name), fields) + call_with(®istry::class(py, name)?, fields) } /// `sqlbuild._native.name(**fields)`. pub(crate) fn native<'py>( py: Python<'py>, - name: &str, + name: impl Into, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - construct(py, (NATIVE_MODULE, name), fields) + call_with(®istry::item(py, name)?, fields) } /// `dataclasses.replace(value, **fields)`. @@ -53,13 +54,22 @@ pub(crate) fn replace<'py>( .call((value,), Some(&keywords(value.py(), fields)?)) } -/// A Python enum member from its value. +/// A staying-Python enum member from its value. pub(crate) fn member<'py>( py: Python<'py>, - (module, name): (&str, &str), + class: impl Into, value: &str, ) -> PyResult> { - imported(py, module, name)?.call1((value,)) + host::entry(py, class)?.call1((value,)) +} + +/// A native string enum member from its value. +pub(crate) fn native_member<'py>( + py: Python<'py>, + class: RecordId, + value: &str, +) -> PyResult> { + registry::class(py, class)?.call1((value,)) } /// `PlannerInputError(message, code=code, help=help)`. @@ -68,7 +78,7 @@ pub(crate) fn planner_error( message: &str, (code, help): (Option<&str>, Option<&str>), ) -> PyErr { - let raised = imported(py, PLANNER_EXCEPTIONS, "PlannerInputError").and_then(|class| { + let raised = registry::error(py, ErrorId::PlannerInput).and_then(|class| { let fields = keywords( py, &[ @@ -146,7 +156,7 @@ pub(crate) fn by_name<'py>( pub(crate) fn config_str(model: &Bound<'_, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", text(model.py(), key)), @@ -161,7 +171,7 @@ pub(crate) fn model_key<'py>( name: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, "sqlbuild._native", "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ( "resource_type", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs index 38700ac347..5ce7e353c0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs @@ -1,12 +1,13 @@ //! Plan output assembly: storage policies, warnings and direct-mode metadata. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_phases::constants::NATIVE_MODULE; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_phases::objects::{ model_key_names, native, replace, sorted_names, strings, text, tuple, }; use crate::bindings::_helpers::planner_warehouse::source_freshness::source_freshness_unknown_warnings; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::PyDict as KeywordDict; @@ -78,7 +79,7 @@ pub(crate) fn assemble_base_plan_output<'py>( let version = identities.getattr("version_identities")?; let extras = native( py, - "PlanOutputExtras", + RecordId::PlanOutputExtras, &[ ( "seed_version_hashes", @@ -92,7 +93,7 @@ pub(crate) fn assemble_base_plan_output<'py>( ], )?; let plan_output = call_with( - &imported(py, NATIVE_MODULE, "build_plan_output")?, + ®istry::native(py, NativeAttr::BuildPlanOutput)?, &[ ("project", runtime.getattr("project")?), ("adapter", runtime.getattr("adapter")?), @@ -191,7 +192,7 @@ pub(crate) fn with_storage_policies<'py>( } entries = native( py, - "with_migration_fingerprints", + NativeAttr::WithMigrationFingerprints, &[ ("entries", entries), ("models_by_name", models.into_any()), @@ -223,8 +224,8 @@ pub(crate) fn with_storage_policies<'py>( ("warehouse", warehouse.clone()), ("scope", selected), ]; - let table_types = native(py, "plan_table_types", &storage)?; - let retention = native(py, "plan_retention", &storage)?; + let table_types = native(py, NativeAttr::PlanTableTypes, &storage)?; + let retention = native(py, NativeAttr::PlanRetention, &storage)?; replace( plan_output, &[ @@ -287,7 +288,7 @@ pub(crate) fn with_plan_warnings<'py>( } let stale = native( py, - "build_stale_out_of_selection_warnings", + NativeAttr::BuildStaleOutOfSelectionWarnings, &[ ("original_scope", scopes.getattr("stale_warning_scope")?), ("execution_scope", pruning.getattr("execution_scope")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs index c7ff944b97..ff3d379738 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs @@ -1,15 +1,19 @@ //! Warehouse-aware time-travel retention and Snowflake table-type planning. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::constants::{ - ADAPTER_MODELS, ADAPTER_TYPES, BIGQUERY_ADAPTER, DECREASE_WARNING, DOWNGRADE_HISTORY_DAYS, - PERMANENT_TABLE_TYPE, PLANNER_TYPES, RETENTION_DECREASE, RETENTION_INCREASE, RETENTION_MATCH, - SNOWFLAKE_ADAPTER, SPEC_MODELS, TRANSIENT_TABLE_TYPE, + BIGQUERY_ADAPTER, DECREASE_WARNING, DOWNGRADE_HISTORY_DAYS, PERMANENT_TABLE_TYPE, + RETENTION_DECREASE, RETENTION_INCREASE, RETENTION_MATCH, SNOWFLAKE_ADAPTER, + TRANSIENT_TABLE_TYPE, }; use crate::bindings::_helpers::planner_phases::objects::{ - construct, member, model_key_names, native, optional_text, planner_error, planner_model, - replace, sorted_names, text, tuple, + construct, member, model_key_names, native, native_member, optional_text, planner_error, + planner_model, replace, sorted_names, text, tuple, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; @@ -31,11 +35,11 @@ pub(crate) fn effective_target_config<'py>( .getattr("project")? .getattr("effective_target_name")?; if project_config.is_none() || local_config.is_none() || target_name.is_none() { - return construct(py, (SPEC_MODELS, "TargetConfig"), &[]); + return native(py, RecordId::TargetConfig, &[]); } native( py, - "resolve_target_config", + NativeAttr::ResolveTargetConfig, &[ ("project_config", project_config), ("local_config", local_config), @@ -115,7 +119,7 @@ pub(crate) fn plan_table_types<'py>( .getattr("snapshot")? .getattr("existing_relations")?; let adapter = runtime.getattr("adapter")?; - let materialization = imported(py, PLANNER_TYPES, "MaterializationType")?; + let materialization = registry::class(py, RecordId::MaterializationType)?; let mut entries: Vec<_> = Vec::new(); for model in models { let config = model.getattr("config")?; @@ -154,7 +158,7 @@ pub(crate) fn plan_table_types<'py>( let target_name: String = destination.getattr("name")?.extract()?; entries.push(planner_model( py, - "TableTypePlanEntry", + RecordId::TableTypePlanEntry, &[ ("model_name", model.getattr("name")?), ("destination", destination), @@ -218,7 +222,7 @@ fn entry<'py>( }; planner_model( py, - "RetentionPlanEntry", + RecordId::RetentionPlanEntry, &[ ("request", base.request.clone()), ("model_names", base.model_names.clone()), @@ -227,11 +231,11 @@ fn entry<'py>( ("source", base.source.clone()), ( "direction", - member(py, (PLANNER_TYPES, "RetentionDirection"), direction)?, + native_member(py, RecordId::RetentionDirection, direction)?, ), ( "phase", - member(py, (PLANNER_TYPES, "RetentionPlanPhase"), phase)?, + native_member(py, RecordId::RetentionPlanPhase, phase)?, ), ("statements", statements), ( @@ -335,12 +339,12 @@ fn relation_retention<'py>( } let request = construct( py, - (ADAPTER_MODELS, "RetentionRequest"), + AdapterHost::RetentionRequest, &[ ("request_id", model.getattr("name")?), ( "scope", - member(py, (ADAPTER_TYPES, "RetentionScope"), "relation")?, + member(py, AdapterHost::RetentionScope, "relation")?, ), ("database", destination.getattr("database")?), ("schema", schema.clone()), @@ -373,7 +377,7 @@ fn relation_retention<'py>( let relation = relations.get_item(model.getattr("name")?)?; let relation_transient = relation.getattr("is_transient")?; let lookup = call_with( - &imported(py, ADAPTER_MODELS, "RelationLookup")?.getattr("key")?, + &host::entry(py, AdapterHost::RelationLookup)?.getattr("key")?, &[ ("database", destination.getattr("database")?), ("schema", schema), @@ -526,12 +530,12 @@ fn bigquery_retention<'py>( let desired_days = desired_values.into_iter().next().unwrap_or_default(); let request = construct( py, - (ADAPTER_MODELS, "RetentionRequest"), + AdapterHost::RetentionRequest, &[ ("request_id", text(py, &format!("{prefix}{schema}"))), ( "scope", - member(py, (ADAPTER_TYPES, "RetentionScope"), "namespace")?, + member(py, AdapterHost::RetentionScope, "namespace")?, ), ("database", optional_text(py, database.as_deref())), ("schema", text(py, &schema)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs index 5e4f8c23e6..4338918063 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs @@ -1,6 +1,7 @@ //! Resolve the selected, stale-warning and inspection scopes of one plan. use crate::bindings::_helpers::planner_phases::objects::{native, replace, tuple}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -17,7 +18,7 @@ pub(crate) fn resolve_planner_scopes<'py>( let auto_load = policies.getattr("auto_load_sources")?; let selected = native( py, - "build_planner_scope", + NativeAttr::BuildPlannerScope, &[ ("project", project.clone()), ("select", selection.getattr("select")?), @@ -37,7 +38,7 @@ pub(crate) fn resolve_planner_scopes<'py>( let empty = PyTuple::empty(py).into_any(); let full = native( py, - "build_planner_scope", + NativeAttr::BuildPlannerScope, &[ ("project", project.clone()), ("select", empty.clone()), @@ -47,7 +48,7 @@ pub(crate) fn resolve_planner_scopes<'py>( )?; let stale_keys = native( py, - "transitive_closure_many", + NativeAttr::TransitiveClosureMany, &[ ("starts", selected.getattr("selected_keys")?), ("edges", full.getattr("upstream_deps")?), @@ -91,7 +92,7 @@ pub(crate) fn resolve_planner_scopes<'py>( )?; native( py, - "PlannerScopeResolution", + RecordId::PlannerScopeResolution, &[ ("selected_scope", selected.clone()), ("stale_warning_scope", stale_scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs index 632375212c..1ac5f15e7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs @@ -1,11 +1,12 @@ //! The warehouse inspection phase: snapshot, migrations and inspection relations in one pass. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_phases::constants::NATIVE_MODULE; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_phases::full_refresh::effectively_full_refreshed_model_names; use crate::bindings::_helpers::planner_phases::objects::native; use crate::bindings::_helpers::planner_warehouse::fingerprint_cache::MigrationFingerprintCache; use crate::bindings::_helpers::planner_warehouse::snapshot::{SnapshotRequest, warehouse_snapshot}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -42,7 +43,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( let config = runtime.getattr("project_config")?; let cursor_scope = native( py, - "CursorSnapshotScope", + RecordId::CursorSnapshotScope, &[ ("model_keys", selected_scope.getattr("selected_keys")?), ( @@ -86,7 +87,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( })?; let migrations = native( py, - "plan_model_migrations", + NativeAttr::PlanModelMigrations, &[ ("runtime", runtime.clone()), ("scope", selected_scope.clone()), @@ -98,7 +99,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( )?; fingerprints.call_method0("persist")?; let inspection_relations = call_with( - &imported(py, NATIVE_MODULE, "build_planner_relations_context")?, + ®istry::native(py, NativeAttr::BuildPlannerRelationsContext)?, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -131,7 +132,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( } let columns = native( py, - "plan_column_migrations", + NativeAttr::PlanColumnMigrations, &[ ("runtime", runtime.clone()), ("scope", selected_scope.clone()), @@ -151,7 +152,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( )?; let old_names = native( py, - "plan_old_name_views", + NativeAttr::PlanOldNameViews, &[ ("runtime", runtime.clone()), ("scope", selected_scope), @@ -178,7 +179,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( } native( py, - "PlannerWarehouseState", + RecordId::PlannerWarehouseState, &[ ("snapshot", columns.getattr("snapshot")?), ("inspection_relations", inspection_relations), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs index 9d4703965a..6bd7cb4f40 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs @@ -1,7 +1,9 @@ //! Layouts of the planner, migration, cursor and fingerprint value records hosted natively. use crate::bindings::_helpers::build_command::support::utc_now; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::record_behaviours::planner::forward_only_backfill; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::models::{EnumSpec, FieldDefault, FieldSpec, RecordSpec}; const R_PARSED_SELECTOR: RecordSpec = RecordSpec { @@ -47,7 +49,7 @@ const R_PLANNER_SCOPE: RecordSpec = RecordSpec { FieldSpec::new("python_read_source_names", FieldDefault::EmptyFrozenset), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), ], }; @@ -86,7 +88,7 @@ const R_PLANNER_SELECTION: RecordSpec = RecordSpec { FieldSpec::new("python_read_source_names", FieldDefault::EmptyFrozenset), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), ], }; @@ -359,7 +361,7 @@ const R_MODEL_CURSOR_SNAPSHOT: RecordSpec = RecordSpec { FieldSpec::new("unavailable_watermark_tags", FieldDefault::EmptyTuple), FieldSpec::new( "cursor_watermark_mode", - FieldDefault::Member("sqlbuild._native", "CursorWatermarkMode", "ALL"), + FieldDefault::NativeMember(RecordId::CursorWatermarkMode, "ALL"), ), FieldSpec::new("upstream_terminal_starts", FieldDefault::EmptyTuple), FieldSpec::new("upstream_terminal_ends", FieldDefault::EmptyTuple), @@ -525,7 +527,7 @@ const R_MODEL_MIGRATION_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("origin_tracked", FieldDefault::False), FieldSpec::new( "missing_origin_policy", - FieldDefault::Member("sqlbuild._native", "MissingMigrationOriginPolicy", "ALLOW"), + FieldDefault::NativeMember(RecordId::MissingMigrationOriginPolicy, "ALLOW"), ), ], }; @@ -579,7 +581,7 @@ const R_COLUMN_MIGRATION_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("message", FieldDefault::None), FieldSpec::new( "missing_origin_policy", - FieldDefault::Member("sqlbuild._native", "MissingMigrationOriginPolicy", "ALLOW"), + FieldDefault::NativeMember(RecordId::MissingMigrationOriginPolicy, "ALLOW"), ), ], }; @@ -691,11 +693,11 @@ const R_SEED_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("fingerprint_metadata_json", FieldDefault::Text("{}")), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "PlanAction", "LOAD_SEED"), + FieldDefault::NativeMember(RecordId::PlanAction, "LOAD_SEED"), ), FieldSpec::new( "reason", - FieldDefault::Member("sqlbuild._native", "PlanReason", "FIRST_RUN"), + FieldDefault::NativeMember(RecordId::PlanReason, "FIRST_RUN"), ), ], }; @@ -709,11 +711,7 @@ const R_SOURCE_LOAD_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::required("destination"), FieldSpec::new( "resource_kind", - FieldDefault::Member( - "sqlbuild.runtime.contracts.types", - "ExecutionResourceKind", - "SOURCE", - ), + FieldDefault::HostMember(ExecutorHost::ExecutionResourceKind.id(), "SOURCE"), ), FieldSpec::new("write_strategy", FieldDefault::None), FieldSpec::new("cursor_column", FieldDefault::None), @@ -747,7 +745,7 @@ const R_FUNCTION_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("previous_query_sql", FieldDefault::None), FieldSpec::new( "reason", - FieldDefault::Member("sqlbuild._native", "PlanReason", "NO_CHANGE"), + FieldDefault::NativeMember(RecordId::PlanReason, "NO_CHANGE"), ), ], }; @@ -1042,7 +1040,7 @@ const R_RELATION_FIXTURE_PLANNING_CONTEXT: RecordSpec = RecordSpec { FieldSpec::required("expected_types"), FieldSpec::uncompared_shown( "fixture_inferences", - FieldDefault::Call("sqlbuild._native", "FixtureColumnInferences"), + FieldDefault::NativeClassCall(NativeAttr::FixtureColumnInferences), ), ], }; @@ -1112,7 +1110,7 @@ const R_WAREHOUSE_SNAPSHOT: RecordSpec = RecordSpec { FieldSpec::new("existing_columns", FieldDefault::EmptyDict), FieldSpec::new( "fingerprints", - FieldDefault::Call("sqlbuild._native", "WarehouseFingerprints"), + FieldDefault::NativeCall(RecordId::WarehouseFingerprints), ), FieldSpec::new("cursor_snapshots", FieldDefault::EmptyDict), FieldSpec::new( @@ -1239,7 +1237,7 @@ const R_PLAN_ENTRY_BUILD_INPUTS: RecordSpec = RecordSpec { FieldSpec::new("max_microbatches_is_override", FieldDefault::False), FieldSpec::new( "microbatch_limit_action", - FieldDefault::Member("sqlbuild._native", "MicrobatchLimitAction", "ERROR"), + FieldDefault::NativeMember(RecordId::MicrobatchLimitAction, "ERROR"), ), ], }; @@ -1250,7 +1248,7 @@ const R_FUNCTION_CHANGE_RESULT: RecordSpec = RecordSpec { FieldSpec::required("fingerprint_sql"), FieldSpec::new( "reason", - FieldDefault::Member("sqlbuild._native", "PlanReason", "NO_CHANGE"), + FieldDefault::NativeMember(RecordId::PlanReason, "NO_CHANGE"), ), ], }; @@ -1364,7 +1362,7 @@ const R_PLANNED_SQL_TESTS: RecordSpec = RecordSpec { FieldSpec::required("selected_keys"), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), FieldSpec::new("entries", FieldDefault::EmptyTuple), FieldSpec::new("warnings", FieldDefault::EmptyTuple), @@ -1472,7 +1470,7 @@ const R_PLANNER_WAREHOUSE_STATE: RecordSpec = RecordSpec { FieldSpec::new("old_name_views", FieldDefault::EmptyTuple), FieldSpec::new( "migration_fingerprints", - FieldDefault::Call("sqlbuild._native", "MigrationFingerprintCache"), + FieldDefault::NativeClassCall(NativeAttr::MigrationFingerprintCache), ), ], }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs index 1daebfe0ac..fa27998cc4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs @@ -1,11 +1,13 @@ //! Snapshot conversion and cursor window policy boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::conversion::{CursorSession, algebra_error}; use crate::bindings::_helpers::cursor::scalars::try_parse; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -15,9 +17,6 @@ use sqlbuild_cursor::planner::main::raw_bounds::raw_bounds; use sqlbuild_cursor::planner::main::replay_start::replay_start; use sqlbuild_cursor::planner::models::{CursorKind, CursorSnapshot, ReplayPolicy}; -const MODELS: &str = "sqlbuild._native"; -const MAXIMUM_START: &str = "sqlbuild._native"; - pub(super) fn optional_scalar<'py>( session: &CursorSession<'py>, value: &Bound<'py, PyAny>, @@ -69,7 +68,7 @@ pub(super) fn duration(value: &Bound<'_, PyAny>) -> PyResult( &["cursor_grain", "maximum_start_policy"], ), )?; - let bounds_class = imported(py, MODELS, "CursorBounds")?; + let bounds_class = registry::class(py, RecordId::CursorBounds)?; if args.flag("is_microbatch")? { - let sentinel = imported(py, "sqlbuild.cursor_algebra.types", "BoundSentinel")?; + let sentinel = host::entry(py, ExecutorHost::BoundSentinel)?; return call_with( &bounds_class, &[ @@ -259,7 +258,7 @@ fn compute_cursor_bounds<'py>( ), ]); let replayed = call_with( - &imported(py, "sqlbuild._native", "apply_typed_cursor_replay_policy")?, + ®istry::native(py, NativeAttr::ApplyTypedCursorReplayPolicy)?, &replay_args, )?; let bounds = call_with( @@ -270,7 +269,7 @@ fn compute_cursor_bounds<'py>( let policy = if policy.is_truthy()? { policy } else { - imported(py, MODELS, "MaximumStartPolicyInputs")?.call0()? + registry::class(py, RecordId::MaximumStartPolicyInputs)?.call0()? }; let mut policy_args = vec![ ("bounds", bounds), @@ -294,7 +293,7 @@ fn compute_cursor_bounds<'py>( policy_args.push((key, args.value(key)?)); } call_with( - &imported(py, MAXIMUM_START, "apply_maximum_start_policy")?, + ®istry::native(py, NativeAttr::ApplyMaximumStartPolicy)?, &policy_args, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs index c9d3b069c1..45114be861 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs @@ -1,29 +1,28 @@ //! Model cursor orchestration over live warehouse snapshot facts. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_resolution::constants::CURSOR_START; use crate::bindings::_helpers::planner_resolution::cursor_policies::{ future_cursor_config, start_cursor_config, }; -use crate::bindings::_helpers::planner_resolution::values::{NATIVE_MODULE, string}; +use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; -const MODELS: &str = "sqlbuild._native"; -const CURSOR: &str = "sqlbuild._native"; - fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { call_with( - &imported( + ®istry::native( model.py(), - NATIVE_MODULE, if key == CURSOR_START { - "get_config_cursor_bound" + NativeAttr::GetConfigCursorBound } else { - "get_config_str" + NativeAttr::GetConfigStr }, )?, &[ @@ -34,9 +33,9 @@ fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult) -> PyResult> { - let sentinel = imported(py, "sqlbuild.cursor_algebra.types", "BoundSentinel")?; + let sentinel = host::entry(py, ExecutorHost::BoundSentinel)?; call_with( - &imported(py, MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[ ("start", sentinel.getattr("START")?), ("end", sentinel.getattr("END")?), @@ -53,12 +52,7 @@ fn destination_exists(model: &Bound<'_, PyAny>, snapshot: &Bound<'_, PyAny>) -> } let destination = model.getattr("destination")?; let key = call_with( - &imported( - model.py(), - "sqlbuild.adapter.contract.models", - "RelationLookup", - )? - .getattr("key")?, + &host::entry(model.py(), AdapterHost::RelationLookup)?.getattr("key")?, &[ ("database", destination.getattr("database")?), ("schema", destination.getattr("schema")?), @@ -114,7 +108,7 @@ fn compute_model_cursor_bounds<'py>( let start_override = args.value("start_cursor_override")?; let end_override = args.value("end_cursor_override")?; let bounded = call_with( - &imported(py, NATIVE_MODULE, "resolve_bounded_cursor_override")?, + ®istry::native(py, NativeAttr::ResolveBoundedCursorOverride)?, &[ ("start_cursor_override", start_override.clone()), ("end_cursor_override", end_override.clone()), @@ -130,7 +124,7 @@ fn compute_model_cursor_bounds<'py>( .getattr("cursor_snapshots")? .call_method1("get", (model.getattr("name")?,))?; let roles = call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_input_roles")?, + ®istry::native(py, NativeAttr::ResolveCursorInputRoles)?, &[("model", model.clone())], )?; let producers = args.value("runtime_cursor_producer_names")?; @@ -145,7 +139,7 @@ fn compute_model_cursor_bounds<'py>( let replaces = args.flag("replaces_relation")?; if !full_refresh && !runtime_owned && !cursor_snapshot.is_none() { let empty = call_with( - &imported(py, CURSOR, "cursor_inputs_without_rows")?, + ®istry::native(py, NativeAttr::CursorInputsWithoutRows)?, &[("cursor_snapshot", cursor_snapshot.clone())], )? .getattr("input_names")?; @@ -155,7 +149,7 @@ fn compute_model_cursor_bounds<'py>( || history_redirected(&model, &cursor_snapshot)?) { let refusal = call_with( - &imported(py, CURSOR, "empty_input_rebuild_refusal")?, + ®istry::native(py, NativeAttr::EmptyInputRebuildRefusal)?, &[ ("model_name", model.getattr("name")?), ("input_names", empty.clone()), @@ -163,7 +157,7 @@ fn compute_model_cursor_bounds<'py>( ], )?; return Err(PyErr::from_value(call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &[ ("message", refusal.get_item(0)?), ("code", string(py, "S302")), @@ -173,7 +167,7 @@ fn compute_model_cursor_bounds<'py>( } if empty.is_truthy()? { return call_with( - &imported(py, CURSOR, "empty_cursor_window")?, + ®istry::native(py, NativeAttr::EmptyCursorWindow)?, &[ ("cursor_type", cursor_type), ("cursor_start", config(&model, "cursor_start")?), @@ -191,7 +185,7 @@ fn compute_model_cursor_bounds<'py>( cursor_snapshot.getattr("unavailable_watermark_tags")? }; return Err(PyErr::from_value(call_with( - &imported(py, CURSOR, "unreadable_cursor_inputs_error")?, + ®istry::native(py, NativeAttr::UnreadableCursorInputsError)?, &[ ("model_name", model.getattr("name")?), ("cursor_type", cursor_type), @@ -211,7 +205,7 @@ fn compute_model_cursor_bounds<'py>( } if replaces { cursor_snapshot = call_with( - &imported(py, CURSOR, "without_destination_cursor")?, + ®istry::native(py, NativeAttr::WithoutDestinationCursor)?, &[("cursor_snapshot", cursor_snapshot)], )?; } @@ -222,7 +216,7 @@ fn compute_model_cursor_bounds<'py>( py.None().into_bound(py) }; let policy = call_with( - &imported(py, MODELS, "MaximumStartPolicyInputs")?, + ®istry::class(py, RecordId::MaximumStartPolicyInputs)?, &[ ("config", start_config), ("invocation_time", context.getattr("invocation_time")?), @@ -234,7 +228,7 @@ fn compute_model_cursor_bounds<'py>( ], )?; let bounds = call_with( - &imported(py, NATIVE_MODULE, "compute_cursor_bounds")?, + ®istry::native(py, NativeAttr::ComputeCursorBounds)?, &[ ("cursor_snapshot", cursor_snapshot.clone()), ("cursor_type", cursor_type.clone()), @@ -255,7 +249,7 @@ fn compute_model_cursor_bounds<'py>( return Ok(bounds); } call_with( - &imported(py, NATIVE_MODULE, "resolve_future_cursor_safety")?, + ®istry::native(py, NativeAttr::ResolveFutureCursorSafety)?, &[ ("bounds", bounds), ("cursor_type", cursor_type), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs index cdc86a32b2..ca59b2a270 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs @@ -1,9 +1,9 @@ //! Selected-model inspection and untyped override conversion. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_resolution::values::{method, string}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -27,7 +27,7 @@ fn type_cursor_overrides<'py>( if overrides.is_none() || !overrides.getattr("has_untyped")?.is_truthy()? { return Ok(overrides); } - let get_config = imported(py, "sqlbuild._native", "get_config_str")?; + let get_config = registry::native(py, NativeAttr::GetConfigStr)?; let mut models: Vec<(String, String)> = Vec::new(); let selected = if args.passed("selected_models")? { args.value("selected_models")? @@ -68,12 +68,12 @@ fn type_cursor_overrides<'py>( } } let kind = override_kind(&models, integer_literals).map_err(|message| { - match imported(py, "sqlbuild._native", "PlannerInputError").and_then(|class| call_with(&class, &[ + match registry::error(py, ErrorId::PlannerInput).and_then(|class| call_with(&class, &[ ("message", string(py, &message)), ("code", string(py, "S303")), ("help", string(py, "select models with one cursor type, or set each type explicitly with --start-cursor-ts/--end-cursor-ts and --start-cursor-int/--end-cursor-int")), ])) { Ok(error) => PyErr::from_value(error), Err(error) => error } })?; - let kind = imported(py, "sqlbuild._native", "CursorType")?.call1((kind,))?; + let kind = registry::class(py, RecordId::CursorType)?.call1((kind,))?; method(&overrides, "typed_as", &[("cursor_type", kind)]) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs index fd73879b06..8d61ea65dc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs @@ -1,31 +1,31 @@ //! Model-level cursor safety policy overrides over project configuration. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; use pyo3::types::PyString; use pyo3::{pyfunction, wrap_pyfunction}; -const SPEC_MODELS: &str = "sqlbuild._native"; -const SPEC_TYPES: &str = "sqlbuild._native"; const POLICY_DISABLED: &str = "disabled"; /// The policy class, its distance field, and the model keys overriding distance and action. struct PolicyFields { - class: &'static str, + class: RecordId, distance: &'static str, distance_key: &'static str, action_key: &'static str, } const FUTURE_POLICY: PolicyFields = PolicyFields { - class: "FutureCursorsConfig", + class: RecordId::FutureCursorsConfig, distance: "max_distance", distance_key: "cursor_future_max_distance", action_key: "cursor_future_action", }; const START_POLICY: PolicyFields = PolicyFields { - class: "StartCursorsConfig", + class: RecordId::StartCursorsConfig, distance: "max_ahead", distance_key: "cursor_start_max_ahead", action_key: "cursor_start_max_action", @@ -37,7 +37,7 @@ fn resolve_policy<'py>( fields: &PolicyFields, ) -> PyResult> { let py = model.py(); - let class = imported(py, SPEC_MODELS, fields.class)?; + let class = registry::class(py, fields.class)?; let configured = if project_config.is_truthy()? { project_config.clone() } else { @@ -56,7 +56,7 @@ fn resolve_policy<'py>( } let mut action = configured.getattr("action")?; if raw_action.is_instance_of::() { - action = imported(py, SPEC_TYPES, "FutureCursorAction")?.call1((raw_action,))?; + action = registry::class(py, RecordId::FutureCursorAction)?.call1((raw_action,))?; } if distance.is_none() { return Ok(py.None().into_bound(py)); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs index 9f973de211..97421226ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs @@ -6,12 +6,16 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::{parse_value, sentinel_to_token}; use crate::bindings::_helpers::cursor::values::{local_date_time, replaced}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_resolution::cursor_bounds::duration; use crate::bindings::_helpers::planner_resolution::cursor_policies::{ future_cursor_config, start_cursor_config, }; use crate::bindings::_helpers::planner_resolution::cursor_safety::{replace, utc_clock}; -use crate::bindings::_helpers::planner_resolution::values::{NATIVE_MODULE, string}; +use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyString, PyTuple}; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; @@ -22,10 +26,6 @@ use sqlbuild_cursor::planner::main::rolling_window::rolling_window; use sqlbuild_cursor::planner::models::CursorEndClamp; use std::cmp::Ordering; -const MODELS: &str = "sqlbuild._native"; -const CURSOR_TYPES: &str = "sqlbuild.cursor_algebra.types"; -const CURSOR_MODELS: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS: &str = "sqlbuild._native"; const INCREMENTAL: &str = "incremental"; const MICROBATCH: &str = "microbatch"; const ROLLING_WINDOW: &str = "rolling_window"; @@ -65,17 +65,17 @@ pub(crate) struct CursorWindowInputs<'py> { fn native<'py>( py: Python<'py>, - name: &str, + name: NativeAttr, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, NATIVE_MODULE, name)?, pairs) + call_with(®istry::native(py, name)?, pairs) } /// One string-valued model configuration entry. pub(crate) fn config_str<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(model.py(), key)), @@ -90,7 +90,7 @@ pub(crate) fn config_bound<'py>( ) -> PyResult> { native( model.py(), - "get_config_cursor_bound", + NativeAttr::GetConfigCursorBound, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(model.py(), key)), @@ -132,7 +132,7 @@ fn token<'py>(py: Python<'py>, bound: &Bound<'py, PyAny>) -> PyResult(py: Python<'py>, name: &str) -> PyResult> { - imported(py, CURSOR_TYPES, "BoundSentinel")?.getattr(name) + host::entry(py, ExecutorHost::BoundSentinel)?.getattr(name) } /// `bounds` clamped so a concrete window never reaches the exclusive configured `cursor_end`. @@ -172,7 +172,7 @@ fn timestamp_value<'py>(clock: &Bound<'py, PyAny>, scalar: &Scalar) -> PyResult< return Ok(clock.py().None().into_bound(clock.py())); }; call_with( - &imported(clock.py(), CURSOR_MODELS, "TimestampValue")?, + ®istry::class(clock.py(), RecordId::TimestampValue)?, &[("value", replaced(clock, ×tamp.local, timestamp.fold)?)], ) } @@ -186,7 +186,7 @@ fn rolling_window_bounds<'py>( let grain = config_str(model, "cursor_grain")?; let lookback = native( py, - "resolve_microbatch_lookback", + NativeAttr::ResolveMicrobatchLookback, &[("model", model.clone()), ("effective_grain", grain.clone())], )?; let lookback = if lookback.is_truthy()? { @@ -225,7 +225,7 @@ fn rolling_window_bounds<'py>( }; let (start, end) = session.checked(rolling_window(&clock, &span, grain))?; let mut bounds = call_with( - &imported(py, MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[ ("start", timestamp_value(&moment, &start)?), ("end", timestamp_value(&moment, &end)?), @@ -257,7 +257,7 @@ fn apply_future_safety<'py>( } native( py, - "resolve_future_cursor_safety", + NativeAttr::ResolveFutureCursorSafety, &[ ("bounds", bounds), ("cursor_type", typing.0.clone()), @@ -282,7 +282,7 @@ fn bounded_override<'py>( ) -> PyResult> { native( model.py(), - "resolve_bounded_cursor_override", + NativeAttr::ResolveBoundedCursorOverride, &[ ("start_cursor_override", inputs.overrides.0.clone()), ("end_cursor_override", inputs.overrides.1.clone()), @@ -314,7 +314,7 @@ fn snapshot_bounds<'py>( ) -> PyResult> { let py = model.py(); let policy = call_with( - &imported(py, MODELS, "MaximumStartPolicyInputs")?, + ®istry::class(py, RecordId::MaximumStartPolicyInputs)?, &[ ("config", inputs.policies.1.clone()), ("invocation_time", inputs.invocation_time.clone()), @@ -327,7 +327,7 @@ fn snapshot_bounds<'py>( )?; let bounds = native( py, - "compute_cursor_bounds", + NativeAttr::ComputeCursorBounds, &[ ("cursor_snapshot", request.cursor_snapshot.clone()), ("cursor_type", request.typing.0.clone()), @@ -362,7 +362,7 @@ fn model_cursor_snapshot<'py>( fn without_destination<'py>(cursor_snapshot: Bound<'py, PyAny>) -> PyResult> { native( cursor_snapshot.py(), - "without_destination_cursor", + NativeAttr::WithoutDestinationCursor, &[("cursor_snapshot", cursor_snapshot)], ) } @@ -451,7 +451,7 @@ pub(crate) fn microbatch_range<'py>( } let effective_grain = native( py, - "resolve_effective_timestamp_grain", + NativeAttr::ResolveEffectiveTimestampGrain, &[ ("cursor_type", cursor_type.clone()), ("downstream_grain", config_str(model, "cursor_grain")?), @@ -462,7 +462,7 @@ pub(crate) fn microbatch_range<'py>( if cursor_type.eq(TIMESTAMP)? && !effective_grain.is_none() { cursor_snapshot = native( py, - "normalize_cursor_snapshot_grain", + NativeAttr::NormalizeCursorSnapshotGrain, &[ ("cursor_snapshot", cursor_snapshot), ("cursor_type", cursor_type.clone()), @@ -472,7 +472,7 @@ pub(crate) fn microbatch_range<'py>( } let lookback = native( py, - "resolve_microbatch_lookback", + NativeAttr::ResolveMicrobatchLookback, &[ ("model", model.clone()), ("effective_grain", effective_grain.clone()), @@ -513,7 +513,7 @@ pub(crate) fn empty_cursor_inputs<'py>( } let decision = native( py, - "cursor_inputs_without_rows", + NativeAttr::CursorInputsWithoutRows, &[("cursor_snapshot", cursor_snapshot)], )?; Ok(( @@ -534,9 +534,7 @@ pub(crate) fn cursor_safety_configs<'py>( } fn compile_input_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, COMPILE_EXCEPTIONS, "CompileInputError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::CompileInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs index 104c8965de..c1259754d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs @@ -3,15 +3,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_bounds::duration; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use sqlbuild_cursor::algebra::models::{DurationSpan, Grain, Scalar}; use sqlbuild_cursor::planner::main::safety_horizon::safety_horizon; -pub(super) const MODELS: &str = "sqlbuild._native"; -pub(super) const EXCEPTIONS: &str = "sqlbuild._native"; - -pub(super) fn safety_error(py: Python<'_>, class: &str, message: &str) -> PyErr { - match imported(py, EXCEPTIONS, class).and_then(|class| class.call1((message,))) { +pub(super) fn safety_error(py: Python<'_>, class: ErrorId, message: &str) -> PyErr { + match registry::error(py, class).and_then(|class| class.call1((message,))) { Ok(value) => PyErr::from_value(value), Err(error) => error, } @@ -31,9 +30,9 @@ pub(super) fn safety_duration(value: &Bound<'_, PyAny>, future: bool) -> PyResul }); } let (class, field) = if future { - ("FutureCursorSafetyError", "future.max_distance") + (ErrorId::FutureCursorSafety, "future.max_distance") } else { - ("MaximumAutomaticStartError", "start.max_ahead") + (ErrorId::MaximumAutomaticStart, "start.max_ahead") }; Err(safety_error( value.py(), @@ -45,8 +44,8 @@ pub(super) fn safety_duration(value: &Bound<'_, PyAny>, future: bool) -> PyResul pub(super) fn timestamp_bound(value: &Bound<'_, PyAny>) -> PyResult { let py = value.py(); Ok( - value.is_instance(&imported(py, "sqlbuild._native", "DateValue")?)? - || value.is_instance(&imported(py, "sqlbuild._native", "TimestampValue")?)?, + value.is_instance(®istry::class(py, RecordId::DateValue)?)? + || value.is_instance(®istry::class(py, RecordId::TimestampValue)?)?, ) } @@ -63,7 +62,7 @@ pub(super) fn horizon<'py>( let py = session.py(); let (clock, grain) = clock_and_grain; let mut clock = utc_clock(clock)?; - let date_only = discovered.is_instance(&imported(py, "sqlbuild._native", "DateValue")?)?; + let date_only = discovered.is_instance(®istry::class(py, RecordId::DateValue)?)?; if date_only || discovered.getattr("value")?.getattr("tzinfo")?.is_none() { clock = call_with( &clock.getattr("replace")?, @@ -71,7 +70,7 @@ pub(super) fn horizon<'py>( )?; } let clock = call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", clock)], )?; let grain = if grain.is_truthy()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs index 71d874c6bd..05fd7b0b17 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs @@ -1,8 +1,10 @@ //! Prominent warnings for applied future-end and automatic-start cursor safety. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::scalars::render_value; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; use pyo3::types::{PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -78,12 +80,12 @@ pub(crate) fn with_cursor_safety_warnings<'py>( ] { let Some(message) = message else { continue }; all.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", model_name.clone()), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, ), ("code", string(py, code)), ("message", string(py, &message)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs index 29be985ba0..096a31951d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs @@ -4,14 +4,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_cursor::algebra::main::floor_to_grain::floor_to_grain; use sqlbuild_cursor::planner::main::empty_inputs::empty_inputs; -const MODELS: &str = "sqlbuild._native"; - #[pyfunction] #[pyo3(signature = (*, empty_input_names, waits_for_every_input, inputs_with_rows, has_window_start))] fn decide_empty_cursor_inputs<'py>( @@ -21,7 +21,7 @@ fn decide_empty_cursor_inputs<'py>( has_window_start: bool, ) -> PyResult> { let py = empty_input_names.py(); - let class = imported(py, MODELS, "EmptyCursorInputDecision")?; + let class = registry::class(py, RecordId::EmptyCursorInputDecision)?; if !empty_input_names.is_truthy()? || !empty_inputs(waits_for_every_input, inputs_with_rows, has_window_start) { @@ -48,7 +48,7 @@ fn cursor_inputs_without_rows<'py>( .getattr("unreadable_input_names")? .is_truthy()? { - return imported(cursor_snapshot.py(), MODELS, "EmptyCursorInputDecision")?.call0(); + return registry::class(cursor_snapshot.py(), RecordId::EmptyCursorInputDecision)?.call0(); } let rows = cursor_snapshot.getattr("upstream_maxes")?.is_truthy()? || cursor_snapshot @@ -94,12 +94,12 @@ fn empty_cursor_window<'py>( let mut bound = parse_value(&raw, &kind)?; if cursor_start.is_none() && !kind.eq("integer")? { bound = call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", bound.getattr("value")?)], )?; } call_with( - &imported(py, MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[("start", bound.clone()), ("end", bound)], ) } @@ -143,7 +143,7 @@ fn unreadable_cursor_inputs_error<'py>( input_names: Vec, ) -> PyResult> { call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &[ ( "message", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs index 099ce98ded..81ac8cc755 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs @@ -1,12 +1,12 @@ //! Future cursor safety evidence and exception boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_safety::{ - MODELS, horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, + horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -96,7 +96,7 @@ fn resolve_future_cursor_safety<'py>( if config.getattr("action")?.eq("error")? { return Err(safety_error( py, - "FutureCursorSafetyError", + ErrorId::FutureCursorSafety, &format!( "future cursor safety limit exceeded: effective bounds ['{}', '{}') exceed maximum allowed bounds ['{}', '{}') (cursors.future.max_distance={})", session.checked(render(&typed_start, &session))?, @@ -120,14 +120,14 @@ fn resolve_future_cursor_safety<'py>( } }; let safety = call_with( - &imported(py, MODELS, "FutureCursorSafetyEvidence")?, + ®istry::class(py, RecordId::FutureCursorSafetyEvidence)?, &[ ("action", config.getattr("action")?), ("max_distance", distance), ( "invocation_time", call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", utc_clock(&clock)?)], )?, ), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs index 6d078b4098..39b8a0efe3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs @@ -1,16 +1,16 @@ //! Maximum automatic-start recovery and evidence boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_bounds::{ optional_scalar, required_scalar, scalars, }; use crate::bindings::_helpers::planner_resolution::cursor_safety::{ - EXCEPTIONS, MODELS, horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, + horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, }; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -114,7 +114,7 @@ fn apply_maximum_start_policy<'py>( if !timestamp_bound(&physical)? { return Err(safety_error( py, - "MaximumAutomaticStartError", + ErrorId::MaximumAutomaticStart, "automatic start requires a timestamp cursor", )); } @@ -172,7 +172,7 @@ fn apply_maximum_start_policy<'py>( replay.push((key, args.value(key)?)); } call_with( - &imported(py, "sqlbuild._native", "apply_typed_cursor_replay_policy")?, + ®istry::native(py, NativeAttr::ApplyTypedCursorReplayPolicy)?, &replay, )? } else { @@ -181,14 +181,14 @@ fn apply_maximum_start_policy<'py>( let relation = snapshot.getattr("target_relation")?; let column = snapshot.getattr("destination_cursor_column")?; let evidence = call_with( - &imported(py, MODELS, "MaximumStartSafetyEvidence")?, + ®istry::class(py, RecordId::MaximumStartSafetyEvidence)?, &[ ("action", config.getattr("action")?), ("max_ahead", config.getattr("max_ahead")?), ( "invocation_time", call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", utc_clock(&clock)?)], )?, ), @@ -219,7 +219,7 @@ fn apply_maximum_start_policy<'py>( )?; let error = |non_idempotent: bool| -> PyResult> { Err(PyErr::from_value(call_with( - &imported(py, EXCEPTIONS, "MaximumAutomaticStartError")?, + ®istry::error(py, ErrorId::MaximumAutomaticStart)?, &[ ( "message", @@ -248,7 +248,7 @@ fn apply_maximum_start_policy<'py>( fn rendered(value: &Bound<'_, PyAny>) -> PyResult { call_with( - &imported(value.py(), "sqlbuild._native", "cursor_render")?, + ®istry::native(value.py(), NativeAttr::CursorRender)?, &[("value", value.clone())], )? .extract() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs index 9e0b106a42..3496d94b0c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs @@ -4,14 +4,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; -use crate::bindings::_helpers::planner_resolution::values::{NATIVE_MODULE, string}; +use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyList, PyString, PyTuple}; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; -const HOOK_MODULE: &str = "sqlbuild._native"; -const ERROR_MODULE: &str = "sqlbuild._native"; - struct Resolution<'py> { adapter: Bound<'py, PyAny>, context: Bound<'py, PyAny>, @@ -35,7 +34,7 @@ impl<'py> Resolution<'py> { } source.extend_from_slice(cursor); let mut resolved = call_with( - &imported(py, NATIVE_MODULE, "resolve_source_references")?, + ®istry::native(py, NativeAttr::ResolveSourceReferences)?, &source, )?; let mut refs = vec![("query_sql", resolved), ("adapter", self.adapter.clone())]; @@ -44,11 +43,11 @@ impl<'py> Resolution<'py> { } refs.extend_from_slice(cursor); resolved = call_with( - &imported(py, NATIVE_MODULE, "resolve_ref_references")?, + ®istry::native(py, NativeAttr::ResolveRefReferences)?, &refs, )?; resolved = call_with( - &imported(py, NATIVE_MODULE, "resolve_dbt_ref_references")?, + ®istry::native(py, NativeAttr::ResolveDbtRefReferences)?, &[ ("query_sql", resolved), ("external_sql_reference_resolver", self.external.clone()), @@ -61,11 +60,11 @@ impl<'py> Resolution<'py> { PyDict::new(py).into_any() }; for name in [ - "resolve_udf_references", - "resolve_table_function_references", + NativeAttr::ResolveUdfReferences, + NativeAttr::ResolveTableFunctionReferences, ] { resolved = call_with( - &imported(py, NATIVE_MODULE, name)?, + ®istry::native(py, name)?, &[ ("query_sql", resolved), ("function_locations", locations.clone()), @@ -78,11 +77,7 @@ impl<'py> Resolution<'py> { fn assert_resolved(&self, sql: &Bound<'py, PyAny>, label: &str) -> PyResult<()> { call_with( - &imported( - self.adapter.py(), - NATIVE_MODULE, - "assert_no_unresolved_sql_markers", - )?, + ®istry::native(self.adapter.py(), NativeAttr::AssertNoUnresolvedSqlMarkers)?, &[ ("sql", sql.clone()), ( @@ -114,7 +109,7 @@ impl<'py> Resolution<'py> { fn hook(&self, value: Bound<'py, PyAny>, label: &str) -> PyResult> { let py = value.py(); - if value.is_instance(&imported(py, HOOK_MODULE, "SqlHookEntry")?)? { + if value.is_instance(®istry::class(py, RecordId::SqlHookEntry)?)? { let kwargs = PyDict::new(py); kwargs.set_item( "statement", @@ -172,7 +167,7 @@ fn resolve_model_sql<'py>( external: args.value("external_sql_reference_resolver")?, }; let cursor_type = call_with( - &imported(py, NATIVE_MODULE, "get_config_str")?, + ®istry::native(py, NativeAttr::GetConfigStr)?, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(py, "cursor_type")), @@ -180,7 +175,7 @@ fn resolve_model_sql<'py>( )?; let overrides = args.value("cursor_overrides")?; let bounds = call_with( - &imported(py, NATIVE_MODULE, "compute_model_cursor_bounds")?, + ®istry::native(py, NativeAttr::ComputeModelCursorBounds)?, &[ ("model", model.clone()), ("snapshot", args.value("snapshot")?), @@ -208,7 +203,7 @@ fn resolve_model_sql<'py>( ], )?; let roles = call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_input_roles")?, + ®istry::native(py, NativeAttr::ResolveCursorInputRoles)?, &[("model", model.clone())], )?; let inclusive = append_cursor_inclusive(&model)?; @@ -222,7 +217,7 @@ fn resolve_model_sql<'py>( ], )?; let intrinsics = call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_intrinsics")?, + ®istry::native(py, NativeAttr::ResolveCursorIntrinsics)?, &[("sql", sql.clone())], )?; let name: String = model.getattr("name")?.extract()?; @@ -248,7 +243,7 @@ fn resolve_model_sql<'py>( fn append_cursor_inclusive<'py>(model: &Bound<'py, PyAny>) -> PyResult> { let py = model.py(); let strategy = call_with( - &imported(py, NATIVE_MODULE, "get_config_str")?, + ®istry::native(py, NativeAttr::GetConfigStr)?, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(py, "incremental_strategy")), @@ -286,7 +281,7 @@ fn intrinsic_bounds<'py>( let start_sql = literal("start")?; let end_sql = literal("end")?; call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_intrinsics")?, + ®istry::native(py, NativeAttr::ResolveCursorIntrinsics)?, &[ ("sql", sql.clone()), ("start_sql", start_sql), @@ -297,7 +292,7 @@ fn intrinsic_bounds<'py>( } fn unresolved_bounds_error(py: Python<'_>, name: &str, cause: Option<&str>) -> PyResult { - let error = imported(py, ERROR_MODULE, "PlannerInputError")?; + let error = registry::error(py, ErrorId::PlannerInput)?; let kwargs = PyDict::new(py); let message = if let Some(cause) = cause { kwargs.set_item("help", "rebuild it with an explicit cursor interval (--start-cursor-ts and --end-cursor-ts, or the -int forms), or make it a microbatch model so a full rebuild can run batch by batch")?; @@ -347,10 +342,7 @@ fn resolve_function_sql<'py>( } let resolution = Resolution { adapter: args.value("adapter")?, - context: call_with( - &imported(py, "sqlbuild._native", "ModelPlanContext")?, - &context, - )?, + context: call_with(®istry::class(py, RecordId::ModelPlanContext)?, &context)?, external: py.None().into_bound(py), }; let function = args.value("function")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs index 24d868c4ff..e212bbf96f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs @@ -1,20 +1,17 @@ //! Reference lookup and adapter callbacks for planner SQL. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; -use crate::bindings::_helpers::planner_resolution::values::{ - NATIVE_MODULE, cursor_wrap, method, string, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_resolution::values::{cursor_wrap, method, string}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_sqltext::resolution::main::{ref_alias::ref_alias, reference_spans::reference_spans}; -const RELATION_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; - #[pyfunction] #[pyo3(signature = (**kwargs))] fn resolve_ref_references(py: Python<'_>, kwargs: Option>) -> PyResult { @@ -49,11 +46,7 @@ fn resolve_ref_references(py: Python<'_>, kwargs: Option>) -> output.push_str(&sql[span.start..span.end]); } else { let relation: String = call_with( - &imported( - py, - RELATION_MODULE, - "resolve_relation_location_qualified_name", - )?, + &host::entry(py, AdapterHost::ResolveRelationLocationQualifiedName)?, &[("adapter", args.value("adapter")?), ("location", target)], )? .extract()?; @@ -124,7 +117,7 @@ fn resolve_function_calls( if spans.is_empty() { return Ok(query_sql.to_owned()); } - let scanner_type = imported(py, NATIVE_MODULE, "SqlCodeScanner")?; + let scanner_type = registry::native(py, NativeAttr::SqlCodeScanner)?; let scan = scanner_type.call1((query_sql, py.None(), "SQL reference resolution"))?; let mut output = String::new(); let mut last = 0; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs index 4898e1db42..8c09e4e2b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs @@ -2,7 +2,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_resolution::values::{cursor_wrap, method, string}; -use crate::bindings::_helpers::planner_strategy::values::{PLANNER_EXCEPTIONS_MODULE, input_error}; +use crate::bindings::_helpers::planner_strategy::values::input_error; +use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::references::source_relations::render_source_relation; use crate::bindings::_helpers::type_system::equality::types_equal; use pyo3::IntoPyObject; @@ -16,12 +17,7 @@ use sqlbuild_sqltext::resolution::main::{ }; fn source_error(py: Python<'_>, message: &str, code: &str) -> PyErr { - input_error( - py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), - message, - Some(code), - ) + input_error(py, ErrorId::PlannerInput, message, Some(code)) } fn rows(value: &Bound<'_, PyAny>) -> PyResult)>> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs index 47d885cc62..489e2b8256 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs @@ -1,15 +1,13 @@ //! Boundary calls shared by reference and source resolution. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; use sqlbuild_sqltext::resolution::main::cursor_subquery::cursor_subquery; use sqlbuild_sqltext::resolution::models::CursorFilter; -pub(super) const NATIVE_MODULE: &str = "sqlbuild._native"; - pub(super) fn string<'py>(py: Python<'py>, text: &str) -> Bound<'py, PyAny> { PyString::new(py, text).into_any() } @@ -44,7 +42,7 @@ pub(super) fn cursor_wrap( let cursor_type = arguments.value("cursor_type")?; let literal = |bound: &str| -> PyResult { let token = call_with( - &imported(py, NATIVE_MODULE, "cursor_sentinel_to_token")?, + ®istry::native(py, NativeAttr::CursorSentinelToToken)?, &[("sentinel", bounds.getattr(bound)?)], )?; method( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs index edda0220c6..e0c5bbdedb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs @@ -1,13 +1,13 @@ //! Scenario artifact dataclasses and adapter identifier callbacks. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_HASH_COLLISION, SCENARIO_PLAN_INVALID_HASH_PREFIX, SCENARIO_PLAN_RELATION_COLLISION, }; -use crate::bindings::_helpers::planner_scenario::values::{MODELS, TYPES, error, string}; +use crate::bindings::_helpers::planner_scenario::values::{error, string}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::relation_naming::fitting::fitted_logical_name; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -151,7 +151,7 @@ pub(crate) fn build_scenario_artifact_name( identifier_limit: i64, ) -> PyResult { let py = kind.py(); - let kind: String = imported(py, TYPES, "ScenarioArtifactKind")? + let kind: String = registry::class(py, RecordId::ScenarioArtifactKind)? .call1((kind,))? .getattr("value")? .extract()?; @@ -167,7 +167,7 @@ pub(crate) fn parse_scenario_artifact_physical_name<'py>( return Ok(py.None().into_bound(py)); }; call_with( - &imported(py, "sqlbuild._native", "ParsedScenarioArtifactName")?, + ®istry::class(py, RecordId::ParsedScenarioArtifactName)?, &[ ("hash_prefix", string(py, hash)), ("kind", string(py, kind)), @@ -221,7 +221,7 @@ pub(crate) fn build_scenario_relation_map<'py>( normalize.call1((&physical,))? }; if let Some(existing) = seen.get_item(&normalized)? { - let kind = imported(py, TYPES, "ScenarioArtifactKind")?; + let kind = registry::class(py, RecordId::ScenarioArtifactKind)?; let old_kind: String = kind .call1((existing.getattr("kind")?,))? .getattr("value")? @@ -243,7 +243,7 @@ pub(crate) fn build_scenario_relation_map<'py>( } seen.set_item(normalized, &artifact)?; artifacts.push(call_with( - &imported(py, "sqlbuild._native", "ScenarioArtifactName")?, + ®istry::class(py, RecordId::ScenarioArtifactName)?, &[ ("identity", artifact), ("physical_name", string(py, &physical)), @@ -251,7 +251,7 @@ pub(crate) fn build_scenario_relation_map<'py>( )?); } call_with( - &imported(py, MODELS, "ScenarioRelationMap")?, + ®istry::class(py, RecordId::ScenarioRelationMap)?, &[ ("scenario_name", args.value("scenario_name")?), ("hash_prefix", args.value("hash_prefix")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs index 9a24f89aa1..86669773d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs @@ -1,17 +1,15 @@ //! Assemble CLI scenario plans while retaining adapter and executor boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_GRAPH_VALIDATION; use crate::bindings::_helpers::planner_scenario::graph::plan_scenario_graph; use crate::bindings::_helpers::planner_scenario::relations::build_scenario_relation_plan; -use crate::bindings::_helpers::planner_scenario::values::{TYPES, string}; +use crate::bindings::_helpers::planner_scenario::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -const ARTIFACTS: &str = "sqlbuild._native"; - #[pyfunction] #[pyo3(signature = (**kwargs))] pub(crate) fn build_cli_scenario_plan<'py>( @@ -39,7 +37,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( let (graph, warnings) = plan_scenario_graph(&scenario, &project, true, None)?; raise_for_warnings(warnings.as_any())?; let hashes = call_with( - &imported(py, ARTIFACTS, "build_scenario_hash_index")?, + ®istry::native(py, NativeAttr::BuildScenarioHashIndex)?, &[ ("project_name", args.value("project_name")?), ("scenarios", project.getattr("sql_scenarios")?), @@ -59,11 +57,11 @@ pub(crate) fn build_cli_scenario_plan<'py>( ] { for name in graph.getattr(field)?.try_iter()? { identities.push(call_with( - &imported(py, "sqlbuild._native", "ScenarioArtifactIdentity")?, + ®istry::class(py, RecordId::ScenarioArtifactIdentity)?, &[ ( "kind", - imported(py, TYPES, "ScenarioArtifactKind")?.getattr(kind)?, + registry::class(py, RecordId::ScenarioArtifactKind)?.getattr(kind)?, ), ("logical_name", name?), ], @@ -71,7 +69,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( } } let map = call_with( - &imported(py, ARTIFACTS, "build_scenario_relation_map")?, + ®istry::native(py, NativeAttr::BuildScenarioRelationMap)?, &[ ("scenario_name", scenario.getattr("name")?), ("hash_prefix", hashes.get_item(scenario.getattr("name")?)?), @@ -99,7 +97,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( } let relations = build_scenario_relation_plan(py, Some(relation_args))?; let result = call_with( - &imported(py, "sqlbuild._native", "build_scenario_execution_plan")?, + ®istry::native(py, NativeAttr::BuildScenarioExecutionPlan)?, &[ ("scenario", scenario), ("project", project), @@ -142,7 +140,7 @@ fn namespace<'py>( fn raise_for_warnings(warnings: &Bound<'_, PyAny>) -> PyResult<()> { let py = warnings.py(); - let severity = imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?; + let severity = registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?; let mut messages: Vec = Vec::new(); let mut code = None; for warning in warnings.try_iter()? { @@ -161,7 +159,7 @@ fn raise_for_warnings(warnings: &Bound<'_, PyAny>) -> PyResult<()> { } if let Some(code) = code { return Err(PyErr::from_value(call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &[ ("message", string(py, &messages.join("\n"))), ("code", code), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs index 59729c0370..ac6fa6bdaf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs @@ -1,15 +1,13 @@ //! Existing seed and function plan dataclass construction. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::compiled_project::loop_classes::by_attribute; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_UNKNOWN_SEED; use crate::bindings::_helpers::planner_scenario::{ - models::SqlContext, - sql::required_target, - values::{MODELS, error}, + models::SqlContext, sql::required_target, values::error, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -44,7 +42,7 @@ pub(super) fn seed_entries<'py>( "seed", )?; result.push(call_with( - &imported(py, MODELS, "SeedPlanEntry")?, + ®istry::class(py, RecordId::SeedPlanEntry)?, &[ ("key", seed.getattr("key")?), ("name", seed.getattr("name")?), @@ -55,7 +53,7 @@ pub(super) fn seed_entries<'py>( ), ( "columns", - imported(py, "sqlbuild._native", "extract_seed_columns")?.call1((&seed,))?, + registry::native(py, NativeAttr::ExtractSeedColumns)?.call1((&seed,))?, ), ( "csv_settings", @@ -75,7 +73,7 @@ pub(super) fn function_entry<'py>( ) -> PyResult> { let py = function.py(); let body = call_with( - &imported(py, "sqlbuild._native", "resolve_function_sql")?, + ®istry::native(py, NativeAttr::ResolveFunctionSql)?, &[ ("adapter", context.adapter.clone()), ("function", function.clone()), @@ -110,12 +108,8 @@ pub(super) fn function_entry<'py>( fields.push(("body_sql", body)); fields.push(( "fingerprint_query_sql", - imported( - py, - "sqlbuild._native", - "build_compiled_function_fingerprint_sql", - )? - .call1((function,))?, + registry::native(py, NativeAttr::BuildCompiledFunctionFingerprintSql)? + .call1((function,))?, )); for name in [ "fingerprint_destination", @@ -128,7 +122,7 @@ pub(super) fn function_entry<'py>( ] { fields.push((name, function.getattr(name)?)); } - call_with(&imported(py, MODELS, "FunctionPlanEntry")?, &fields) + call_with(®istry::class(py, RecordId::FunctionPlanEntry)?, &fields) } #[pyfunction] diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs index f87b466239..ba484b2854 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs @@ -10,8 +10,10 @@ use crate::bindings::_helpers::planner_scenario::{ fixtures::{extract_fixtures, fixture_plans}, models::SqlContext, sql::{required_target, resolve_model_dbt_refs, resolve_sql, source_label}, - values::{MODELS, error, string}, + values::{error, string}, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; @@ -54,7 +56,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( let snapshot = if snapshot.is_truthy()? { snapshot } else { - imported(py, "sqlbuild._native", "WarehouseSnapshot")?.call0()? + registry::class(py, RecordId::WarehouseSnapshot)?.call0()? }; let columns = args.value("source_warehouse_columns")?; let columns = if columns.is_truthy()? { @@ -70,12 +72,12 @@ pub(crate) fn build_scenario_execution_plan<'py>( }; let models = by_attribute(&project.getattr("models")?, "name")?; let functions = by_attribute(&project.getattr("functions")?, "key")?; - let locations = imported(py, "sqlbuild._native", "build_function_locations")? + let locations = registry::native(py, NativeAttr::BuildFunctionLocations)? .call1((project.getattr("functions")?,))?; let upstream = - imported(py, "sqlbuild._native", "build_execution_upstream_deps")?.call1((&project,))?; + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((&project,))?; let ordered = call_with( - &imported(py, "sqlbuild._native", "topologically_order_keys")?, + ®istry::native(py, NativeAttr::TopologicallyOrderKeys)?, &[("upstream", upstream)], )?; let mut model_keys: Vec> = Vec::new(); @@ -121,14 +123,14 @@ pub(crate) fn build_scenario_execution_plan<'py>( &model.getattr("query_sql")?.extract::()?, &context.relations, )?; - let record = imported(py, "sqlbuild._native", "compiled_model_record")?.call1((&model,))?; + let record = registry::native(py, NativeAttr::CompiledModelRecord)?.call1((&model,))?; let changes = PyDict::new(py); changes.set_item("destination", destination)?; changes.set_item("query_sql", query)?; let model = imported(py, "dataclasses", "replace")?.call((record,), Some(&changes))?; let model_context = model_context(&project, &context, (&models, &locations, &columns))?; let result = call_with( - &imported(py, "sqlbuild._native", "plan_model")?, + ®istry::native(py, NativeAttr::PlanModel)?, &[ ("model", model), ("snapshot", snapshot.clone()), @@ -145,7 +147,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( ("full_refresh", PyBool::new(py, true).to_owned().into_any()), ( "cursor_overrides", - imported(py, "sqlbuild._native", "CursorOverridePair")?.call0()?, + registry::class(py, RecordId::CursorOverridePair)?.call0()?, ), ], )?; @@ -156,7 +158,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( } let (expected, assertions) = expectations(&scenario, &context)?; let plan = call_with( - &imported(py, MODELS, "ScenarioExecutionPlan")?, + ®istry::class(py, RecordId::ScenarioExecutionPlan)?, &[ ("key", scenario.getattr("key")?), ("name", scenario.getattr("name")?), @@ -206,7 +208,7 @@ fn complete_fixtures<'py>( .map(|key| key.getattr("name")) .collect::>>()?; let completion = call_with( - &imported(py, "sqlbuild._native", "build_relation_fixture_completion")?, + ®istry::native(py, NativeAttr::BuildRelationFixtureCompletion)?, &[ ("project", project.clone()), ("adapter", context.adapter.clone()), @@ -255,7 +257,7 @@ fn model_context<'py>( let (models, locations, columns) = inputs; let py = project.py(); call_with( - &imported(py, "sqlbuild._native", "ModelPlanContext")?, + ®istry::class(py, RecordId::ModelPlanContext)?, &[ ( "model_locations", @@ -298,7 +300,7 @@ fn expectations<'py>( false, )?; expected.push(call_with( - &imported(py, MODELS, "ScenarioExpectedExpectationPlan")?, + ®istry::class(py, RecordId::ScenarioExpectedExpectationPlan)?, &[ ("model_name", string(py, name)), ("actual_destination", target), @@ -316,7 +318,7 @@ fn expectations<'py>( false, )?; assertions.push(call_with( - &imported(py, MODELS, "ScenarioAssertionExpectationPlan")?, + ®istry::class(py, RecordId::ScenarioAssertionExpectationPlan)?, &[ ( "name", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs index 8d21bc9df3..3ed53c053d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs @@ -1,14 +1,14 @@ //! Materialize fixture SQL plans while preserving adapter and completion boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_MISSING_FIXTURE_SQL; use crate::bindings::_helpers::planner_scenario::{ models::SqlContext, sql::{required_target, resolve_sql, source_label}, - values::{MODELS, TYPES, error, string}, + values::{error, string}, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -90,11 +90,11 @@ pub(super) fn fixture_plans<'py>( format!("WITH {} {sql}", helpers.join(", ")) }; result.push(call_with( - &imported(py, MODELS, "ScenarioFixturePlan")?, + ®istry::class(py, RecordId::ScenarioFixturePlan)?, &[ ( "kind", - imported(py, TYPES, "ScenarioArtifactKind")?.call1((kind,))?, + registry::class(py, RecordId::ScenarioArtifactKind)?.call1((kind,))?, ), ("logical_name", string(py, &name)), ("destination", target), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs index d0ec52b3ce..4be6a925ee 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs @@ -1,9 +1,11 @@ //! Convert project views into pure scenario dependency facts. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::graph::keys::{KeyCache, native_key}; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_GRAPH_VALIDATION; use crate::bindings::_helpers::planner_scenario::values::string; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, pyfunction}; use pyo3::types::PyTuple; use sqlbuild_analysis::planner_scenario::main::graph::graph; @@ -55,7 +57,7 @@ pub(crate) fn plan_scenario_graph<'py>( input.targets.sort(); input.assertions.sort(); let plan = call_with( - &imported(py, "sqlbuild._native", "ScenarioGraphPlan")?, + ®istry::class(py, RecordId::ScenarioGraphPlan)?, &[ ("key", scenario.getattr("key")?), ("name", scenario.getattr("name")?), @@ -97,7 +99,7 @@ pub(crate) fn plan_scenario_graph<'py>( ("model_name", py.None().into_bound(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, ), ("message", string(py, &message)), ]; @@ -105,7 +107,7 @@ pub(crate) fn plan_scenario_graph<'py>( values.push(("code", string(py, SCENARIO_PLAN_GRAPH_VALIDATION))); } warnings.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &values, )?); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs index cb8e14f3d1..07a8992cd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs @@ -7,7 +7,9 @@ use crate::bindings::_helpers::planner_scenario::constants::{REF, SOURCE}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_INTERNAL, SCENARIO_PLAN_MISSING_RELATION_TARGET, }; -use crate::bindings::_helpers::planner_scenario::values::{MODELS, error}; +use crate::bindings::_helpers::planner_scenario::values::error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods}; @@ -100,7 +102,7 @@ pub(crate) fn build_scenario_relation_plan<'py>( seed_fixtures.set_item(name, target)?; } call_with( - &imported(py, MODELS, "ScenarioRelationPlan")?, + ®istry::class(py, RecordId::ScenarioRelationPlan)?, &[ ("scenario_name", graph.getattr("name")?), ("relation_map", args.value("relation_map")?), @@ -142,7 +144,7 @@ fn target_for_artifact<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ("database", database), ("schema", schema), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs index 5fceaafc9c..32528173d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs @@ -1,8 +1,6 @@ //! Resolve scenario SQL through the shared native scanner and adapter callbacks. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_MISSING_RELATION_TARGET, SCENARIO_PLAN_UNRESOLVED_RELATION_MARKER, }; @@ -10,6 +8,8 @@ use crate::bindings::_helpers::planner_scenario::{ models::SqlContext, values::{error, string}, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyStringMethods}; @@ -46,7 +46,7 @@ pub(super) fn resolve_sql( result = replace_markers(&result, kind, Some(syntax.clone().into()), |name, marker| { let target = locations.call_method1("get", (name,))?; let target = if target.is_none() {target} else if kind == super::constants::SOURCE_MARKER { - call_with(&imported(py, "sqlbuild._native", "render_source_relation")?, &[("entry", target), ("adapter", context.adapter.clone())])? + call_with(®istry::native(py, NativeAttr::RenderSourceRelation)?, &[("entry", target), ("adapter", context.adapter.clone())])? } else {target.getattr("qualified_name")?}; if target.is_none() { let marker = string(py, marker).repr()?.to_str()?.to_owned(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs index 57b3d25c93..ac7486a74d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs @@ -1,12 +1,11 @@ //! Existing Python model constructors and scenario diagnostics. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyErr, PyResult, Python}; use pyo3::types::PyString; -pub(super) const MODELS: &str = "sqlbuild._native"; -pub(super) const TYPES: &str = "sqlbuild._native"; - pub(super) fn string<'py>(py: Python<'py>, text: &str) -> Bound<'py, PyAny> { PyString::new(py, text).into_any() } @@ -22,7 +21,7 @@ pub(super) fn error( args.push(("help", string(py, help))); } Ok(PyErr::from_value(call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &args, )?)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs index 40cc222cce..22ad2c1584 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs @@ -1,11 +1,13 @@ //! Loader dependency mapping and synthetic source boundary construction. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::graph::views::edge_dict; use crate::bindings::_helpers::planner_scope::values::{ - NATIVE, SOURCE_MODELS, dependency_names, edges, keys, loader_graph, loader_names, project_graph, + dependency_names, edges, keys, loader_graph, loader_names, project_graph, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, IntoPyObject, PyAny, PyAnyMethods, PyResult, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; use sqlbuild_analysis::planner_scope::main::{ @@ -40,7 +42,7 @@ pub(crate) fn loader_to_source_entry<'py>( .into_any(); } let parts = call_with( - &imported(py, NATIVE, "loader_destination_parts")?, + ®istry::native(py, NativeAttr::LoaderDestinationPartsFunction)?, &[ ("destination", destination), ( @@ -71,7 +73,7 @@ pub(crate) fn loader_to_source_entry<'py>( let meta = PyDict::new(py); meta.set_item("sqlbuild_loader_node", true)?; kwargs.set_item("meta", meta)?; - imported(py, SOURCE_MODELS, "SourceEntry")?.call((), Some(&kwargs)) + registry::class(py, RecordId::SourceEntry)?.call((), Some(&kwargs)) } pub(crate) fn intermediate_map<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs index 0a194a1d52..6daf82827c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs @@ -1,12 +1,12 @@ //! Planner scope assembly using native graph and selector operations. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::planner_scope::loaders::expand_selected_loader_dependencies; -use crate::bindings::_helpers::planner_scope::values::{NATIVE, edges, keys}; +use crate::bindings::_helpers::planner_scope::values::{edges, keys}; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::PyDictMethods; use pyo3::types::{PyDict, PyFrozenSet, PyTuple}; @@ -16,8 +16,6 @@ use sqlbuild_analysis::planner_scope::main::{ }; use std::collections::HashSet; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - #[pyfunction] #[pyo3(signature = (*, selected_keys, upstream_deps, project))] pub(crate) fn managed_source_upstream_keys<'py>( @@ -65,14 +63,16 @@ pub(crate) fn build_planner_scope<'py>( let select = default_tuple(&args.value("select")?); let exclude = default_tuple(&args.value("exclude")?); let auto_load = args.value("auto_load_sources")?.is_truthy()?; - let mut upstream = imported(py, NATIVE, "build_execution_upstream_deps")?.call1((&project,))?; - let mut downstream = imported(py, NATIVE, "build_downstream_deps")?.call1((&upstream,))?; + let mut upstream = + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((&project,))?; + let mut downstream = + registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; let lineage = project.getattr("lineage_graph")?; - let all_keys = imported(py, NATIVE, "lineage_graph_names")?.call1((&lineage,))?; + let all_keys = registry::native(py, NativeAttr::LineageGraphNames)?.call1((&lineage,))?; let mut selected = args.value("selected_keys")?; if selected.is_none() { selected = call_with( - &imported(py, NATIVE, "resolve_graph_selection")?, + ®istry::native(py, NativeAttr::ResolveGraphSelection)?, &[ ("graph", lineage), ("select", select.clone()), @@ -114,11 +114,12 @@ pub(crate) fn build_planner_scope<'py>( expand_selected_loader_dependencies(&project, &selected, &upstream, Some(&executable))?; selected = expanded.into_any(); upstream = deps.into_any(); - downstream = imported(py, NATIVE, "build_downstream_deps")?.call1((&upstream,))?; + downstream = registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; } - let origins = imported(py, NATIVE, "build_execution_edge_origins")?.call1((&project,))?; + let origins = + registry::native(py, NativeAttr::BuildExecutionEdgeOrigins)?.call1((&project,))?; let order = call_with( - &imported(py, NATIVE, "topologically_order_keys")?, + ®istry::native(py, NativeAttr::TopologicallyOrderKeys)?, &[ ("upstream", upstream.clone()), ("injected_edge_origins", origins), @@ -135,10 +136,10 @@ pub(crate) fn build_planner_scope<'py>( } let mut tests = args.value("sql_test_selection")?; if !tests.is_truthy()? { - tests = imported(py, NATIVE_RECORDS_MODULE, "SqlTestSelection")?.call0()?; + tests = registry::class(py, RecordId::SqlTestSelection)?.call0()?; } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerScope")?, + ®istry::class(py, RecordId::PlannerScope)?, &[ ("upstream_deps", upstream), ("downstream_deps", downstream), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs index ba79f23caa..1eccb0a12c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs @@ -1,16 +1,16 @@ //! Existing source and load plan models materialised at the Python boundary. -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::graph::algorithms::closure; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_scope::loaders::intermediate_map; -use crate::bindings::_helpers::planner_scope::values::MODELS; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, pyfunction}; use pyo3::types::{PyBool, PyDict, PyTuple}; use pyo3::types::{PyDictMethods, PySetMethods}; use sqlbuild_analysis::planner_scope::constants::SOURCE; -const EXECUTION_TYPES: &str = "sqlbuild.runtime.contracts.types"; - #[pyfunction] #[pyo3(signature = (*, project, selected_keys, upstream_deps = None))] pub(crate) fn build_source_load_map<'py>( @@ -55,8 +55,8 @@ pub(crate) fn build_source_load_entries<'py>( is_reload: bool, ) -> PyResult> { let py = execution_order.py(); - let class = imported(py, MODELS, "SourceLoadPlanEntry")?; - let kinds = imported(py, EXECUTION_TYPES, "ExecutionResourceKind")?; + let class = registry::class(py, RecordId::SourceLoadPlanEntry)?; + let kinds = host::entry(py, ExecutorHost::ExecutionResourceKind)?; let mut entries: Vec> = Vec::new(); for key in execution_order.try_iter()? { let key = key?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs index 56545f3785..1f791d8c01 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs @@ -7,10 +7,6 @@ use pyo3::types::PyDictMethods; use sqlbuild_analysis::assembly::project::types::ObjectKey; use sqlbuild_analysis::planner_scope::models::LoaderGraph; -pub(crate) const NATIVE: &str = "sqlbuild._native"; -pub(crate) const MODELS: &str = "sqlbuild._native"; -pub(crate) const SOURCE_MODELS: &str = "sqlbuild._native"; - pub(crate) fn keys(values: &Bound<'_, PyAny>) -> PyResult> { values .try_iter()? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs index a34b4384f6..95dbc9e71d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs @@ -6,9 +6,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyStringMethods}; use sqlbuild_analysis::graph::errors::SelectorError; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const PRIVATE_USE: std::ops::Range = 0xF0000..0xFFFFE; /// `str.translate` tables to and from carrier characters; empty when no text holds a surrogate. @@ -107,7 +107,7 @@ impl<'py> Carrier<'py> { .map(|help| self.back(help)) .transpose()?; keywords.set_item("help", help)?; - imported(self.py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")? + registry::error(self.py, ErrorId::PlannerInput)? .call((self.back(&error.message)?,), Some(&keywords)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs index eb4b6d65ac..69a6e63fc8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs @@ -14,6 +14,8 @@ use sqlbuild_analysis::planner_selection::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::planner_selection::carrier::Carrier; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const SHARED_EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; @@ -34,7 +36,7 @@ fn split_selector_expansion<'py>(raw: &Bound<'py, PyString>) -> PyResult( upstream, downstream, } => call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ParsedSelector")?, + ®istry::class(py, RecordId::ParsedSelector)?, &[ ( "kind", - imported(py, NATIVE_RECORDS_MODULE, "SelectorKind")?.call1((kind,))?, + registry::class(py, RecordId::SelectorKind)?.call1((kind,))?, ), ("value", carrier.back(&value)?.into_any()), ("upstream", PyBool::new(py, upstream).to_owned().into_any()), @@ -66,7 +64,7 @@ pub(crate) fn parsed_selector<'py>( upstream, downstream, } => call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PathSelector")?, + ®istry::class(py, RecordId::PathSelector)?, &[ ("start_name", carrier.back(&start)?.into_any()), ("end_name", carrier.back(&end)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs index 8f063da418..b420ee0868 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs @@ -10,10 +10,10 @@ use sqlbuild_analysis::planner_selection::models::{ SelectorGroups, UnitTestSelectorNames, UnitTestSelectorSplit, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const RESOURCE_ENTRY_FILES: [(&str, &str); 3] = [ ("schema_files", "model_entries"), @@ -111,7 +111,7 @@ fn split_python<'py>( ) .map_err(|error| carrier.error(error))?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlTestSelection")?, + ®istry::class(py, RecordId::SqlTestSelection)?, &[ ("names", PyFrozenSet::new(py, &split.test_names)?.into_any()), ( @@ -129,7 +129,7 @@ fn split_python<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "UnitTestSelectorSplit")?, + ®istry::class(py, RecordId::UnitTestSelectorSplit)?, &[ ("select", restored(&carrier, &split.select)?.into_any()), ("exclude", restored(&carrier, &split.exclude)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs index 5b1a3d6b84..793f79e5e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs @@ -10,9 +10,10 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::planner_strategy::constants::{ - AUDIT_TYPES_MODULE, COMPILE_MODELS_MODULE, COMPILE_TYPES_MODULE, MODEL_ATTACHMENT, - NATIVE_MODULE, PLAN_MODELS_MODULE, SOURCE_ATTACHMENT, + AUDIT_TYPES_MODULE, COMPILE_TYPES_MODULE, MODEL_ATTACHMENT, SOURCE_ATTACHMENT, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Render and validate one audit query with the supplied adapter callbacks. fn render_query<'py>( @@ -22,7 +23,7 @@ fn render_query<'py>( ) -> PyResult> { let py = request.py(); let resolved = call_with( - &imported(py, NATIVE_MODULE, "render_audit_sql")?, + ®istry::native(py, NativeAttr::RenderAuditSql)?, &[ ("unresolved_sql", sql), ("model_locations", request.get("model_locations")?), @@ -32,7 +33,7 @@ fn render_query<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "assert_no_unresolved_sql_markers")?, + ®istry::native(py, NativeAttr::AssertNoUnresolvedSqlMarkers)?, &[ ("sql", resolved.clone()), ("context", PyString::new(py, context).into_any()), @@ -81,7 +82,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< )? }; let attachment = call_with( - &imported(py, NATIVE_MODULE, "resolve_attachment_kind")?, + ®istry::native(py, NativeAttr::ResolveAttachmentKind)?, &[ ("audit", audit.clone()), ("downstream_deps", request.get("lineage_downstream_deps")?), @@ -101,7 +102,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< let requested = audit_enum(&audit, "run_scope", "AuditRunScope", "final")?; let severity = audit_enum(&audit, "severity", "AuditSeverity", "warn")?; let effective = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_run_scope")?, + ®istry::native(py, NativeAttr::ResolveEffectiveRunScope)?, &[ ("requested_run_scope", requested.clone()), ("attached_model_materialization", materialization), @@ -153,7 +154,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< ( "reads_outside_target_lineage", call_with( - &imported(py, NATIVE_MODULE, "reads_outside_target_lineage")?, + ®istry::native(py, NativeAttr::ReadsOutsideTargetLineage)?, &[ ("audit", audit), ("upstream_deps", request.get("lineage_upstream_deps")?), @@ -163,7 +164,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< ] { values.set_item(field, value)?; } - imported(py, PLAN_MODELS_MODULE, "AuditPlanEntry")?.call((), Some(&values)) + registry::class(py, RecordId::AuditPlanEntry)?.call((), Some(&values)) } /// Select attached audits by their logical target and singular audits by dependency overlap. @@ -183,7 +184,7 @@ pub(crate) fn audit_is_selected( let resource_type = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.call1((target_value,))?; let key = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[("resource_type", resource_type), ("name", name)], )?; return selected_keys.contains(key); @@ -217,7 +218,7 @@ pub(crate) fn build_selected_audit_entries<'py>( if selected.is_empty() { return Ok(entries); } - let lineage = imported(py, NATIVE_MODULE, "project_lineage_views")?.call1((project,))?; + let lineage = registry::native(py, NativeAttr::ProjectLineageViews)?.call1((project,))?; let relations = request.get("relations")?; let arguments = PyDict::new(py); for field in ["model_locations", "seed_locations"] { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs index b0e46157c8..ba45519683 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs @@ -11,12 +11,11 @@ use sqlbuild_analysis::planner_strategy::models::{AuditAttachmentKind, AuditRunS use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::graph::algorithms::closure; -use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, input_error, optional_string, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const MODEL_RESOURCE_TYPE: &str = "model"; const FINAL_RUN_SCOPE: &str = "final"; @@ -24,7 +23,7 @@ const FINAL_RUN_SCOPE: &str = "final"; /// `CompiledObjectKey(resource_type=CompiledResourceType(kind), name=name)`. fn object_key<'py>(py: Python<'py>, kind: &str, name: &str) -> PyResult> { call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ( "resource_type", @@ -85,7 +84,7 @@ fn resolve_attachment_kind<'py>( if name.is_none() { return Err(input_error( py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), + ErrorId::PlannerInput, &format!( "audit '{}': attached audit is missing an attached target name", text(&audit.getattr("name")?)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs index b306b03409..4646fba279 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs @@ -1,15 +1,9 @@ //! Audit plan boundary modules and attachment values. -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; pub(crate) const AUDIT_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; pub(crate) const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -pub(crate) const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const PLAN_MODELS_MODULE: &str = "sqlbuild._native"; pub(crate) const MODEL_ATTACHMENT: &str = "model"; pub(crate) const SOURCE_ATTACHMENT: &str = "source"; -pub(crate) const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const CURSOR_TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; pub(crate) const INCREMENTAL_MATERIALIZATION: &str = "incremental"; pub(crate) const MICROBATCH_MODE: &str = "microbatch"; pub(crate) const CUSTOM_MATERIALIZATION: &str = "custom"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs index d877d50c3f..40cc22382d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs @@ -9,18 +9,18 @@ use sqlbuild_analysis::planner_strategy::models::{CursorInputColumnCheck, Cursor use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_attachment::cursor_roles::resolve_cursor_input_roles; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::model_config::config_values::{config_cursor_bound, config_str}; use crate::bindings::_helpers::planner_identity::metadata::or_empty; use crate::bindings::_helpers::planner_strategy::constants::{ - ENFORCED_CONTRACT, INCREMENTAL_MATERIALIZATION, MODEL_REFERENCE, PLAN_MODELS_MODULE, - SEED_REFERENCE, SOURCE_REFERENCE, TIMESTAMP_CURSOR, + ENFORCED_CONTRACT, INCREMENTAL_MATERIALIZATION, MODEL_REFERENCE, SEED_REFERENCE, + SOURCE_REFERENCE, TIMESTAMP_CURSOR, }; use crate::bindings::_helpers::planner_strategy::lineage::resolve_lineage_reference; -use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, input_error, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::references::source_relations::render_source_relation; /// A model's authored config values. @@ -52,12 +52,7 @@ fn names(items: &Bound<'_, PyAny>) -> PyResult> { /// Raise the planner error of a cursor input column its input does not expose. fn check_column(check: CursorInputColumnCheck, py: Python<'_>) -> PyResult<()> { match cursor_input_column_failure(&check) { - Some((message, code)) => Err(input_error( - py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), - &message, - Some(code), - )), + Some((message, code)) => Err(input_error(py, ErrorId::PlannerInput, &message, Some(code))), None => Ok(()), } } @@ -188,7 +183,7 @@ fn input_kind<'py>( SOURCE_REFERENCE => "source", _ => return Ok(py.None().into_bound(py)), }; - imported(py, "sqlbuild._native", "GraphResourceKind")?.call1((value,)) + registry::class(py, RecordId::GraphResourceKind)?.call1((value,)) } /// A producer model's terminal cursor bound parsed with the consumer's cursor type. @@ -226,7 +221,7 @@ pub(crate) fn build_cursor_input_relations<'py>( Some(cursor_type) if cursor_type.is_truthy()? => cursor_type, _ => PyString::new(py, TIMESTAMP_CURSOR).into_any(), }; - let class: Bound<'py, PyAny> = imported(py, PLAN_MODELS_MODULE, "CursorInputRelation")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::CursorInputRelation)?; let mut relations: Vec> = Vec::new(); let watermarks: Bound<'py, PyAny> = resolve_cursor_input_roles(&model)?.getattr("watermark_inputs")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs index 848cb748f2..b20c8c11e1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs @@ -12,17 +12,19 @@ use sqlbuild_analysis::planner_strategy::models::{CursorType, CursorTypeCheck}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_changes::results::planner_enum; use crate::bindings::_helpers::planner_strategy::values::{optional_string, plan_warning, text}; +use crate::bindings::_helpers::records::ids::RecordId; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const PLANNER_LOGGER: &str = "sqlbuild.planner"; /// Log the Polyglot parse error that left a warehouse type unclassified. fn log_parse_failure(py: Python<'_>, failure: &TypeParseFailure) -> PyResult<()> { let text = |value: &str| PyString::new(py, value).into_any(); call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, + &host::entry(py, ExecutorHost::LogDebugEvent)?, &[ ( "logger", @@ -59,7 +61,7 @@ fn classify_cursor_sql_type<'py>( CursorType::Timestamp => "timestamp", CursorType::Integer => "integer", }; - planner_enum(py, "CursorType", value) + planner_enum(py, RecordId::CursorType, value) }) .transpose() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs index 6833326347..f614f02137 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs @@ -17,6 +17,7 @@ use crate::bindings::_helpers::planner_strategy::fixture_types::expected_fixture use crate::bindings::_helpers::planner_strategy::fixture_values::{ columns, diagnostic, folded, label, record, strings, }; +use crate::bindings::_helpers::records::ids::RecordId; #[pyfunction] #[pyo3(signature = (*, project, adapter, request, planning_context = None))] @@ -198,7 +199,7 @@ fn complete_prepared<'py>( kwargs.set_item("inferred_by_fixture", prepared.inferred)?; kwargs.set_item("expected_types", expected)?; kwargs.set_item("diagnostics", PyTuple::new(py, prepared.diagnostics)?)?; - record(py, "RelationFixtureCompletion", &kwargs) + record(py, RecordId::RelationFixtureCompletion, &kwargs) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs index d6509e6ecf..3d2187cabe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs @@ -7,6 +7,7 @@ use sqlbuild_analysis::planner_fixtures::main::fixture_schema::fixture_schema; use crate::bindings::_helpers::planner_strategy::fixture_metadata::model_fixture_metadata; use crate::bindings::_helpers::planner_strategy::fixture_values::{columns, metadata, record}; +use crate::bindings::_helpers::records::ids::RecordId; #[pyfunction] #[pyo3(signature = (*, project, models_by_name = None))] @@ -35,7 +36,7 @@ pub(crate) fn build_relation_fixture_context<'py>( )?; kwargs.set_item("expected_types", base_expected_fixture_types(&relations)?)?; kwargs.set_item("relations", relations)?; - record(py, "RelationFixturePlanningContext", &kwargs) + record(py, RecordId::RelationFixturePlanningContext, &kwargs) } fn relation_metadata<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs index 6a4b719603..ce01565e3c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs @@ -16,6 +16,8 @@ use std::collections::{HashMap, HashSet}; use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::planner_strategy::fixture_values::columns; use crate::bindings::_helpers::planner_strategy::models::FixtureReadCacheHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; #[pyfunction] @@ -83,10 +85,8 @@ pub(crate) fn required_fixture_columns<'py>( stars: model_stars(&model, &schema, adapter)?, }); } - let owner = py - .import("sqlbuild._native")? - .getattr("_fixture_read_cache")? - .cast_into::()?; + let owner = + registry::native(py, NativeAttr::FixtureReadCache)?.cast_into::()?; let mut owner = owner.borrow_mut(); let cache = std::mem::take(&mut owner.cache); let fixture_keys: HashSet<(String, String)> = keys.keys().cloned().collect(); @@ -124,11 +124,11 @@ fn lineage_reads(model: &Bound<'_, PyAny>) -> PyResult> { } for fact in facts.try_iter()? { let fact = fact?; - if !fact.getattr("confidence")?.eq(model - .py() - .import("sqlbuild._native")? - .getattr("ColumnLineageConfidence")? - .getattr("HIGH")?)? + if !fact.getattr("confidence")?.eq(registry::class( + model.py(), + RecordId::ColumnLineageConfidence, + )? + .getattr("HIGH")?)? { continue; } @@ -218,10 +218,8 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { #[pyfunction] #[pyo3(signature = (*, reset = false))] fn fixture_read_cache_info(py: pyo3::Python<'_>, reset: bool) -> PyResult<(u64, usize)> { - let owner = py - .import("sqlbuild._native")? - .getattr("_fixture_read_cache")? - .cast_into::()?; + let owner = + registry::native(py, NativeAttr::FixtureReadCache)?.cast_into::()?; let mut owner = owner.borrow_mut(); if reset { owner.cache = Default::default(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs index ff677ebe85..beb34dfd93 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs @@ -10,6 +10,8 @@ use sqlbuild_analysis::planner_fixtures::models::{ }; use crate::bindings::_helpers::planner_strategy::fixture_values::folded; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; #[pyfunction] #[pyo3(signature = (*, base_expected_types, ordered_model_names, model_map))] @@ -80,11 +82,7 @@ fn projections(model: &Bound<'_, PyAny>) -> PyResult> if lineage.is_none() { return Ok(projections); } - let direct = model - .py() - .import("sqlbuild._native")? - .getattr("ColumnTransformKind")? - .getattr("DIRECT")?; + let direct = registry::class(model.py(), RecordId::ColumnTransformKind)?.getattr("DIRECT")?; for column in lineage.try_iter()? { let column = column?; if !column.getattr("transform_kind")?.eq(&direct)? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs index f22fa7a1ec..2177f21723 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs @@ -1,11 +1,11 @@ //! Fixture boundary records and adapter callbacks. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use sqlbuild_analysis::planner_fixtures::models::FixtureColumn; -pub(crate) const MODELS: &str = "sqlbuild._native"; - pub(crate) fn folded(value: &Bound<'_, PyAny>) -> PyResult { value.call_method0("casefold")?.extract() } @@ -36,10 +36,10 @@ pub(crate) fn columns(values: &Bound<'_, PyAny>, inferred: bool) -> PyResult( py: Python<'py>, - name: &str, + name: RecordId, kwargs: &Bound<'py, PyDict>, ) -> PyResult> { - py.import(MODELS)?.getattr(name)?.call((), Some(kwargs)) + registry::class(py, name)?.call((), Some(kwargs)) } pub(crate) fn metadata<'py>( @@ -54,13 +54,13 @@ pub(crate) fn metadata<'py>( kwargs.set_item("name", &column.name)?; kwargs.set_item("type", &column.sql_type)?; kwargs.set_item("nullable", column.nullable)?; - record(py, "FixtureColumnMetadata", &kwargs) + record(py, RecordId::FixtureColumnMetadata, &kwargs) }) .collect::>>()?; let kwargs = PyDict::new(py); kwargs.set_item("columns", PyTuple::new(py, values)?)?; kwargs.set_item("authoritative_names", authoritative)?; - record(py, "FixtureRelationMetadata", &kwargs) + record(py, RecordId::FixtureRelationMetadata, &kwargs) } pub(crate) fn label(key: &Bound<'_, PyAny>) -> PyResult { @@ -78,5 +78,5 @@ pub(crate) fn diagnostic<'py>( let kwargs = PyDict::new(key.py()); kwargs.set_item("key", key)?; kwargs.set_item("message", message)?; - record(key.py(), "RelationFixtureDiagnostic", &kwargs) + record(key.py(), RecordId::RelationFixtureDiagnostic, &kwargs) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs index 1a7d6f905c..9fc4edac60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs @@ -17,9 +17,8 @@ use crate::bindings::_helpers::model_config::config_values::config_str; use crate::bindings::_helpers::planner_strategy::microbatches::{ CAP_ACTIONS, microbatch_limit_config, }; -use crate::bindings::_helpers::planner_strategy::values::{ - COMPILE_EXCEPTIONS_MODULE, PLANNER_EXCEPTIONS_MODULE, input_error, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::ids::ErrorId; const MICROBATCH_MODE: &str = "microbatch"; @@ -106,7 +105,7 @@ fn lineage_error(py: Python<'_>, failure: LineageFailure) -> PyErr { LineageFailure::Graph(error) => error, LineageFailure::NotInLineage { model, input } => input_error( py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), + ErrorId::PlannerInput, &LineageFailure::::message(&model, &input), Some(LineageFailure::::code()), ), @@ -158,7 +157,7 @@ fn reject_capped_watermark_inputs<'py>( Ok(None) => Ok(()), Ok(Some(capped)) => Err(input_error( py, - (COMPILE_EXCEPTIONS_MODULE, "CompileInputError"), + ErrorId::CompileInput, &capped.message(), None, )), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs index f63f1dfafb..9e99296361 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs @@ -11,16 +11,15 @@ use sqlbuild_core::plan_display::errors::UnknownCursorGrain; use sqlbuild_core::plan_display::main::effective_timestamp_grain::effective_timestamp_grain; use sqlbuild_core::plan_display::models::TimestampGrainFacts; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compiled_project::records::replace; use crate::bindings::_helpers::plan_output::display::resolve_effective_microbatch_batch_size; -use crate::bindings::_helpers::planner_strategy::constants::PLANNER_TYPES_MODULE; use crate::bindings::_helpers::planner_strategy::microbatches::{ - CAP_ACTIONS, CONTRACT_TYPES_MODULE, capped_microbatch_bounds, count_microbatches, -}; -use crate::bindings::_helpers::planner_strategy::values::{ - COMPILE_EXCEPTIONS_MODULE, input_error, optional_string, text, + CAP_ACTIONS, capped_microbatch_bounds, count_microbatches, }; +use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const ERROR_ACTION: &str = "error"; const SKIP_ACTION: &str = "skip"; @@ -33,7 +32,7 @@ type LimitedEntry<'py> = (Bound<'py, PyAny>, Option>); /// `CursorGrain(grain)`'s error for a grain outside the known grains. pub(crate) fn unknown_grain(py: Python<'_>, error: UnknownCursorGrain) -> PyErr { - imported(py, PLANNER_TYPES_MODULE, "CursorGrain") + registry::class(py, RecordId::CursorGrain) .and_then(|class| class.call1((error.0,))) .err() .unwrap_or_else(|| pyo3::exceptions::PyValueError::new_err("unknown cursor grain")) @@ -149,18 +148,16 @@ fn limited<'py>( return Ok((limited_entry, None)); }; if action_text == ERROR_ACTION { - return Err(input_error( - py, - (COMPILE_EXCEPTIONS_MODULE, "CompileInputError"), - &message, - None, - )); + return Err(input_error(py, ErrorId::CompileInput, &message, None)); } let plan_warning: Bound<'py, PyAny> = call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", name), - ("severity", native_enum(py, "WarningSeverity", "warning")?), + ( + "severity", + native_enum(py, RecordId::WarningSeverity, "warning")?, + ), ( "code", PyString::new(py, MICROBATCH_LIMIT_WARNING_CODE).into_any(), @@ -238,7 +235,7 @@ pub(crate) fn apply_configured_microbatch_limits<'py>( .is_truthy()? { let error: Bound<'py, PyAny> = - imported(py, CONTRACT_TYPES_MODULE, "MicrobatchLimitAction")?.getattr("ERROR")?; + registry::class(py, RecordId::MicrobatchLimitAction)?.getattr("ERROR")?; return with_warnings(limited(entry, configured, error)?, Vec::new()); } let declared: Bound<'py, PyAny> = entry.getattr("declared_microbatch_limit_action")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs index 596c9526f3..270bfe373d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs @@ -18,14 +18,13 @@ use sqlbuild_analysis::planner_strategy::types::Timeline; use sqlbuild_cursor::algebra::main::parse_duration::parse_duration; use sqlbuild_cursor::algebra::models::{DurationDigits, DurationSpan}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::durations::shifted_moment; use crate::bindings::_helpers::plan_output::display::aligned_timestamp_bounds; -use crate::bindings::_helpers::planner_changes::results::MODELS_MODULE; use crate::bindings::_helpers::planner_strategy::values::text; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const CURSOR_MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; const INTEGER_CURSOR: &str = "integer"; const TIMESTAMP_CURSOR: &str = "timestamp"; const MONTHS_PER_YEAR: i64 = 12; @@ -80,14 +79,14 @@ fn duration_span(batch_size: &str) -> Option { /// The bounds of a `CursorBounds` when each is an instance of one of the cursor value `classes`. fn typed_bounds<'py>( bounds: &Bound<'py, PyAny>, - classes: &[&str], + classes: &[RecordId], ) -> PyResult>> { let py: Python<'py> = bounds.py(); let (start, end) = (bounds.getattr("start")?, bounds.getattr("end")?); for value in [&start, &end] { let mut typed: bool = false; for class in classes { - typed = typed || value.is_instance(&imported(py, CURSOR_MODELS_MODULE, class)?)?; + typed = typed || value.is_instance(®istry::class(py, *class)?)?; } if !typed { return Ok(None); @@ -98,7 +97,7 @@ fn typed_bounds<'py>( /// The integer values of integer bounds. fn integer_bounds(bounds: &Bound<'_, PyAny>) -> PyResult> { - let Some((start, end)) = typed_bounds(bounds, &["IntegerValue"])? else { + let Some((start, end)) = typed_bounds(bounds, &[RecordId::IntegerValue])? else { return Ok(None); }; Ok(Some(( @@ -115,7 +114,9 @@ fn timeline_bounds<'py>( let Some(span) = duration_span(batch_size) else { return Ok(None); }; - let Some((start, end)) = typed_bounds(bounds, &["DateValue", "TimestampValue"])? else { + let Some((start, end)) = + typed_bounds(bounds, &[RecordId::DateValue, RecordId::TimestampValue])? + else { return Ok(None); }; let aligned: Bound<'py, PyTuple> = aligned_timestamp_bounds(&start, &end)?; @@ -156,7 +157,7 @@ fn cursor_bounds<'py>( ) -> PyResult> { let py: Python<'py> = start.py(); call_with( - &imported(py, MODELS_MODULE, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[("start", start), ("end", end)], ) } @@ -164,13 +165,10 @@ fn cursor_bounds<'py>( /// `(value=value)` of the cursor values module. fn cursor_value<'py>( py: Python<'py>, - class: &str, + class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult> { - call_with( - &imported(py, CURSOR_MODELS_MODULE, class)?, - &[("value", value)], - ) + call_with(®istry::class(py, class)?, &[("value", value)]) } /// One concrete range capped to `max_batches` batch windows from the end the action keeps. @@ -197,12 +195,12 @@ pub(crate) fn capped_microbatch_bounds<'py>( match capped { CappedBound::End(value) => CappedBound::End(cursor_value( py, - "IntegerValue", + RecordId::IntegerValue, value.into_pyobject(py)?.into_any(), )?), CappedBound::Start(value) => CappedBound::Start(cursor_value( py, - "IntegerValue", + RecordId::IntegerValue, value.into_pyobject(py)?.into_any(), )?), } @@ -214,9 +212,11 @@ pub(crate) fn capped_microbatch_bounds<'py>( return Ok(bounds); }; match cap_timeline_bounds(&timeline, range, max_batches, direction)? { - CappedBound::End(value) => CappedBound::End(cursor_value(py, "TimestampValue", value)?), + CappedBound::End(value) => { + CappedBound::End(cursor_value(py, RecordId::TimestampValue, value)?) + } CappedBound::Start(value) => { - CappedBound::Start(cursor_value(py, "TimestampValue", value)?) + CappedBound::Start(cursor_value(py, RecordId::TimestampValue, value)?) } } }; @@ -269,7 +269,7 @@ pub(crate) fn microbatch_limit_config<'py>( .filter(|action| action.is_instance_of::()); if let (Some(max_batches), Some(action)) = (max_batches, action) { let action: Bound<'py, PyAny> = - imported(py, CONTRACT_TYPES_MODULE, "MicrobatchLimitAction")?.call1((action,))?; + registry::class(py, RecordId::MicrobatchLimitAction)?.call1((action,))?; return Ok((Some(max_batches), Some(action))); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs index 8ed737996f..0b69c266ef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs @@ -10,21 +10,23 @@ use sqlbuild_analysis::planner_strategy::main::table_type_ddl::table_type_ddl; use sqlbuild_analysis::planner_strategy::models::LookbackConfig; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; use crate::bindings::_helpers::model_config::config_values::{ config_bool, config_cursor_bound, config_int, config_str, config_string_tuple, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_strategy::constants::{ - ADAPTER_MODELS_MODULE, CURSOR_TYPES_MODULE, CUSTOM_MATERIALIZATION, DDL_SEPARATOR, - ENFORCED_CONTRACT, MICROBATCH_MODE, NATIVE_MODULE, PLAN_MODELS_MODULE, PLANNER_TYPES_MODULE, + CUSTOM_MATERIALIZATION, DDL_SEPARATOR, ENFORCED_CONTRACT, MICROBATCH_MODE, }; use crate::bindings::_helpers::planner_strategy::cursor_inputs::model_values; use crate::bindings::_helpers::planner_strategy::microbatch_limits::{replay_grain, unknown_grain}; use crate::bindings::_helpers::planner_strategy::microbatches::microbatch_limit_config; -use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, input_error, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Model config keys copied to the plan entry as optional strings, with their entry fields. const STRING_FIELDS: [(&str, &str); 15] = [ @@ -106,7 +108,7 @@ fn schema_columns<'py>( if entry.is_none() { return Ok(PyTuple::empty(py)); } - let class: Bound<'py, PyAny> = imported(py, ADAPTER_MODELS_MODULE, "ColumnInfo")?; + let class: Bound<'py, PyAny> = host::entry(py, AdapterHost::ColumnInfo)?; let mut columns: Vec> = Vec::new(); for column in entry.getattr("columns")?.try_iter()? { let column: Bound<'py, PyAny> = column?; @@ -152,7 +154,7 @@ fn on_schema_change<'py>(model: &Bound<'py, PyAny>) -> PyResult() { - match imported(py, PLANNER_TYPES_MODULE, "OnSchemaChange")?.call1((&raw,)) { + match registry::class(py, RecordId::OnSchemaChange)?.call1((&raw,)) { Ok(policy) => return Ok(policy), Err(error) if !error.is_instance_of::(py) => return Err(error), Err(_) => {} @@ -160,7 +162,7 @@ fn on_schema_change<'py>(model: &Bound<'py, PyAny>) -> PyResult( let model: Bound<'py, PyAny> = request.get("model")?; let name: Bound<'py, PyAny> = model.getattr("name")?; let mut warnings: Vec> = call_with( - &imported(py, NATIVE_MODULE, "build_model_warnings")?, + ®istry::native(py, NativeAttr::BuildModelWarnings)?, &[ ("model_name", name.clone()), ("materialization_type", request.get("materialization_type")?), @@ -345,7 +347,7 @@ fn model_warnings<'py>( .try_iter()? .collect::>()?; let cursor_warning: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "check_cursor_type_consistency")?, + ®istry::native(py, NativeAttr::CheckCursorTypeConsistency)?, &[ ("model_name", name), ("cursor_column", request.get("cursor_column")?), @@ -371,9 +373,9 @@ fn ddl_cursor_bounds<'py>(request: &Request<'_, 'py>) -> PyResult = imported(py, CURSOR_TYPES_MODULE, "BoundSentinel")?; + let sentinel: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BoundSentinel)?; call_with( - &imported(py, PLAN_MODELS_MODULE, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[ ("start", sentinel.getattr("START")?), ("end", sentinel.getattr("END")?), @@ -491,7 +493,7 @@ pub(crate) fn assemble_model_plan_entry<'py>( let name: Bound<'py, PyAny> = model.getattr("name")?; let policy: Bound<'py, PyAny> = on_schema_change(&model)?; let schema_actions: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_schema_actions")?, + ®istry::native(py, NativeAttr::ResolveSchemaActions)?, &[ ("schema_findings", change.getattr("schema_findings")?), ("on_schema_change", policy.clone()), @@ -560,6 +562,6 @@ pub(crate) fn assemble_model_plan_entry<'py>( &entry, )?; let plan_entry: Bound<'py, PyAny> = - imported(py, PLAN_MODELS_MODULE, "ModelPlanEntry")?.call((), Some(&entry))?; + registry::class(py, RecordId::ModelPlanEntry)?.call((), Some(&entry))?; PyTuple::new(py, [plan_entry, warnings.into_any()]) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs index ba5ea4535a..389a5f139b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs @@ -13,14 +13,14 @@ use sqlbuild_analysis::planner_strategy::models::{ ModelStrategy, PlanAction, PlanReason, SchemaActionKind, WarningInputs, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_changes::results::planner_enum; use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, change_facts, config_value, finding_rows, input_error, - materialization_value, model_materialization, optional_string, plan_warning, text, + change_facts, config_value, finding_rows, input_error, materialization_value, + model_materialization, optional_string, plan_warning, text, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; fn action_value(action: PlanAction) -> &'static str { match action { @@ -64,7 +64,7 @@ fn schema_policy(on_schema_change: &Bound<'_, PyAny>) -> PyResult fn materialization_of<'py>(model: &Bound<'py, PyAny>) -> PyResult> { planner_enum( model.py(), - "MaterializationType", + RecordId::MaterializationType, materialization_value(model_materialization(model)?), ) } @@ -90,7 +90,7 @@ fn resolve_model_plan_action<'py>( |error| { input_error( py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), + ErrorId::PlannerInput, &error.message(), Some(error.code()), ) @@ -99,8 +99,8 @@ fn resolve_model_plan_action<'py>( PyTuple::new( py, [ - planner_enum(py, "PlanAction", action_value(action))?, - planner_enum(py, "PlanReason", reason_value(reason))?, + planner_enum(py, RecordId::PlanAction, action_value(action))?, + planner_enum(py, RecordId::PlanReason, reason_value(reason))?, ], ) } @@ -113,7 +113,7 @@ fn resolve_schema_actions<'py>( on_schema_change: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = schema_findings.py(); - let class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "SchemaAction")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SchemaAction)?; let policy: Option = schema_policy(on_schema_change)?; let mut actions: Vec> = Vec::new(); for row in schema_actions(&finding_rows(schema_findings)?, policy.as_deref()) { @@ -125,7 +125,7 @@ fn resolve_schema_actions<'py>( actions.push(call_with( &class, &[ - ("kind", native_enum(py, "SchemaActionKind", kind)?), + ("kind", native_enum(py, RecordId::SchemaActionKind, kind)?), ( "column_name", PyString::new(py, &row.column_name).into_any(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs index 14815320f1..0617f5ce8e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs @@ -13,10 +13,10 @@ use sqlbuild_analysis::planner_strategy::models::{ ChangeFacts, MaterializationType, PlanChangeKind, WarningRow, WarningSeverity, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -pub(crate) const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -pub(crate) const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const YML_SOURCE: &str = "yml"; const FULL_BACKFILL: &str = "full"; const FORWARD_ONLY_BACKFILL: &str = "forward"; @@ -137,24 +137,27 @@ pub(crate) fn plan_warning<'py>( WarningSeverity::Info => "info", }; call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", model_name.clone()), - ("severity", native_enum(py, "WarningSeverity", severity)?), + ( + "severity", + native_enum(py, RecordId::WarningSeverity, severity)?, + ), ("message", PyString::new(py, &row.message).into_any()), ("code", row.code.into_pyobject(py)?.into_any()), ], ) } -/// `.(message, code=code)` raised as an error. +/// The native error `class(message, code=code)`. pub(crate) fn input_error( py: Python<'_>, - (module, class): (&str, &str), + class: ErrorId, message: &str, code: Option<&str>, ) -> PyErr { - let raised: PyResult> = imported(py, module, class).and_then(|class| { + let raised: PyResult> = registry::error(py, class).and_then(|class| { let options: Bound<'_, PyDict> = PyDict::new(py); if let Some(code) = code { options.set_item("code", code)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs index 49765eee0a..7a9b13e5d2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs @@ -1,51 +1,28 @@ //! Python modules and fixed names of warehouse inspection for planning. -pub(crate) const RELATIONS_MAIN_LIST: (&str, &str) = ( - "sqlbuild.adapter.relations.main._list_relations_for_inspection", - "list_relations_for_inspection", -); -pub(crate) const RELATIONS_MAIN_COLUMNS: (&str, &str) = ( - "sqlbuild.adapter.relations.main._get_columns_for_inspection", - "get_columns_for_inspection", -); -pub(crate) const ACTIVE_CATALOG: (&str, &str) = ( - "sqlbuild.adapter.relations.main.active_inspection_catalog", - "active_inspection_catalog", -); -pub(crate) const RUN_BOUNDED: (&str, &str) = ( - "sqlbuild.adapter.relations.main.run_bounded_inspections", - "run_bounded_inspections", -); -pub(crate) const RECORD_QUERY: (&str, &str) = ( - "sqlbuild.adapter.relations.main.record_inspection_query", - "record_inspection_query", -); -pub(crate) const RUN_RECORDED_QUERY: (&str, &str) = ( - "sqlbuild.adapter.relations.main.run_recorded_inspection_query", - "run_recorded_inspection_query", -); -pub(crate) const QUERY_RECORD: (&str, &str) = - ("sqlbuild.adapter.relations.models", "InspectionQueryRecord"); -pub(crate) const COLUMN_INFO: (&str, &str) = ("sqlbuild.adapter.contract.models", "ColumnInfo"); -pub(crate) const RELATION_LOOKUP: (&str, &str) = - ("sqlbuild.adapter.contract.models", "RelationLookup"); -pub(crate) const LOG_DEBUG_EVENT: (&str, &str) = ( - "sqlbuild.diagnostics.main.log_debug_event", - "log_debug_event", -); -pub(crate) const CURSOR_RESOLVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const CURSOR_POLICIES: (&str, &str) = - ("sqlbuild._native", "resolve_start_cursor_config"); -pub(crate) const MAXIMUM_START: (&str, &str) = ("sqlbuild._native", "maximum_allowed_start"); -pub(crate) const RENDER_SOURCE_READ: (&str, &str) = - ("sqlbuild._native", "render_source_read_relation"); -pub(crate) const YAML_ENTRY_LINE: (&str, &str) = ("sqlbuild._native", "yaml_entry_line"); -pub(crate) const RUN_SCOPE: (&str, &str) = - ("sqlbuild.runtime.observability.main.run_scope", "run_scope"); -pub(crate) const SQL_HOOK_ENTRY: (&str, &str) = ("sqlbuild._native", "SqlHookEntry"); +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; + +pub(crate) const RELATIONS_MAIN_LIST: AdapterHost = AdapterHost::ListRelationsForInspection; +pub(crate) const RELATIONS_MAIN_COLUMNS: AdapterHost = AdapterHost::GetColumnsForInspection; +pub(crate) const ACTIVE_CATALOG: AdapterHost = AdapterHost::ActiveInspectionCatalog; +pub(crate) const RUN_BOUNDED: AdapterHost = AdapterHost::RunBoundedInspections; +pub(crate) const RECORD_QUERY: AdapterHost = AdapterHost::RecordInspectionQuery; +pub(crate) const RUN_RECORDED_QUERY: AdapterHost = AdapterHost::RunRecordedInspectionQuery; +pub(crate) const QUERY_RECORD: AdapterHost = AdapterHost::InspectionQueryRecord; +pub(crate) const COLUMN_INFO: AdapterHost = AdapterHost::ColumnInfo; +pub(crate) const RELATION_LOOKUP: AdapterHost = AdapterHost::RelationLookup; +pub(crate) const LOG_DEBUG_EVENT: ExecutorHost = ExecutorHost::LogDebugEvent; +pub(crate) const CURSOR_POLICIES: NativeAttr = NativeAttr::ResolveStartCursorConfig; +pub(crate) const MAXIMUM_START: NativeAttr = NativeAttr::MaximumAllowedStart; +pub(crate) const RENDER_SOURCE_READ: NativeAttr = NativeAttr::RenderSourceReadRelation; +pub(crate) const YAML_ENTRY_LINE: NativeAttr = NativeAttr::YamlEntryLine; +pub(crate) const RUN_SCOPE: ExecutorHost = ExecutorHost::RunScope; +pub(crate) const SQL_HOOK_ENTRY: RecordId = RecordId::SqlHookEntry; pub(crate) const CURSOR_ALGEBRA_ERROR: (&str, &str) = ("sqlbuild.cursor_algebra.exceptions", "CursorAlgebraError"); -pub(crate) const DATE_VALUE: (&str, &str) = ("sqlbuild._native", "DateValue"); +pub(crate) const DATE_VALUE: RecordId = RecordId::DateValue; pub(crate) const PLANNER_LOGGER: &str = "sqlbuild.planner"; pub(crate) const METADATA_NAME_FILTER_LIMIT: usize = 250; pub(crate) const FUNCTION_NODE_TYPES: [&str; 2] = ["udf", "table_fn"]; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs index 5b7f203d9b..f316e0e19d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs @@ -1,20 +1,22 @@ //! Cursor MIN/MAX inspection of selected incremental models and their watermark inputs. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; -use crate::bindings::_helpers::planner_phases::constants::PLANNER_TYPES; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{ - by_name, config_str, member, model_key, native, optional_text, planner_error, planner_model, - text, tuple, + by_name, config_str, model_key, native, native_member, optional_text, planner_error, + planner_model, text, tuple, }; use crate::bindings::_helpers::planner_warehouse::constants::{ - CURSOR_ALGEBRA_ERROR, CURSOR_POLICIES, CURSOR_RESOLVE_MODULE, CURSOR_TYPE_INTEGER, - CURSOR_TYPE_TIMESTAMP, DATE_VALUE, LOG_DEBUG_EVENT, MAXIMUM_START, PLANNER_LOGGER, - QUERY_RECORD, RECORD_QUERY, RUN_BOUNDED, RUN_RECORDED_QUERY, + CURSOR_ALGEBRA_ERROR, CURSOR_POLICIES, CURSOR_TYPE_INTEGER, CURSOR_TYPE_TIMESTAMP, DATE_VALUE, + LOG_DEBUG_EVENT, MAXIMUM_START, PLANNER_LOGGER, QUERY_RECORD, RECORD_QUERY, RUN_BOUNDED, + RUN_RECORDED_QUERY, }; use crate::bindings::_helpers::planner_warehouse::snapshot::{ SnapshotRequest, inspection_concurrency, }; use crate::bindings::_helpers::planner_warehouse::watermarks::validate_watermark_column; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::exceptions::PyException; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; @@ -95,17 +97,21 @@ fn parse<'py>( ) -> PyResult> { native( py, - "cursor_parse", + NativeAttr::CursorParse, &[("raw", raw.clone()), ("cursor_type", text(py, cursor_type))], ) } fn render<'py>(value: &Bound<'py, PyAny>) -> PyResult> { - native(value.py(), "cursor_render", &[("value", value.clone())]) + native( + value.py(), + NativeAttr::CursorRender, + &[("value", value.clone())], + ) } fn grain<'py>(py: Python<'py>, value: &str) -> PyResult> { - member(py, (PLANNER_TYPES, "CursorGrain"), value) + native_member(py, RecordId::CursorGrain, value) } fn parse_warehouse_cursor<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -132,10 +138,7 @@ fn log_debug<'py>( let logger = imported(py, "logging", "getLogger")?.call1((PLANNER_LOGGER,))?; let mut fields = vec![("logger", logger), ("message", text(py, message))]; fields.extend(context.iter().cloned()); - call_with( - &imported(py, LOG_DEBUG_EVENT.0, LOG_DEBUG_EVENT.1)?, - &fields, - )?; + call_with(&host::entry(py, LOG_DEBUG_EVENT)?, &fields)?; Ok(()) } @@ -236,7 +239,7 @@ fn upstream_relation<'py>( { return native( py, - "render_source_relation", + NativeAttr::RenderSourceRelation, &[ ("entry", source.getattr("source_entry")?), ("adapter", adapter.clone()), @@ -253,7 +256,7 @@ fn lineage_reference<'py>( ) -> PyResult> { native( model.py(), - "resolve_lineage_reference", + NativeAttr::ResolveLineageReference, &[ ("model", model.clone()), ("input_name", input_name.clone()), @@ -277,7 +280,7 @@ fn terminal_bound<'py>( }; let value = native( py, - "get_config_cursor_bound", + NativeAttr::GetConfigCursorBound, &[ ("values", upstream.getattr("config")?.getattr("values")?), ("key", text(py, key)), @@ -359,7 +362,7 @@ fn collect_models<'py>( }; let watermark_inputs = native( py, - "resolve_cursor_input_roles", + NativeAttr::ResolveCursorInputRoles, &[("model", model.clone())], )? .getattr("watermark_inputs")?; @@ -457,11 +460,7 @@ fn collect_models<'py>( }); } let effective_grain = call_with( - &imported( - py, - CURSOR_RESOLVE_MODULE, - "resolve_effective_timestamp_grain", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveTimestampGrain)?, &[ ("cursor_type", optional_text(py, cursor_type.as_deref())), ( @@ -478,7 +477,7 @@ fn collect_models<'py>( let mode = config_str(&model, "cursor_watermark_mode")? .filter(|value| !value.is_empty()) .unwrap_or_else(|| "all".to_owned()); - let watermark_mode = member(py, ("sqlbuild._native", "CursorWatermarkMode"), &mode)?; + let watermark_mode = native_member(py, RecordId::CursorWatermarkMode, &mode)?; models.push(CursorModel { target_tag, target_relation, @@ -493,7 +492,7 @@ fn collect_models<'py>( upstreams, effective_grain, start_cursor_config: call_with( - &imported(py, CURSOR_POLICIES.0, CURSOR_POLICIES.1)?, + ®istry::native(py, CURSOR_POLICIES)?, &[ ("model", model.clone()), ("project_config", inputs.start_cursor_config.clone()), @@ -584,7 +583,7 @@ fn read_cursor_query<'py>( error.str()?.into_any() }; let record = call_with( - &imported(py, QUERY_RECORD.0, QUERY_RECORD.1)?, + &host::entry(py, QUERY_RECORD)?, &[ ("sql", text(py, sql)), ("elapsed_seconds", PyFloat::new(py, elapsed).into_any()), @@ -592,10 +591,7 @@ fn read_cursor_query<'py>( ("error", error_text), ], )?; - call_with( - &imported(py, RECORD_QUERY.0, RECORD_QUERY.1)?, - &[("record", record)], - )?; + call_with(&host::entry(py, RECORD_QUERY)?, &[("record", record)])?; Ok(PyTuple::new(py, [row, PyFloat::new(py, elapsed).into_any(), error])?.into_any()) } @@ -746,7 +742,7 @@ fn execute_queries<'py>( tasks.push(bound_task(py, cursor_query_sql(query), handles)?); } call_with( - &imported(py, RUN_BOUNDED.0, RUN_BOUNDED.1)?, + &host::entry(py, RUN_BOUNDED)?, &[ ("tasks", tuple(py, tasks)?), ("concurrency", concurrency.into_pyobject(py)?.into_any()), @@ -805,7 +801,7 @@ fn eligible_task<'py>( }, )?; let rows = call_with( - &imported(py, RUN_RECORDED_QUERY.0, RUN_RECORDED_QUERY.1)?, + &host::entry(py, RUN_RECORDED_QUERY)?, &[("sql", text(py, &sql)), ("run", run.into_any())], ); match rows { @@ -855,7 +851,7 @@ fn eligible_target_maxes<'py>( continue; } let allowed = call_with( - &imported(py, MAXIMUM_START.0, MAXIMUM_START.1)?, + ®istry::native(py, MAXIMUM_START)?, &[ ("discovered_value", render(target_max)?), ("cursor_grain", info.effective_grain.clone()), @@ -866,7 +862,7 @@ fn eligible_target_maxes<'py>( let horizon = parse(py, &allowed, CURSOR_TYPE_TIMESTAMP)?; let order: i64 = native( py, - "cursor_compare", + NativeAttr::CursorCompare, &[("left", target_max.clone()), ("right", horizon.clone())], )? .extract()?; @@ -889,7 +885,7 @@ fn eligible_target_maxes<'py>( ( "is_date", horizon - .is_instance(&imported(py, DATE_VALUE.0, DATE_VALUE.1)?)? + .is_instance(®istry::class(py, DATE_VALUE)?)? .into_pyobject(py)? .to_owned() .into_any(), @@ -912,7 +908,7 @@ fn eligible_target_maxes<'py>( )?); } let rows_by_request = call_with( - &imported(py, RUN_BOUNDED.0, RUN_BOUNDED.1)?, + &host::entry(py, RUN_BOUNDED)?, &[ ("tasks", tuple(py, tasks)?), ( @@ -939,13 +935,13 @@ fn eligible_target_maxes<'py>( .unwrap_or(CURSOR_TYPE_TIMESTAMP); let value = parse(py, &first, cursor_type)?; let normalized = call_with( - &imported(py, CURSOR_RESOLVE_MODULE, "normalize_cursor_snapshot_grain")?, + ®istry::native(py, NativeAttr::NormalizeCursorSnapshotGrain)?, &[ ( "cursor_snapshot", planner_model( py, - "ModelCursorSnapshot", + RecordId::ModelCursorSnapshot, &[ ("target_max", py.None().into_bound(py)), ("upstream_mins", PyTuple::empty(py).into_any()), @@ -1035,7 +1031,7 @@ fn assemble<'py>( let effective_max = match &upstream.terminal_end { Some(end) => Some(native( py, - "cursor_exclusive_to_inclusive", + NativeAttr::CursorExclusiveToInclusive, &[ ("value", end.clone()), ("grain", cursor_grain(py, info, upstream)?), @@ -1060,7 +1056,7 @@ fn assemble<'py>( }; evidence.push(planner_model( py, - "CursorInputEvidence", + RecordId::CursorInputEvidence, &[ ("relation", text(py, &upstream.relation)), ("cursor_column", text(py, &upstream.cursor_column)), @@ -1095,7 +1091,7 @@ fn assemble<'py>( available = Some( native( py, - "cursor_observed_partition", + NativeAttr::CursorObservedPartition, &[ ("value", max_value.clone()), ("grain", cursor_grain(py, info, upstream)?), @@ -1118,7 +1114,7 @@ fn assemble<'py>( &info.model_name, planner_model( py, - "ModelCursorSnapshot", + RecordId::ModelCursorSnapshot, &[ ("target_max", optional(py, target_max)), ("upstream_mins", tuple(py, mins)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs index 7f970753fc..7fe85474b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs @@ -4,6 +4,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::planner_phases::objects::{native, optional_text, text}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::{PyOSError, PyValueError}; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyFloat, PyList, PyListMethods, PyTuple}; @@ -274,17 +276,16 @@ impl MigrationFingerprintCache { let metadata_json: String = args.value("metadata_json")?.extract()?; let ref_identities = &args.value("ref_identities")?; let dialect: Option = args.value("dialect")?.extract()?; - let metadata_json: String = - imported(py, "sqlbuild._native", "without_declared_columns_hash")? - .call1((metadata_json,))? - .extract()?; + let metadata_json: String = registry::native(py, NativeAttr::WithoutDeclaredColumnsHash)? + .call1((metadata_json,))? + .extract()?; let definition = (query_sql.clone(), metadata_json.clone()); let names = match self.ref_names.get(&definition) { Some(names) => names.clone(), None => { let found = native( py, - "migration_fingerprint_ref_names", + NativeAttr::MigrationFingerprintRefNames, &[ ("query_sql", text(py, &query_sql)), ("metadata_json", text(py, &metadata_json)), @@ -320,7 +321,7 @@ impl MigrationFingerprintCache { } let computed: Option = native( py, - "build_migration_fingerprint", + NativeAttr::BuildMigrationFingerprint, &[ ("query_sql", text(py, &key.0)), ("metadata_json", text(py, &key.1)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs index 747681446e..f91f79d345 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs @@ -1,7 +1,8 @@ //! Complete missing source metadata in bounded, database-grouped batches. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{text, tuple}; use crate::bindings::_helpers::planner_warehouse::constants::ACTIVE_CATALOG; use crate::bindings::_helpers::planner_warehouse::snapshot::{columns_for, list_relations}; @@ -54,7 +55,7 @@ fn source_candidates<'py>( let mut pending: Vec> = entries.to_vec(); let mut relations: Vec> = Vec::new(); let catalog = call_with( - &imported(py, ACTIVE_CATALOG.0, ACTIVE_CATALOG.1)?, + &host::entry(py, ACTIVE_CATALOG)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs index 81872bf21a..40177197f8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs @@ -1,7 +1,8 @@ //! Gather a frozen point-in-time warehouse snapshot for planning. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::full_refresh::effectively_full_refreshed_model_names; use crate::bindings::_helpers::planner_phases::objects::{ by_name, frozenset, model_key, native, text, tuple, @@ -11,6 +12,8 @@ use crate::bindings::_helpers::planner_warehouse::constants::{ RELATIONS_MAIN_COLUMNS, RELATIONS_MAIN_LIST, }; use crate::bindings::_helpers::planner_warehouse::cursors::{CursorInputs, cursor_snapshots}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; @@ -80,11 +83,10 @@ fn relevant_state_keys<'py>( if selected.is_none() { return Ok(selected.clone()); } - let deps = - imported(py, "sqlbuild._native", "build_execution_upstream_deps")?.call1((project,))?; + let deps = registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((project,))?; native( py, - "transitive_closure_many", + NativeAttr::TransitiveClosureMany, &[ ("starts", selected.clone()), ("edges", deps), @@ -205,7 +207,7 @@ fn metadata_name_filter( } let intermediate = native( py, - "build_upstream_intermediate_source_map", + NativeAttr::BuildUpstreamIntermediateSourceMap, &[ ("project", project.clone()), ("selected_keys", selected.clone()), @@ -234,7 +236,7 @@ fn active_catalog<'py>( connection: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(adapter.py(), ACTIVE_CATALOG.0, ACTIVE_CATALOG.1)?, + &host::entry(adapter.py(), ACTIVE_CATALOG)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -300,7 +302,7 @@ pub(crate) fn list_relations<'py>( names: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(adapter.py(), RELATIONS_MAIN_LIST.0, RELATIONS_MAIN_LIST.1)?, + &host::entry(adapter.py(), RELATIONS_MAIN_LIST)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -317,11 +319,7 @@ pub(crate) fn columns_for<'py>( relations: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported( - adapter.py(), - RELATIONS_MAIN_COLUMNS.0, - RELATIONS_MAIN_COLUMNS.1, - )?, + &host::entry(adapter.py(), RELATIONS_MAIN_COLUMNS)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -486,7 +484,7 @@ fn gather_fingerprints<'py>( ) -> PyResult> { let py = request.project.py(); let Some(schemas) = schemas else { - return native(py, "WarehouseFingerprints", &[]); + return native(py, RecordId::WarehouseFingerprints, &[]); }; let node_names: Option> = if relevant.is_none() { None @@ -521,7 +519,7 @@ fn gather_fingerprints<'py>( let schema_lower = lower(&text(py, schema))?; let mut set = native( py, - "read_latest_fingerprints", + NativeAttr::ReadLatestFingerprints, &[ ("connection", request.connection.clone()), ("execute", request.execute.clone()), @@ -570,7 +568,7 @@ fn gather_fingerprints<'py>( if node_names.is_some() { set = native( py, - "select_latest_fingerprints", + NativeAttr::SelectLatestFingerprints, &[ ("fingerprint_set", set), ("node_names", node_names_value.clone()), @@ -605,7 +603,7 @@ fn gather_fingerprints<'py>( } native( py, - "WarehouseFingerprints", + RecordId::WarehouseFingerprints, &[ ("models", models.into_any()), ("functions", functions.into_any()), @@ -636,7 +634,7 @@ pub(crate) fn warehouse_snapshot<'py>( let schemas = target_schemas(project, &relevant)?; let metadata_names = metadata_name_filter(project, &relevant)?; if schemas.is_empty() && metadata_names.is_none() { - return native(py, "WarehouseSnapshot", &[]); + return native(py, RecordId::WarehouseSnapshot, &[]); } let query_schemas = (!schemas.is_empty()).then_some(&schemas); prefetch_schemas(request, &database, &schemas, &relevant)?; @@ -692,7 +690,7 @@ pub(crate) fn warehouse_snapshot<'py>( }; native( py, - "WarehouseSnapshot", + RecordId::WarehouseSnapshot, &[ ("existing_relations", relations.into_any()), ("existing_columns", columns.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs index 36f4cdcce9..7b3260e026 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs @@ -1,6 +1,7 @@ //! Warehouse source relation listing and column gathering for planning. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{frozenset, text, tuple}; use crate::bindings::_helpers::planner_warehouse::constants::{ COLUMN_INFO, METADATA_NAME_FILTER_LIMIT, RELATION_LOOKUP, @@ -84,7 +85,7 @@ pub(crate) fn source_inspection<'py>( let mut listed: Vec> = Vec::new(); let mut listed_names: HashSet = HashSet::new(); let mut groups: Vec<(String, BTreeSet)> = Vec::new(); - let column_info = imported(py, COLUMN_INFO.0, COLUMN_INFO.1)?; + let column_info = host::entry(py, COLUMN_INFO)?; for entry in &entries { let expression = entry.getattr("expression")?; if !expression.is_none() { @@ -120,7 +121,7 @@ pub(crate) fn source_inspection<'py>( None => groups.push((key, BTreeSet::from([schema]))), } } - let lookup = imported(py, RELATION_LOOKUP.0, RELATION_LOOKUP.1)?.getattr("key")?; + let lookup = host::entry(py, RELATION_LOOKUP)?.getattr("key")?; for (key, schemas) in groups { let database = (!key.is_empty()).then_some(key.as_str()); let database_value = match database { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs index be0af28693..d31120fc7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs @@ -1,13 +1,16 @@ //! Source read deferral for managed source loaders. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_phases::objects::{ native, optional_text, planner_error, replace, text, }; use crate::bindings::_helpers::planner_warehouse::constants::{ MODEL_HOOK_CONFIG_KEYS, SQL_HOOK_ENTRY, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; use pyo3::types::PyDict as KeywordDict; @@ -43,9 +46,8 @@ fn hook_source_reads( ) -> PyResult> { let mut names: Vec = Vec::new(); let py = model.py(); - let hook_entry = imported(py, SQL_HOOK_ENTRY.0, SQL_HOOK_ENTRY.1)?; - let source_kind = - imported(py, "sqlbuild.python_nodes.types", "SqlResourceRefKind")?.getattr("SOURCE")?; + let hook_entry = registry::class(py, SQL_HOOK_ENTRY)?; + let source_kind = host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; let values = model.getattr("config")?.getattr("values")?; for key in MODEL_HOOK_CONFIG_KEYS { let value = values.call_method1("get", (key,))?; @@ -127,7 +129,7 @@ fn resolve_target<'py>( ) -> PyResult> { native( project_config.py(), - "resolve_target_config", + NativeAttr::ResolveTargetConfig, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -191,7 +193,7 @@ fn target_field<'py>( Ok(Some( native( py, - "render_project_var_text", + NativeAttr::RenderProjectVarText, &[ ("value", effective_vars.get_item(name)?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs index fcc5658781..6fa37b875c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs @@ -5,11 +5,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::freshness::planning::{ build_direct_source_freshness_planning_result, build_direct_source_freshness_propagation_result, }; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{ model_key_names, native, replace, text, tuple, }; use crate::bindings::_helpers::planner_warehouse::constants::RUN_SCOPE; use crate::bindings::_helpers::planner_warehouse::snapshot::{destination_nodes, lower}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::PyDict as KeywordDict; use pyo3::types::{PyBool, PyDict, PyDictMethods}; @@ -47,12 +50,12 @@ pub(crate) fn source_freshness_unknown_warnings<'py>( if let Some(message) = freshness_unknown_message(&reason, &names) { warnings.push(native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", py.None().into_bound(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, ), ("message", text(py, &message)), ], @@ -148,7 +151,7 @@ pub(crate) fn build_planner_source_freshness_result<'py>( let observed_at = imported(py, "datetime", "datetime")? .call_method1("now", (imported(py, "datetime", "UTC")?,))?; let result = within( - &imported(py, RUN_SCOPE.0, RUN_SCOPE.1)?.call1((project.getattr("run_id")?,))?, + &host::entry(py, RUN_SCOPE)?.call1((project.getattr("run_id")?,))?, |_| { call_with( wrap_pyfunction!(build_direct_source_freshness_planning_result, py)?.as_any(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs index 60dcbb90af..ea54c1ccd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs @@ -2,11 +2,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{planner_error, tuple}; use crate::bindings::_helpers::planner_warehouse::constants::{ MISSING_SOURCE_TABLE_CODE, RENDER_SOURCE_READ, RUN_BOUNDED, WILDCARD_TABLE_CHARACTER, YAML_ENTRY_LINE, }; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -65,7 +67,7 @@ fn read_relation<'py>( entry: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(adapter.py(), RENDER_SOURCE_READ.0, RENDER_SOURCE_READ.1)?, + ®istry::native(adapter.py(), RENDER_SOURCE_READ)?, &[ ("adapter", adapter.clone()), ("source_entry", entry.clone()), @@ -80,7 +82,7 @@ fn declaration_location(source: &Bound<'_, PyAny>) -> PyResult { .call_method0("as_posix")? .extract()?; let line = call_with( - &imported(source.py(), YAML_ENTRY_LINE.0, YAML_ENTRY_LINE.1)?, + ®istry::native(source.py(), YAML_ENTRY_LINE)?, &[ ("contents", file.getattr("contents")?), ("name", source.getattr("name")?), @@ -142,7 +144,7 @@ pub(crate) fn check_selected_source_tables_exist<'py>( .call1((adapter.getattr("metadata_inspection_concurrency")?,))? .extract()?; let probes = call_with( - &imported(py, RUN_BOUNDED.0, RUN_BOUNDED.1)?, + &host::entry(py, RUN_BOUNDED)?, &[ ("tasks", tuple(py, tasks)?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs index 42ab6ce201..ab046e7c97 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs @@ -4,6 +4,8 @@ use crate::bindings::_helpers::planner_phases::objects::{config_str, planner_err use crate::bindings::_helpers::planner_warehouse::constants::{ CURSOR_TYPE_INTEGER, CURSOR_TYPE_TIMESTAMP, WATERMARK_CONTRACT_CODE, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods}; @@ -21,12 +23,8 @@ pub(crate) fn watermark_type_is_compatible( else { return Ok(true); }; - let classified = crate::bindings::_helpers::compile_driver::python_calls::imported( - declared_type.py(), - "sqlbuild._native", - "classify_cursor_sql_type", - )? - .call1((declared_type,))?; + let classified = registry::native(declared_type.py(), NativeAttr::ClassifyCursorSqlType)? + .call1((declared_type,))?; classified.eq(expected) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs index a4cb860f93..6df67839b2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs @@ -23,9 +23,10 @@ use crate::bindings::_helpers::model_config::template_data::record_reads; use crate::bindings::_helpers::project_assembly::models::{ NativeProjectResources, NativeResourceFailure, }; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const OS_MODULE: &str = "os"; const PROJECT_ASSEMBLY_CONTEXT: &str = "native project assembly"; const GENERIC_DIALECT: &str = "generic"; @@ -344,7 +345,7 @@ fn failure_error<'py>( ) -> PyResult> { match error { AssemblyError::CompileInput { message, help } => { - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call( + registry::error(py, ErrorId::CompileInput)?.call( (message,), Some(&keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs index 28fb19a8fc..72415f7c9e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs @@ -11,12 +11,9 @@ use sqlbuild_analysis::assembly::project::main::sql_analysis_opt_out_help::sql_a use sqlbuild_analysis::assembly::project::models::{SyntaxCheck, SyntaxMode}; use crate::bindings::_helpers::project_assembly::resources::syntax_error_message; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const SQL_HOOK_ENTRY: &str = "SqlHookEntry"; -const PYTHON_HOOK_ENTRY: &str = "PythonHookEntry"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; const GENERIC_DIALECT: &str = "generic"; /// `(placeholder defaults, dialect)` a statement parses with. @@ -44,10 +41,8 @@ fn syntax_error(py: Python<'_>, message: String) -> PyErr { let raised = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", sql_analysis_opt_out_help(true))?; - let error: Bound<'_, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) @@ -158,7 +153,6 @@ fn validate_hook_sql_syntax( let (model_name, file_path) = model; let (placeholders, dialect) = parsing; let py: Python<'_> = value.py(); - let models: Bound<'_, PyModule> = py.import(DISCOVERY_MODELS_MODULE)?; let dialect: String = dialect .filter(|name| !name.is_empty()) .unwrap_or_else(|| GENERIC_DIALECT.to_owned()); @@ -167,8 +161,8 @@ fn validate_hook_sql_syntax( model_name: &model_name, placeholders: &placeholders, dialect: &dialect, - sql_hook_entry: models.getattr(SQL_HOOK_ENTRY)?, - python_hook_entry: models.getattr(PYTHON_HOOK_ENTRY)?, + sql_hook_entry: registry::class(py, RecordId::SqlHookEntry)?, + python_hook_entry: registry::class(py, RecordId::PythonHookEntry)?, }; check_hook_value( &context, @@ -234,10 +228,7 @@ pub(crate) fn hook_statements( /// The hook SQL strings a hook value holds, in validation order. #[pyfunction] pub(crate) fn hook_sql_statements(value: Bound<'_, PyAny>) -> PyResult> { - let sql_hook_entry: Bound<'_, PyAny> = value - .py() - .import(DISCOVERY_MODELS_MODULE)? - .getattr(SQL_HOOK_ENTRY)?; + let sql_hook_entry: Bound<'_, PyAny> = registry::class(value.py(), RecordId::SqlHookEntry)?; hook_statements(&value, &sql_hook_entry) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs index 73b24b16fd..45ccf21c8d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs @@ -6,14 +6,11 @@ use pyo3::types::{PyBool, PyDict}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::query_command::main::render_dag_summary::render_dag_summary; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; use crate::bindings::_helpers::query_command::project::{ProjectAdapter, project_adapter}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; fn array_length(document: &serde_json::Value, key: &str) -> PyResult { document @@ -46,18 +43,14 @@ fn run_dag_command<'py>(py: Python<'py>, options: Option>) -> adapter, } = project_adapter(py, project_dir)?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", project_dir), ("discovered_inputs", discovered.clone()), ], )?; let graph: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_graph")?, + ®istry::native(py, NativeAttr::BuildProjectGraph)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter), @@ -71,7 +64,7 @@ fn run_dag_command<'py>(py: Python<'py>, options: Option>) -> )?; let python_graph: Bound<'py, PyAny> = python_node_graph(&discovered)?; let dag_json: String = call_with( - &imported(py, NATIVE_MODULE, "build_dag_json")?, + ®istry::native(py, NativeAttr::BuildDagJson)?, &[ ("graph", graph), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs b/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs index 2368f1e8c6..11fe3cdf69 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs @@ -4,9 +4,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// The project directory, defaulting to the working directory, with its inputs and adapter. pub(crate) struct ProjectAdapter<'py> { @@ -24,11 +23,11 @@ pub(crate) fn project_adapter<'py>( None => imported(py, "pathlib", "Path")?.call_method0("cwd")?, }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs index f8eb8af746..698ba6b7d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs @@ -13,17 +13,16 @@ use sqlbuild_core::query_command::main::render_query_long::render_query_long; use sqlbuild_core::query_command::main::render_query_table::render_query_table; use sqlbuild_core::query_command::models::QueryDisplay; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::query_command::project::{ProjectAdapter, project_adapter}; use crate::bindings::_helpers::query_command::query_values::{ csv_text, display_text, json_cell, python_json_rows, strict_width, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_ERRORS_MODULE: &str = "sqlbuild.adapter.contract.exceptions"; const LONG_FORMAT: &str = "long"; const TABLE_FORMAT: &str = "table"; const JSON_FORMAT: &str = "json"; @@ -41,7 +40,7 @@ fn user_error(py: Python<'_>, message: &str, code: Option<&str>, help: Option<&s if let Some(help) = help { options.set_item("help", help)?; } - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -147,7 +146,7 @@ fn run_sql<'py>( ) -> PyResult> { let py: Python<'py> = sql.py(); let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", project.discovered.clone()), ("project_dir", project.project_dir.clone()), @@ -173,7 +172,7 @@ fn run_sql<'py>( ], ); let queried: PyResult> = queried.map_err(|error| { - let adapter_error: bool = imported(py, ADAPTER_ERRORS_MODULE, "AdapterUserError") + let adapter_error: bool = host::entry(py, AdapterHost::AdapterUserError) .is_ok_and(|class| error.value(py).is_instance(&class).unwrap_or(false)); if adapter_error || !error.is_instance_of::(py) { return error; @@ -183,9 +182,9 @@ fn run_sql<'py>( Ok(message) => message.str().map(Bound::into_any), Err(_) => value.str().map(Bound::into_any), }; - let wrapped: PyErr = match message.and_then(|message| { - imported(py, ERRORS_MODULE, "QueryExecutionError")?.call1((message,)) - }) { + let wrapped: PyErr = match message + .and_then(|message| registry::error(py, ErrorId::QueryExecution)?.call1((message,))) + { Ok(raised) => PyErr::from_value(raised), Err(failure) => failure, }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs index 737a0b63b1..e963885efc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs @@ -8,16 +8,12 @@ use sqlbuild_model_config::header_metadata::main::check_threshold_bound::check_t use sqlbuild_model_config::header_metadata::main::check_threshold_pair::check_threshold_pair; use sqlbuild_model_config::header_metadata::models::{ThresholdBound, ThresholdInput}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; - -const MEASUREMENT_AUDIT_ERROR: &str = "MeasurementAuditError"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// The `MeasurementAuditError` carrying `message`. fn measurement_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, NATIVE_MODULE, MEASUREMENT_AUDIT_ERROR) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::MeasurementAudit).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs index d04e1d558e..2bb74087d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs @@ -5,7 +5,8 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::relation_keys::entry_relation_key_row; /// Module of the compiled resource type enum. @@ -41,6 +42,13 @@ fn coerce(py: Python<'_>, value: &Py, (module, class): (&str, &str)) -> P .unbind()) } +/// The native string enum `class(value)`. +fn coerce_native(py: Python<'_>, value: &Py, class: RecordId) -> PyResult> { + Ok(registry::class(py, class)? + .call1((value.bind(py),))? + .unbind()) +} + /// `value` coerced to `CompiledResourceType` unless it is `None`. fn coerce_resource_type(py: Python<'_>, value: &Py) -> PyResult> { if value.is_none(py) { @@ -54,15 +62,15 @@ pub(crate) fn finish_compiler_diagnostic( py: Python<'_>, mut values: Vec>, ) -> PyResult>> { - values[DIAGNOSTIC_PHASE_FIELD] = coerce( + values[DIAGNOSTIC_PHASE_FIELD] = coerce_native( py, &values[DIAGNOSTIC_PHASE_FIELD], - (NATIVE_MODULE, "DiagnosticPhase"), + RecordId::DiagnosticPhase, )?; - values[DIAGNOSTIC_SEVERITY_FIELD] = coerce( + values[DIAGNOSTIC_SEVERITY_FIELD] = coerce_native( py, &values[DIAGNOSTIC_SEVERITY_FIELD], - (NATIVE_MODULE, "DiagnosticSeverity"), + RecordId::DiagnosticSeverity, )?; values[DIAGNOSTIC_RESOURCE_TYPE_FIELD] = coerce_resource_type(py, &values[DIAGNOSTIC_RESOURCE_TYPE_FIELD])?; @@ -79,7 +87,7 @@ pub(crate) fn finish_compiler_diagnostic( keywords.set_item("line", values[DIAGNOSTIC_LINE_FIELD].bind(py))?; keywords.set_item("column", values[DIAGNOSTIC_COLUMN_FIELD].bind(py))?; let location: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "SourceLocation")?.call((), Some(&keywords))?; + registry::class(py, RecordId::SourceLocation)?.call((), Some(&keywords))?; values[DIAGNOSTIC_LOCATION_FIELD] = location.unbind(); } let location: Bound<'_, PyAny> = values[DIAGNOSTIC_LOCATION_FIELD].bind(py).clone(); @@ -95,7 +103,7 @@ pub(crate) fn finish_compiler_diagnostic( pub(crate) fn diagnostic_is_error(instance: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = instance.py(); let error: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "DiagnosticSeverity")?.getattr("ERROR")?; + registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?; instance.getattr("severity")?.eq(error)?.into_py_any(py) } @@ -104,7 +112,7 @@ pub(crate) fn finish_sql_reference( py: Python<'_>, mut values: Vec>, ) -> PyResult>> { - values[0] = coerce(py, &values[0], (NATIVE_MODULE, "SqlReferenceKind"))?; + values[0] = coerce_native(py, &values[0], RecordId::SqlReferenceKind)?; Ok(values) } @@ -362,7 +370,7 @@ pub(crate) fn build_context_declaration_expansion( let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item( "declarations", - imported(py, NATIVE_MODULE, "DeclarationResolutionContext")? + registry::class(py, RecordId::DeclarationResolutionContext)? .call((), Some(&declarations))?, )?; keywords.set_item("value_renderer", instance.getattr("value_renderer")?)?; @@ -371,7 +379,7 @@ pub(crate) fn build_context_declaration_expansion( instance.getattr("collection_rendering")?, )?; keywords.set_item("resolver", instance.getattr("declaration_resolver")?)?; - Ok(imported(py, NATIVE_MODULE, "DeclarationExpansionContext")? + Ok(registry::class(py, RecordId::DeclarationExpansionContext)? .call((), Some(&keywords))? .unbind()) } @@ -379,6 +387,6 @@ pub(crate) fn build_context_declaration_expansion( /// A `CompileInputError` carrying `message`. pub(crate) fn compile_input_error(py: Python<'_>, message: &str) -> PyResult { Ok(PyErr::from_value( - imported(py, NATIVE_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs index 9fc69a029e..803d2a8ebd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs @@ -5,14 +5,19 @@ use pyo3::types::{PyString, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::scalars::parse_value; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// One cursor value conversion. type Conversion = for<'py> fn(&Bound<'py, PyAny>) -> PyResult>; -const SCALAR_CLASSES: [&str; 3] = ["TimestampValue", "DateValue", "IntegerValue"]; -const CURSOR_TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; -const BOUND_SENTINEL: &str = "BoundSentinel"; +const SCALAR_CLASSES: [RecordId; 3] = [ + RecordId::TimestampValue, + RecordId::DateValue, + RecordId::IntegerValue, +]; const CURSOR_ERRORS_MODULE: &str = "sqlbuild.cursor_algebra.exceptions"; const CURSOR_ALGEBRA_ERROR: &str = "CursorAlgebraError"; @@ -20,7 +25,7 @@ const CURSOR_ALGEBRA_ERROR: &str = "CursorAlgebraError"; pub(crate) fn inferred<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = raw.py(); for class in SCALAR_CLASSES { - if raw.is_instance(&imported(py, NATIVE_MODULE, class)?)? { + if raw.is_instance(®istry::class(py, class)?)? { return Ok(raw.clone()); } } @@ -49,7 +54,7 @@ pub(crate) fn optional_inferred<'py>(raw: &Bound<'py, PyAny>) -> PyResult(raw: &Bound<'py, PyAny>) -> PyResult> { - let sentinel: Bound<'py, PyAny> = imported(raw.py(), CURSOR_TYPES_MODULE, BOUND_SENTINEL)?; + let sentinel: Bound<'py, PyAny> = host::entry(raw.py(), ExecutorHost::BoundSentinel)?; if raw.is_instance(&sentinel)? { return Ok(raw.clone()); } @@ -115,7 +120,7 @@ pub(crate) fn finish_cursor_snapshot( .collect::>()?; values[13] = PyTuple::new(py, pairs)?.into_any().unbind(); let mode: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "CursorWatermarkMode")?.call1((values[10].bind(py),))?; + registry::class(py, RecordId::CursorWatermarkMode)?.call1((values[10].bind(py),))?; values[10] = mode.unbind(); Ok(values) } @@ -172,7 +177,7 @@ pub(crate) fn finish_input_relation( ) -> PyResult>> { if !values[2].bind(py).is_none() { let grain: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "CursorGrain")?.call1((values[2].bind(py),))?; + registry::class(py, RecordId::CursorGrain)?.call1((values[2].bind(py),))?; values[2] = grain.unbind(); } converted(py, values, (&[5, 6], optional_inferred)) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs index 7264226e08..0fdfe4b00e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs @@ -3,9 +3,9 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyDict; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::freshness::observation::require_aware_utc_datetime; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `SourceFreshnessRecord.observed_at` position. const OBSERVED_AT_FIELD: usize = 9; @@ -34,7 +34,7 @@ pub(crate) fn freshness_record_identity(instance: &Bound<'_, PyAny>) -> PyResult for name in IDENTITY_FIELDS { pyo3::types::PyDictMethods::set_item(&keywords, name, instance.getattr(name)?)?; } - Ok(imported(py, NATIVE_MODULE, "SourceFreshnessIdentity")? + Ok(registry::class(py, RecordId::SourceFreshnessIdentity)? .call((), Some(&keywords))? .unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs index dd5e7855fb..1160ad05c2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs @@ -10,8 +10,8 @@ use sqlbuild_sqltext::sql_scan::main::reads_differently_from_generic::reads_diff use sqlbuild_sqltext::sql_scan::main::unsupported_line_comment_prefixes::unsupported_line_comment_prefixes; use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `SqlLexicalSyntax.line_comment_prefixes` position. const LINE_COMMENT_PREFIXES_FIELD: usize = 5; @@ -56,7 +56,7 @@ pub(crate) fn finish_lexical_syntax( PyList::new(py, &supported)?.repr()? ); let error: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "UnsupportedLexicalSyntaxError")?.call1((message,))?; + registry::error(py, ErrorId::UnsupportedLexicalSyntax)?.call1((message,))?; Err(PyErr::from_value(error)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs index 21d5d48e4a..144ea2cdb4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs @@ -5,9 +5,9 @@ use pyo3::exceptions::PyIndexError; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PySlice, PyTuple, PyTupleMethods}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::record_behaviours::call_arguments::call_argument; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `ColumnTransformKind` members by native transform code. const TRANSFORM_KINDS: [&str; 6] = [ @@ -23,26 +23,24 @@ const CONFIDENCES: [&str; 3] = ["UNKNOWN", "HIGH", "MEDIUM"]; /// The `ColumnTransformKind` member of a native transform code. pub(crate) fn transform_kind_member(py: Python<'_>, code: usize) -> PyResult> { - coded_member(py, ("ColumnTransformKind", &TRANSFORM_KINDS), code) + coded_member(py, (RecordId::ColumnTransformKind, &TRANSFORM_KINDS), code) } /// The `ColumnLineageConfidence` member of a native confidence code. pub(crate) fn confidence_member(py: Python<'_>, code: usize) -> PyResult> { - coded_member(py, ("ColumnLineageConfidence", &CONFIDENCES), code) + coded_member(py, (RecordId::ColumnLineageConfidence, &CONFIDENCES), code) } /// The enum member at `code` of `members`. fn coded_member( py: Python<'_>, - (class, members): (&str, &[&str]), + (class, members): (RecordId, &[&str]), code: usize, ) -> PyResult> { let Some(member) = members.get(code) else { return Err(PyIndexError::new_err("tuple index out of range")); }; - Ok(imported(py, NATIVE_MODULE, class)? - .getattr(*member)? - .unbind()) + Ok(registry::member(py, class, member)?.unbind()) } /// `CompactLineageFacts.resource_name(index)`: the model's name for a native relation index. @@ -81,7 +79,7 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult, ) = row.extract()?; - let source_class: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "CompiledLineageSourceFact")?; + let source_class: Bound<'_, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; let mut upstream: Vec> = Vec::new(); for source in sources.try_iter()? { let source: Bound<'_, PyTuple> = source?.cast_into::()?; @@ -98,7 +96,7 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult, row: &Bound<'_, PyAny>) -> PyResult) -> PyResult { member.str()?.extract() @@ -194,7 +192,7 @@ pub(crate) fn history_status( } else { "PENDING_ARCHIVE" }; - Ok(imported(py, NATIVE_MODULE, VIEW_STATUS)? + Ok(registry::class(py, RecordId::OldNameViewStatus)? .getattr(status)? .unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs index 8c6fbbaffd..270d52fef3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs @@ -12,7 +12,8 @@ use sqlbuild_core::migration_values::models::{DecisionTraits, ModelMigrationFact use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::record_behaviours::call_arguments::call_argument; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const DECREASING_DIRECTIONS: [&str; 2] = ["decrease", "mixed"]; const STEP_ACTIONS: [&str; 2] = ["archive_and_view", "view_only"]; @@ -22,7 +23,6 @@ const SOURCE_MISSING: &str = "source_missing"; const DENY_POLICY: &str = "deny"; const INCOMPATIBLE: &str = "incompatible"; const DEFAULT_TARGET_LABEL: &str = "default"; -const PLANNER_INPUT_ERROR: &str = "PlannerInputError"; const INTEGER_CURSOR: &str = "integer"; /// `CursorOverrides` field positions: `start_ts`, `end_ts`, `start_int`, `end_int`, `start`, `end`. const OVERRIDE_FIELDS: [&str; 6] = ["start_ts", "end_ts", "start_int", "end_int", "start", "end"]; @@ -153,8 +153,7 @@ pub(crate) fn column_migration_blocks_build(entry: &Bound<'_, PyAny>) -> PyResul /// The `PlannerInputError` carrying `message`. fn planner_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, NATIVE_MODULE, PLANNER_INPUT_ERROR).and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::PlannerInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -211,7 +210,7 @@ fn override_integer<'py>( } let parsed: Bound<'py, PyAny> = parse_value(value, &PyString::new(py, INTEGER_CURSOR).into_any())?; - if parsed.is_instance(&imported(py, NATIVE_MODULE, "IntegerValue")?)? { + if parsed.is_instance(®istry::class(py, RecordId::IntegerValue)?)? { return Ok(parsed); } Err(invalid()) @@ -305,9 +304,9 @@ pub(crate) fn forward_only_backfill(py: Python<'_>) -> PyResult let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item( "action", - imported(py, NATIVE_MODULE, "BackfillAction")?.getattr("FORWARD_ONLY")?, + registry::class(py, RecordId::BackfillAction)?.getattr("FORWARD_ONLY")?, )?; - imported(py, NATIVE_MODULE, "BackfillResult")?.call((), Some(&keywords)) + registry::class(py, RecordId::BackfillResult)?.call((), Some(&keywords)) } /// `ScenarioArtifactIdentity.kind` coerced to `ScenarioArtifactKind`. @@ -316,7 +315,7 @@ pub(crate) fn finish_scenario_artifact_kind( mut values: Vec>, ) -> PyResult>> { if let Some(kind) = values.first_mut() { - *kind = imported(py, NATIVE_MODULE, "ScenarioArtifactKind")? + *kind = registry::class(py, RecordId::ScenarioArtifactKind)? .call1((kind.bind(py),))? .unbind(); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs index a535e8be08..901ee0b876 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs @@ -6,7 +6,8 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyTupleMethods}; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Keyword fields every `RuleOption` constructor requires. const REQUIRED_OPTION_FIELDS: [&str; 3] = ["name", "default", "description"]; @@ -37,7 +38,7 @@ pub(crate) fn project_path_depth(instance: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { let project: Bound<'_, PyAny> = - imported(instance.py(), NATIVE_MODULE, "RuleSubject")?.getattr("PROJECT")?; + registry::class(instance.py(), RecordId::RuleSubject)?.getattr("PROJECT")?; instance .getattr("subject")? .is(&project) @@ -59,7 +60,7 @@ pub(crate) fn rule_option_class_getitem( _kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { let py: Python<'_> = receiver.py(); - let class: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "RuleOption")?; + let class: Bound<'_, PyAny> = registry::class(py, RecordId::RuleOption)?; Ok(imported(py, "types", "GenericAlias")? .call1((class, args.get_item(0)?))? .unbind()) @@ -100,7 +101,7 @@ fn rule_option( for (name, value) in extra { fields.set_item(*name, value)?; } - Ok(imported(py, NATIVE_MODULE, "RuleOption")? + Ok(registry::class(py, RecordId::RuleOption)? .call((), Some(&fields))? .unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs index 09870bba21..d537996df5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs @@ -6,6 +6,7 @@ use crate::bindings::_helpers::compiler_errors::constants::COMPILER_ERRORS; use crate::bindings::_helpers::compiler_records::constants::COMPILER_RECORDS; use crate::bindings::_helpers::contract_records::constants::CONTRACT_RECORDS; use crate::bindings::_helpers::freshness::observation::normalize_observation; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::planner_records::constants::{ PLANNER_ENUMS, PLANNER_RECORDS, PLANNER_VALUE_RECORDS, }; @@ -13,6 +14,7 @@ use crate::bindings::_helpers::record_behaviours::constants::{ BEHAVIOUR_CACHED_PROPERTIES, BEHAVIOUR_FINISHERS, BEHAVIOUR_METHODS, BEHAVIOUR_ORDERED, BEHAVIOUR_PROPERTIES, BEHAVIOUR_SEQUENCES, }; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{ ClassValue, EnumSpec, ErrorSpec, FieldDefault, FieldSpec, RecordSpec, }; @@ -283,7 +285,7 @@ const BUILD_INVOCATION: RecordSpec = RecordSpec { FieldSpec::new("effective_target_name", FieldDefault::None), FieldSpec::new( "execution_limits", - FieldDefault::Call("sqlbuild._native", "ExecutionLimitsConfig"), + FieldDefault::NativeCall(RecordId::ExecutionLimitsConfig), ), ], }; @@ -582,7 +584,7 @@ const SCENARIO_TEST_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("refresh", FieldDefault::False), FieldSpec::new( "limit_inputs", - FieldDefault::Call("sqlbuild._native", "ScenarioSnapshotLimitInputs"), + FieldDefault::NativeCall(RecordId::ScenarioSnapshotLimitInputs), ), FieldSpec::new("json_output", FieldDefault::False), FieldSpec::new("json_output_path", FieldDefault::None), @@ -601,7 +603,7 @@ const SCENARIO_CAPTURE_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("retain", FieldDefault::False), FieldSpec::new( "limit_inputs", - FieldDefault::Call("sqlbuild._native", "ScenarioSnapshotLimitInputs"), + FieldDefault::NativeCall(RecordId::ScenarioSnapshotLimitInputs), ), ], }; @@ -760,7 +762,7 @@ const BUILD_COST_FINALIZATION: RecordSpec = RecordSpec { FieldSpec::new("render", FieldDefault::True), FieldSpec::new( "cost_status", - FieldDefault::Member("sqlbuild.cost.types", "CostStatus", "COMPLETE"), + FieldDefault::HostMember(ExecutorHost::CostStatus.id(), "COMPLETE"), ), FieldSpec::new( "cost_message", @@ -799,7 +801,7 @@ const LINEAGE_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("exclude", FieldDefault::EmptyTuple), FieldSpec::new( "lineage_mode", - FieldDefault::Member("sqlbuild._native", "ColumnLineageMode", "RICH"), + FieldDefault::NativeMember(RecordId::ColumnLineageMode, "RICH"), ), FieldSpec::new("include_uses", FieldDefault::False), FieldSpec::new("cli_vars", FieldDefault::None), @@ -869,14 +871,14 @@ const COMPILE_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("no_color", FieldDefault::False), FieldSpec::new( "lineage_mode", - FieldDefault::Member("sqlbuild._native", "CompileLineageMode", "FAST"), + FieldDefault::NativeMember(RecordId::CompileLineageMode, "FAST"), ), FieldSpec::new("select", FieldDefault::EmptyTuple), FieldSpec::new("exclude", FieldDefault::EmptyTuple), FieldSpec::new("cli_vars", FieldDefault::None), FieldSpec::new( "profile_flags", - FieldDefault::Call("sqlbuild._native", "CompileProfileFlags"), + FieldDefault::NativeCall(RecordId::CompileProfileFlags), ), FieldSpec::new("no_cache", FieldDefault::False), FieldSpec::new("debug", FieldDefault::False), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs index 02c32155fd..d37c1c4cda 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs @@ -10,10 +10,12 @@ use pyo3::types::{ }; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::constants::{ FROZEN_INSTANCE_ERROR, ORDERED_RECORDS, RECORD_FINISHERS, RECORD_SPEC_ATTRIBUTE, RECORDS, }; use crate::bindings::_helpers::records::models::{FieldDefault, NativeRecord, RecordSpec}; +use crate::bindings::_helpers::records::registry; /// Every registered record spec, in registration order. pub(crate) fn all_specs() -> impl Iterator { @@ -113,7 +115,10 @@ fn default_value<'py>( FieldDefault::EmptyDict => Ok(PyDict::new(py).into_any()), FieldDefault::EmptyFrozenset => Ok(PyFrozenSet::empty(py)?.into_any()), FieldDefault::Member(module, class, member) => imported(py, module, class)?.getattr(member), - FieldDefault::Call(module, name) => imported(py, module, name)?.call0(), + FieldDefault::HostMember(class, member) => host::entry(py, class)?.getattr(member), + FieldDefault::NativeMember(class, member) => registry::class(py, class)?.getattr(member), + FieldDefault::NativeCall(class) => registry::class(py, class)?.call0(), + FieldDefault::NativeClassCall(class) => registry::native(py, class)?.call0(), FieldDefault::Factory(factory) => factory(py), FieldDefault::CallText(module, name, text) => imported(py, module, name)?.call1((text,)), } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs new file mode 100644 index 0000000000..9a8610eb09 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs @@ -0,0 +1,1313 @@ +//! Typed names of what `sqlbuild._native` registers, so native code never looks them up by string. + +/// An enum of registered names with its declaration-order list and each variant's Python name. +macro_rules! native_ids { + ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $text:literal,)* }) => { + $(#[$meta])* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + $vis enum $name { + $($variant,)* + } + + impl $name { + /// Every variant, in declaration order. + pub(crate) const ALL: &'static [Self] = &[$(Self::$variant,)*]; + + /// The attribute name the variant is registered under. + pub(crate) const fn name(self) -> &'static str { + match self { + $(Self::$variant => $text,)* + } + } + } + }; +} + +native_ids! { + /// A native record class or string enum, registered from the record and enum specs. + pub(crate) enum RecordId { + AdapterConnectionContext => "AdapterConnectionContext", + AdapterSourceFreshnessBatch => "AdapterSourceFreshnessBatch", + AlignedInterval => "AlignedInterval", + AnalysisCacheContext => "AnalysisCacheContext", + AttachedAuditGateEdge => "AttachedAuditGateEdge", + AuditCommandRequest => "AuditCommandRequest", + AuditGateIdentity => "AuditGateIdentity", + AuditIdentity => "AuditIdentity", + AuditInvocation => "AuditInvocation", + AuditPlanEntry => "AuditPlanEntry", + AuthoredSqlSet => "AuthoredSqlSet", + AuthoredSqlValueCall => "AuthoredSqlValueCall", + AuthoredTimeTravelRetention => "AuthoredTimeTravelRetention", + BackfillAction => "BackfillAction", + BackfillResult => "BackfillResult", + BuildCommandRequest => "BuildCommandRequest", + BuildCostFinalization => "BuildCostFinalization", + BuildInvocation => "BuildInvocation", + ChainStep => "ChainStep", + ChangeDetectionResult => "ChangeDetectionResult", + ChangeKind => "ChangeKind", + CheckCommandRequest => "CheckCommandRequest", + CheckInvocation => "CheckInvocation", + CliCommand => "CliCommand", + CliEntrypointHandlers => "CliEntrypointHandlers", + CloneCommandRequest => "CloneCommandRequest", + ClonePipelineConnection => "ClonePipelineConnection", + ClonePipelineOptions => "ClonePipelineOptions", + ClonePipelineResult => "ClonePipelineResult", + ClonePolicy => "ClonePolicy", + CloneSourcePlanEntry => "CloneSourcePlanEntry", + CollectionRendering => "CollectionRendering", + ColumnContractMode => "ColumnContractMode", + ColumnLineage => "ColumnLineage", + ColumnLineageConfidence => "ColumnLineageConfidence", + ColumnLineageEdge => "ColumnLineageEdge", + ColumnLineageMode => "ColumnLineageMode", + ColumnLineageSource => "ColumnLineageSource", + ColumnMigrationDecision => "ColumnMigrationDecision", + ColumnMigrationEvent => "ColumnMigrationEvent", + ColumnMigrationPlanEntry => "ColumnMigrationPlanEntry", + ColumnMigrationPlanning => "ColumnMigrationPlanning", + ColumnRenameHint => "ColumnRenameHint", + ColumnTransformKind => "ColumnTransformKind", + CommandWarehouseScope => "CommandWarehouseScope", + CompactLineageFacts => "CompactLineageFacts", + CompileAdapterContext => "CompileAdapterContext", + CompileAnalysis => "CompileAnalysis", + CompileAnalysisSelection => "CompileAnalysisSelection", + CompileCommandRequest => "CompileCommandRequest", + CompileDirectLogicSqlTestInputPayload => "CompileDirectLogicSqlTestInputPayload", + CompileFacts => "CompileFacts", + CompileLineageMode => "CompileLineageMode", + CompileModelConfig => "CompileModelConfig", + CompileModelInput => "CompileModelInput", + CompileModelSqlTestInputPayload => "CompileModelSqlTestInputPayload", + CompilePipelineOptions => "CompilePipelineOptions", + CompilePipelineResult => "CompilePipelineResult", + CompileProfileFlags => "CompileProfileFlags", + CompileProjectInputs => "CompileProjectInputs", + CompileSqlReference => "CompileSqlReference", + CompileSqlScenarioCte => "CompileSqlScenarioCte", + CompileSqlScenarioInput => "CompileSqlScenarioInput", + CompileSqlTestCte => "CompileSqlTestCte", + CompileSqlTestInput => "CompileSqlTestInput", + CompiledAudit => "CompiledAudit", + CompiledDirectLogicSqlTestPayload => "CompiledDirectLogicSqlTestPayload", + CompiledFunction => "CompiledFunction", + CompiledLineageColumnFact => "CompiledLineageColumnFact", + CompiledLineageSourceFact => "CompiledLineageSourceFact", + CompiledModel => "CompiledModel", + CompiledModelSqlTestPayload => "CompiledModelSqlTestPayload", + CompiledObjectKey => "CompiledObjectKey", + CompiledProject => "CompiledProject", + CompiledProjectPhaseResult => "CompiledProjectPhaseResult", + CompiledRelationLocation => "CompiledRelationLocation", + CompiledSeed => "CompiledSeed", + CompiledSource => "CompiledSource", + CompiledSqlExpansion => "CompiledSqlExpansion", + CompiledSqlScenario => "CompiledSqlScenario", + CompiledSqlTest => "CompiledSqlTest", + CompiledSqlTestResource => "CompiledSqlTestResource", + CompilerDiagnostic => "CompilerDiagnostic", + CompilerStage => "CompilerStage", + CompletionState => "CompletionState", + ConstantDeclaration => "ConstantDeclaration", + ConstantMetadata => "ConstantMetadata", + ConstantsConfig => "ConstantsConfig", + ContractAdoptionResult => "ContractAdoptionResult", + ContractCommandRequest => "ContractCommandRequest", + ContractEvidence => "ContractEvidence", + ContractFinding => "ContractFinding", + ContractFindingKind => "ContractFindingKind", + ContractPolicy => "ContractPolicy", + ContractValidationResult => "ContractValidationResult", + CostCommandRequest => "CostCommandRequest", + CostConfig => "CostConfig", + CostRunRecord => "CostRunRecord", + CursorBounds => "CursorBounds", + CursorGrain => "CursorGrain", + CursorInputEvidence => "CursorInputEvidence", + CursorInputRelation => "CursorInputRelation", + CursorInputRoles => "CursorInputRoles", + CursorOverridePair => "CursorOverridePair", + CursorOverrides => "CursorOverrides", + CursorSnapshotScope => "CursorSnapshotScope", + CursorType => "CursorType", + CursorWatermarkMode => "CursorWatermarkMode", + CursorsConfig => "CursorsConfig", + CustomHostPartition => "CustomHostPartition", + CustomRuleEvaluation => "CustomRuleEvaluation", + CustomRuleRun => "CustomRuleRun", + CustomRulesOutcome => "CustomRulesOutcome", + DateValue => "DateValue", + DbtCliConfigOverrides => "DbtCliConfigOverrides", + DbtCliOptions => "DbtCliOptions", + DbtCommandExecutionResult => "DbtCommandExecutionResult", + DbtCommandResult => "DbtCommandResult", + DbtConfig => "DbtConfig", + DbtInitCommandRequest => "DbtInitCommandRequest", + DbtInitProgressCallbacks => "DbtInitProgressCallbacks", + DbtInitRequest => "DbtInitRequest", + DbtInitResult => "DbtInitResult", + DbtInteropCommand => "DbtInteropCommand", + DbtInteropCommandArgs => "DbtInteropCommandArgs", + DbtInteropCompiledProject => "DbtInteropCompiledProject", + DbtInteropExecutionRequest => "DbtInteropExecutionRequest", + DbtInteropInvocation => "DbtInteropInvocation", + DbtInteropParsedArgs => "DbtInteropParsedArgs", + DbtInteropPlan => "DbtInteropPlan", + DbtInteropPlanResolution => "DbtInteropPlanResolution", + DbtInteropRoutedArgs => "DbtInteropRoutedArgs", + DbtInteropSelectionResult => "DbtInteropSelectionResult", + DbtInteropSkipReason => "DbtInteropSkipReason", + DbtLsNode => "DbtLsNode", + DbtLsResult => "DbtLsResult", + DbtManifestIndex => "DbtManifestIndex", + DbtManifestModel => "DbtManifestModel", + DbtManifestSeed => "DbtManifestSeed", + DbtManifestSource => "DbtManifestSource", + DbtNodeExecutionResult => "DbtNodeExecutionResult", + DbtNodeMessage => "DbtNodeMessage", + DbtPlanEnvironment => "DbtPlanEnvironment", + DbtSqlbuildPlanRequest => "DbtSqlbuildPlanRequest", + DbtSqlbuildWorkContext => "DbtSqlbuildWorkContext", + DeclarationExpansionContext => "DeclarationExpansionContext", + DeclarationExplanation => "DeclarationExplanation", + DeclarationIdentity => "DeclarationIdentity", + DeclarationKind => "DeclarationKind", + DeclarationRecord => "DeclarationRecord", + DeclarationReport => "DeclarationReport", + DeclarationResolutionContext => "DeclarationResolutionContext", + DeclarationRuntimeProjection => "DeclarationRuntimeProjection", + DeclarationScopeBuild => "DeclarationScopeBuild", + DeclarationScopeResolver => "DeclarationScopeResolver", + DeclarationVisibility => "DeclarationVisibility", + DeclarationVisibilityIndex => "DeclarationVisibilityIndex", + DefaultsConfig => "DefaultsConfig", + DeferralInputs => "DeferralInputs", + DiagnosticPhase => "DiagnosticPhase", + DiagnosticSeverity => "DiagnosticSeverity", + DiffCommandRequest => "DiffCommandRequest", + DiffConfig => "DiffConfig", + DirectModelVersionIdentities => "DirectModelVersionIdentities", + DirectSemanticColumnUse => "DirectSemanticColumnUse", + DirectSourceFreshnessPlanningResult => "DirectSourceFreshnessPlanningResult", + DirectSourceFreshnessPropagationResult => "DirectSourceFreshnessPropagationResult", + DiscoveredAdapterFile => "DiscoveredAdapterFile", + DiscoveredAssetFunction => "DiscoveredAssetFunction", + DiscoveredAuditBlock => "DiscoveredAuditBlock", + DiscoveredAuditFactory => "DiscoveredAuditFactory", + DiscoveredAuditFile => "DiscoveredAuditFile", + DiscoveredCheckFunction => "DiscoveredCheckFunction", + DiscoveredCommandOutputSink => "DiscoveredCommandOutputSink", + DiscoveredCommandOutputSinkDeclaration => "DiscoveredCommandOutputSinkDeclaration", + DiscoveredConstantFile => "DiscoveredConstantFile", + DiscoveredEnumFile => "DiscoveredEnumFile", + DiscoveredEventExporter => "DiscoveredEventExporter", + DiscoveredEventExporterDeclaration => "DiscoveredEventExporterDeclaration", + DiscoveredHookFunction => "DiscoveredHookFunction", + DiscoveredLoaderFunction => "DiscoveredLoaderFunction", + DiscoveredMacroFile => "DiscoveredMacroFile", + DiscoveredMaterializationFile => "DiscoveredMaterializationFile", + DiscoveredModelSchemaFile => "DiscoveredModelSchemaFile", + DiscoveredProjectInputs => "DiscoveredProjectInputs", + DiscoveredProvider => "DiscoveredProvider", + DiscoveredProviderUsage => "DiscoveredProviderUsage", + DiscoveredPythonAssetMetadata => "DiscoveredPythonAssetMetadata", + DiscoveredPythonCheckMetadata => "DiscoveredPythonCheckMetadata", + DiscoveredPythonFunctionFile => "DiscoveredPythonFunctionFile", + DiscoveredPythonLoaderMetadata => "DiscoveredPythonLoaderMetadata", + DiscoveredPythonNode => "DiscoveredPythonNode", + DiscoveredPythonNodeFunctions => "DiscoveredPythonNodeFunctions", + DiscoveredPythonTaskMetadata => "DiscoveredPythonTaskMetadata", + DiscoveredRuntimeExtensions => "DiscoveredRuntimeExtensions", + DiscoveredSchemaFile => "DiscoveredSchemaFile", + DiscoveredSeedFile => "DiscoveredSeedFile", + DiscoveredSourceFile => "DiscoveredSourceFile", + DiscoveredSqlFunctionFile => "DiscoveredSqlFunctionFile", + DiscoveredSqlHookFile => "DiscoveredSqlHookFile", + DiscoveredSqlScenarioFile => "DiscoveredSqlScenarioFile", + DiscoveredSqlTestBlock => "DiscoveredSqlTestBlock", + DiscoveredSqlTestCase => "DiscoveredSqlTestCase", + DiscoveredSqlTestFile => "DiscoveredSqlTestFile", + DiscoveredTaskFunction => "DiscoveredTaskFunction", + DiscoveryFileFault => "DiscoveryFileFault", + DisplayOptions => "DisplayOptions", + Duration => "Duration", + DynamicColumnContractProof => "DynamicColumnContractProof", + DynamicColumnFamilyProof => "DynamicColumnFamilyProof", + EmptyCursorInputDecision => "EmptyCursorInputDecision", + EnumDeclaration => "EnumDeclaration", + EnumMember => "EnumMember", + EnumMemberMetadata => "EnumMemberMetadata", + EnumMetadata => "EnumMetadata", + EventExportSeverity => "EventExportSeverity", + ExecutionLimitsConfig => "ExecutionLimitsConfig", + ExpandedLintFile => "ExpandedLintFile", + ExpansionSpan => "ExpansionSpan", + Finding => "Finding", + Fingerprint => "Fingerprint", + FingerprintSet => "FingerprintSet", + FixVerdict => "FixVerdict", + FixtureColumnInference => "FixtureColumnInference", + FixtureColumnMetadata => "FixtureColumnMetadata", + FixtureRelationMetadata => "FixtureRelationMetadata", + FreshnessCommandRequest => "FreshnessCommandRequest", + FunctionArgument => "FunctionArgument", + FunctionChangeResult => "FunctionChangeResult", + FunctionPlanEntry => "FunctionPlanEntry", + FunctionReturnColumn => "FunctionReturnColumn", + FutureCursorAction => "FutureCursorAction", + FutureCursorSafetyEvidence => "FutureCursorSafetyEvidence", + FutureCursorsConfig => "FutureCursorsConfig", + GrantKind => "GrantKind", + GrantRecord => "GrantRecord", + GraphEdgeExclusion => "GraphEdgeExclusion", + GraphResourceKind => "GraphResourceKind", + HandBuiltAnalysis => "HandBuiltAnalysis", + HardCodedRelationOwnerKind => "HardCodedRelationOwnerKind", + HeaderSpan => "HeaderSpan", + HistoricalInput => "HistoricalInput", + HookReadEdge => "HookReadEdge", + HookReadType => "HookReadType", + InaccessibleReason => "InaccessibleReason", + InaccessibleRecord => "InaccessibleRecord", + IncrementalMode => "IncrementalMode", + IncrementalStrategy => "IncrementalStrategy", + InferredColumn => "InferredColumn", + InitialValidFrom => "InitialValidFrom", + IntegerValue => "IntegerValue", + IntegrationLoaderConfig => "IntegrationLoaderConfig", + InterpolationSite => "InterpolationSite", + InvalidSqlReferenceCall => "InvalidSqlReferenceCall", + JanitorCommandRequest => "JanitorCommandRequest", + JanitorConfig => "JanitorConfig", + JanitorInvocation => "JanitorInvocation", + JanitorSettings => "JanitorSettings", + LayoutConfig => "LayoutConfig", + LifecycleEventSinkFilterConfig => "LifecycleEventSinkFilterConfig", + LifecycleEventSinksConfig => "LifecycleEventSinksConfig", + LineageCommandRequest => "LineageCommandRequest", + LineageGraphViews => "LineageGraphViews", + LintBody => "LintBody", + LintConfig => "LintConfig", + LintEdit => "LintEdit", + LintFileRole => "LintFileRole", + LintRunResult => "LintRunResult", + LintSeverity => "LintSeverity", + LintViolation => "LintViolation", + LiteralSqlRelation => "LiteralSqlRelation", + LoadCommandRequest => "LoadCommandRequest", + LoadedMacro => "LoadedMacro", + LoaderConnectionMode => "LoaderConnectionMode", + LoaderDestinationParts => "LoaderDestinationParts", + LocalClonePolicy => "LocalClonePolicy", + LocalConfig => "LocalConfig", + LocalDbtConfig => "LocalDbtConfig", + LocalTargetConfig => "LocalTargetConfig", + MacroContext => "MacroContext", + MacroExpansionResult => "MacroExpansionResult", + MacroMetadata => "MacroMetadata", + ManualLocation => "ManualLocation", + MappedOffset => "MappedOffset", + MaterializationDefaultsConfig => "MaterializationDefaultsConfig", + MaterializationRetentionDefaults => "MaterializationRetentionDefaults", + MaterializationType => "MaterializationType", + MaximumStartPolicyInputs => "MaximumStartPolicyInputs", + MaximumStartSafetyEvidence => "MaximumStartSafetyEvidence", + MeasurementContract => "MeasurementContract", + MeasurementThresholdBound => "MeasurementThresholdBound", + MeasurementThresholds => "MeasurementThresholds", + MicrobatchLimitAction => "MicrobatchLimitAction", + MicrobatchLimitsConfig => "MicrobatchLimitsConfig", + MicrobatchStrategy => "MicrobatchStrategy", + MicrobatchesConfig => "MicrobatchesConfig", + MigrationCompatibility => "MigrationCompatibility", + MigrationCompatibilityResult => "MigrationCompatibilityResult", + MigrationDecision => "MigrationDecision", + MigrationDiscovery => "MigrationDiscovery", + MigrationEvent => "MigrationEvent", + MigrationPromotion => "MigrationPromotion", + MigrationRelation => "MigrationRelation", + MigrationsConfig => "MigrationsConfig", + MissingMigrationOriginPolicy => "MissingMigrationOriginPolicy", + MissingUpstream => "MissingUpstream", + Model => "Model", + ModelColumnLineage => "ModelColumnLineage", + ModelCursorSnapshot => "ModelCursorSnapshot", + ModelInputBuildContext => "ModelInputBuildContext", + ModelMigrationDeclaration => "ModelMigrationDeclaration", + ModelMigrationDiscovery => "ModelMigrationDiscovery", + ModelMigrationPlanEntry => "ModelMigrationPlanEntry", + ModelMigrationPlanning => "ModelMigrationPlanning", + ModelMigrationRequest => "ModelMigrationRequest", + ModelPlanContext => "ModelPlanContext", + ModelPlanEntry => "ModelPlanEntry", + ModelSchemaDeclaration => "ModelSchemaDeclaration", + ModelSql => "ModelSql", + ModelSqlAnalysis => "ModelSqlAnalysis", + ModelValidationRequest => "ModelValidationRequest", + ModuleStateFingerprint => "ModuleStateFingerprint", + ModuleStateTrace => "ModuleStateTrace", + MovePreview => "MovePreview", + NamedSqlHookEntry => "NamedSqlHookEntry", + NativeColumnQuery => "NativeColumnQuery", + NativeModelAnalyses => "NativeModelAnalyses", + NativeModelAnalysisRequest => "NativeModelAnalysisRequest", + NativeModelConfigInputs => "NativeModelConfigInputs", + NativeModelConfigSession => "NativeModelConfigSession", + NativePivotTables => "NativePivotTables", + NativeQueryColumns => "NativeQueryColumns", + NativeRulesEvaluation => "NativeRulesEvaluation", + NativeSqlTestArtifact => "NativeSqlTestArtifact", + NativeSqlTestAssembly => "NativeSqlTestAssembly", + NativeSqlTestChainRequest => "NativeSqlTestChainRequest", + NativeSqlTestFailure => "NativeSqlTestFailure", + NativeSqlTestPlan => "NativeSqlTestPlan", + NativeSqlTestPlanningRequest => "NativeSqlTestPlanningRequest", + NestedProgressChildRow => "NestedProgressChildRow", + NormalizedDbtProfileConnection => "NormalizedDbtProfileConnection", + OldNameView => "OldNameView", + OldNameViewAction => "OldNameViewAction", + OldNameViewDropReason => "OldNameViewDropReason", + OldNameViewEvent => "OldNameViewEvent", + OldNameViewEventType => "OldNameViewEventType", + OldNameViewHistory => "OldNameViewHistory", + OldNameViewPlanEntry => "OldNameViewPlanEntry", + OldNameViewPlanning => "OldNameViewPlanning", + OldNameViewStatus => "OldNameViewStatus", + OnSchemaChange => "OnSchemaChange", + OwnershipRoot => "OwnershipRoot", + OwnershipRootKind => "OwnershipRootKind", + ParsedCliInvocation => "ParsedCliInvocation", + ParsedScenarioArtifactName => "ParsedScenarioArtifactName", + ParsedSelector => "ParsedSelector", + PartialBuildPhaseTimings => "PartialBuildPhaseTimings", + PathSelector => "PathSelector", + PendingLintBody => "PendingLintBody", + PlanAction => "PlanAction", + PlanCommandRequest => "PlanCommandRequest", + PlanEntryBuildInputs => "PlanEntryBuildInputs", + PlanOutput => "PlanOutput", + PlanOutputExtras => "PlanOutputExtras", + PlanProviderUsage => "PlanProviderUsage", + PlanReason => "PlanReason", + PlanSafetyGates => "PlanSafetyGates", + PlanWarning => "PlanWarning", + PlannedSqlTests => "PlannedSqlTests", + PlannerChangeReconciliation => "PlannerChangeReconciliation", + PlannerChangeResults => "PlannerChangeResults", + PlannerEntryResults => "PlannerEntryResults", + PlannerIdentityContext => "PlannerIdentityContext", + PlannerModelEntryResults => "PlannerModelEntryResults", + PlannerOverrides => "PlannerOverrides", + PlannerPolicies => "PlannerPolicies", + PlannerRelationsContext => "PlannerRelationsContext", + PlannerResolvedActions => "PlannerResolvedActions", + PlannerRuntime => "PlannerRuntime", + PlannerScope => "PlannerScope", + PlannerScopePruningResult => "PlannerScopePruningResult", + PlannerScopeResolution => "PlannerScopeResolution", + PlannerSelection => "PlannerSelection", + PlannerWarehouseState => "PlannerWarehouseState", + PlaygroundCommandRequest => "PlaygroundCommandRequest", + PlaygroundTemplate => "PlaygroundTemplate", + PolyglotAnalysisDialect => "PolyglotAnalysisDialect", + PolyglotAnalysisResult => "PolyglotAnalysisResult", + PreparedLintFiles => "PreparedLintFiles", + PreparedSqlLint => "PreparedSqlLint", + PreparedSqlLintResult => "PreparedSqlLintResult", + Project => "Project", + ProjectConfig => "ProjectConfig", + ProjectGraph => "ProjectGraph", + ProjectPath => "ProjectPath", + PythonHookEntry => "PythonHookEntry", + PythonIdentityDependency => "PythonIdentityDependency", + PythonIdentityStatus => "PythonIdentityStatus", + PythonNodeDependencyEdge => "PythonNodeDependencyEdge", + PythonNodeGraph => "PythonNodeGraph", + PythonNodeIdentity => "PythonNodeIdentity", + PythonPlanEntry => "PythonPlanEntry", + PythonRunPhase => "PythonRunPhase", + PythonRunPlanOutputs => "PythonRunPlanOutputs", + PythonSqlReferenceReport => "PythonSqlReferenceReport", + PythonSqlRunLifecyclePlan => "PythonSqlRunLifecyclePlan", + PythonSqlRunSelection => "PythonSqlRunSelection", + QualifiedLineageColumn => "QualifiedLineageColumn", + QueryCostObservation => "QueryCostObservation", + QueryDiffPreparation => "QueryDiffPreparation", + QueryProjection => "QueryProjection", + QueryShape => "QueryShape", + RefactorCommandRequest => "RefactorCommandRequest", + RefactorProject => "RefactorProject", + ReferenceRename => "ReferenceRename", + ReferencesConfig => "ReferencesConfig", + RelatedLocation => "RelatedLocation", + RelationFixtureCompletion => "RelationFixtureCompletion", + RelationFixtureDiagnostic => "RelationFixtureDiagnostic", + RelationFixturePlanningContext => "RelationFixturePlanningContext", + ResolvedDbtConfig => "ResolvedDbtConfig", + ResolvedDbtProfileOutput => "ResolvedDbtProfileOutput", + ResolvedModelAction => "ResolvedModelAction", + ResolvedTableType => "ResolvedTableType", + ResolvedTimeTravelRetention => "ResolvedTimeTravelRetention", + ResolvedWarehouse => "ResolvedWarehouse", + ResourceCost => "ResourceCost", + ResourceIdentity => "ResourceIdentity", + ResourceKind => "ResourceKind", + ResourceRecord => "ResourceRecord", + RetentionDecreasePolicy => "RetentionDecreasePolicy", + RetentionDirection => "RetentionDirection", + RetentionPlanEntry => "RetentionPlanEntry", + RetentionPlanPhase => "RetentionPlanPhase", + Rule => "Rule", + RuleCase => "RuleCase", + RuleExemption => "RuleExemption", + RuleFactViews => "RuleFactViews", + RuleFile => "RuleFile", + RuleFixResult => "RuleFixResult", + RuleGuidance => "RuleGuidance", + RuleIgnore => "RuleIgnore", + RuleOption => "RuleOption", + RuleResult => "RuleResult", + RuleRunPlan => "RuleRunPlan", + RuleSubject => "RuleSubject", + RulesCacheConfig => "RulesCacheConfig", + RulesCommandRequest => "RulesCommandRequest", + RulesConfig => "RulesConfig", + RulesResult => "RulesResult", + RulesRunResult => "RulesRunResult", + ScenarioArtifactIdentity => "ScenarioArtifactIdentity", + ScenarioArtifactKind => "ScenarioArtifactKind", + ScenarioArtifactName => "ScenarioArtifactName", + ScenarioAssertionExpectationPlan => "ScenarioAssertionExpectationPlan", + ScenarioCaptureCommandRequest => "ScenarioCaptureCommandRequest", + ScenarioConfig => "ScenarioConfig", + ScenarioExecutionPlan => "ScenarioExecutionPlan", + ScenarioExpectedExpectationPlan => "ScenarioExpectedExpectationPlan", + ScenarioFixturePlan => "ScenarioFixturePlan", + ScenarioGraphPlan => "ScenarioGraphPlan", + ScenarioRelationMap => "ScenarioRelationMap", + ScenarioRelationPlan => "ScenarioRelationPlan", + ScenarioSnapshotLimitInputs => "ScenarioSnapshotLimitInputs", + ScenarioSnapshotLimitsConfig => "ScenarioSnapshotLimitsConfig", + ScenarioTestCommandRequest => "ScenarioTestCommandRequest", + SchemaAction => "SchemaAction", + SchemaActionKind => "SchemaActionKind", + SchemaAuditInstance => "SchemaAuditInstance", + SchemaChangeKind => "SchemaChangeKind", + SchemaColumn => "SchemaColumn", + SchemaColumnSource => "SchemaColumnSource", + SchemaDynamicColumnFamily => "SchemaDynamicColumnFamily", + SchemaFinding => "SchemaFinding", + SchemaModelEntry => "SchemaModelEntry", + SchemaSeedEntry => "SchemaSeedEntry", + ScopeBrowseResult => "ScopeBrowseResult", + ScopeChainEntry => "ScopeChainEntry", + ScopeCommandRequest => "ScopeCommandRequest", + ScopeCompleteness => "ScopeCompleteness", + ScopeDiagnostic => "ScopeDiagnostic", + ScopeDiagnosticCode => "ScopeDiagnosticCode", + ScopeDiagnosticSeverity => "ScopeDiagnosticSeverity", + ScopeFolder => "ScopeFolder", + ScopeIndex => "ScopeIndex", + ScopeKind => "ScopeKind", + ScopeListResult => "ScopeListResult", + ScopeLookup => "ScopeLookup", + ScopeReport => "ScopeReport", + ScopeReportFilters => "ScopeReportFilters", + ScopeResourceReport => "ScopeResourceReport", + ScopeSection => "ScopeSection", + ScopeSourceLocation => "ScopeSourceLocation", + ScopeTargetQuery => "ScopeTargetQuery", + ScopesConfig => "ScopesConfig", + SeedCommandRequest => "SeedCommandRequest", + SeedCsvSettings => "SeedCsvSettings", + SeedInvocation => "SeedInvocation", + SeedPlanEntry => "SeedPlanEntry", + SelectStarAllow => "SelectStarAllow", + SelectedDbtProfileOutput => "SelectedDbtProfileOutput", + SelectionStalenessGraph => "SelectionStalenessGraph", + SelectionStalenessNodeKey => "SelectionStalenessNodeKey", + SelectionStalenessWarning => "SelectionStalenessWarning", + SelectorExpansion => "SelectorExpansion", + SelectorFileSummary => "SelectorFileSummary", + SelectorKind => "SelectorKind", + SettingsConfig => "SettingsConfig", + SinksConfig => "SinksConfig", + SkillMaintenanceResult => "SkillMaintenanceResult", + SkillUpdateResult => "SkillUpdateResult", + SnapshotSchemaChangePolicy => "SnapshotSchemaChangePolicy", + SnapshotStrategy => "SnapshotStrategy", + SnapshotsConfig => "SnapshotsConfig", + SourceColumnEntry => "SourceColumnEntry", + SourceEntry => "SourceEntry", + SourceFreshnessAgePolicy => "SourceFreshnessAgePolicy", + SourceFreshnessAgeStatus => "SourceFreshnessAgeStatus", + SourceFreshnessConfig => "SourceFreshnessConfig", + SourceFreshnessIdentity => "SourceFreshnessIdentity", + SourceFreshnessObservation => "SourceFreshnessObservation", + SourceFreshnessRecord => "SourceFreshnessRecord", + SourceFreshnessRenderers => "SourceFreshnessRenderers", + SourceFreshnessSet => "SourceFreshnessSet", + SourceFreshnessStrategy => "SourceFreshnessStrategy", + SourceFreshnessUnknown => "SourceFreshnessUnknown", + SourceFreshnessUnknownReason => "SourceFreshnessUnknownReason", + SourceFreshnessValueKind => "SourceFreshnessValueKind", + SourceLoadPlanEntry => "SourceLoadPlanEntry", + SourceLocation => "SourceLocation", + SourceWriteStrategy => "SourceWriteStrategy", + SqlBindingDiagnostic => "SqlBindingDiagnostic", + SqlBindingResult => "SqlBindingResult", + SqlExpansionContext => "SqlExpansionContext", + SqlExpansionReuse => "SqlExpansionReuse", + SqlHookEntry => "SqlHookEntry", + SqlLexicalSyntax => "SqlLexicalSyntax", + SqlLogicalType => "SqlLogicalType", + SqlNode => "SqlNode", + SqlReferenceKind => "SqlReferenceKind", + SqlReferenceScan => "SqlReferenceScan", + SqlRulesEvaluation => "SqlRulesEvaluation", + SqlSchemaValidationRequest => "SqlSchemaValidationRequest", + SqlTestAssertionStep => "SqlTestAssertionStep", + SqlTestParameterDeclaration => "SqlTestParameterDeclaration", + SqlTestPlanEntry => "SqlTestPlanEntry", + SqlTestPlanResult => "SqlTestPlanResult", + SqlTestRulesConfig => "SqlTestRulesConfig", + SqlTestSelection => "SqlTestSelection", + SqlValue => "SqlValue", + SqlValueKind => "SqlValueKind", + SqlValueLimits => "SqlValueLimits", + StartCursorsConfig => "StartCursorsConfig", + StateSqlValueType => "StateSqlValueType", + StatementLedgerEntry => "StatementLedgerEntry", + StaticCommandContext => "StaticCommandContext", + StaticMacroExport => "StaticMacroExport", + StaticMacroFault => "StaticMacroFault", + StaticMacroInventory => "StaticMacroInventory", + TableType => "TableType", + TableTypeDowngradePolicy => "TableTypeDowngradePolicy", + TableTypePlanEntry => "TableTypePlanEntry", + TableTypeSource => "TableTypeSource", + TargetConfig => "TargetConfig", + TargetDiffConfig => "TargetDiffConfig", + TargetWarehousesConfig => "TargetWarehousesConfig", + TestCommandRequest => "TestCommandRequest", + TestInvocation => "TestInvocation", + ThresholdOverride => "ThresholdOverride", + TimeTravelRetentionSource => "TimeTravelRetentionSource", + TimestampValue => "TimestampValue", + TolerantScopeDiscovery => "TolerantScopeDiscovery", + UnitTestSelectorSplit => "UnitTestSelectorSplit", + UsageKind => "UsageKind", + UsageRecord => "UsageRecord", + VisibilityProvenance => "VisibilityProvenance", + VisibilityReason => "VisibilityReason", + VisibilityRecord => "VisibilityRecord", + VisibilityResolution => "VisibilityResolution", + VisibleModelDeclarations => "VisibleModelDeclarations", + WarehouseFingerprints => "WarehouseFingerprints", + WarehouseGroup => "WarehouseGroup", + WarehouseSnapshot => "WarehouseSnapshot", + WarningSeverity => "WarningSeverity", + WrittenTarget => "WrittenTarget", + } +} + +native_ids! { + /// A native error class, registered from the error specs; variants drop the `Error` suffix. + pub(crate) enum ErrorId { + CliUser => "CliUserError", + CompileInput => "CompileInputError", + ConfigValueType => "ConfigValueTypeError", + ContractAdoption => "ContractAdoptionError", + DbtInteropArgument => "DbtInteropArgumentError", + DbtInteropConfig => "DbtInteropConfigError", + DbtInterop => "DbtInteropError", + DbtInteropRuntime => "DbtInteropRuntimeError", + DeclarationParse => "DeclarationParseError", + DiffSizeGuard => "DiffSizeGuardError", + DiscoveryConflict => "DiscoveryConflictError", + Discovery => "DiscoveryError", + EventExporterDiscovery => "EventExporterDiscoveryError", + FactDigest => "FactDigestError", + FingerprintInput => "FingerprintInputError", + FutureCursorSafety => "FutureCursorSafetyError", + HostCancelled => "HostCancelledError", + InvalidQualifiedIdentity => "InvalidQualifiedIdentityError", + InvalidScopePath => "InvalidScopePathError", + Lint => "LintError", + LintStopped => "LintStoppedError", + MacroArgument => "MacroArgumentError", + MaximumAutomaticStart => "MaximumAutomaticStartError", + MeasurementAudit => "MeasurementAuditError", + MigrationState => "MigrationStateError", + ModelSqlParse => "ModelSqlParseError", + NativeLint => "NativeLintError", + NativeSqlTestPlanning => "NativeSqlTestPlanningError", + NativeStageMismatch => "NativeStageMismatchError", + NonHermeticRule => "NonHermeticRuleError", + OpaqueModuleState => "OpaqueModuleStateError", + PlannerInput => "PlannerInputError", + ProjectCompile => "ProjectCompileError", + ProjectConfig => "ProjectConfigError", + ProjectPath => "ProjectPathError", + ProjectPythonPath => "ProjectPythonPathError", + ProviderDiscovery => "ProviderDiscoveryError", + PythonNodeDiscovery => "PythonNodeDiscoveryError", + QueryDiffExecution => "QueryDiffExecutionError", + QueryDiffIncomplete => "QueryDiffIncompleteError", + QueryDiffOutcome => "QueryDiffOutcomeError", + QueryExecution => "QueryExecutionError", + QueryFingerprint => "QueryFingerprintError", + RefactorIO => "RefactorIOError", + RefactorValue => "RefactorValueError", + ResourceIdentity => "ResourceIdentityError", + RuleAssertion => "RuleAssertionError", + RuleUsage => "RuleUsageError", + RulesConfig => "RulesConfigError", + Rules => "RulesError", + SchemaParse => "SchemaParseError", + Scope => "ScopeError", + ScopeValidation => "ScopeValidationError", + SeedDiscovery => "SeedDiscoveryError", + SourceParse => "SourceParseError", + SpecConfig => "SpecConfigError", + SqlAuditParse => "SqlAuditParseError", + SqlHookParse => "SqlHookParseError", + SqlScenarioParse => "SqlScenarioParseError", + SqlTestParse => "SqlTestParseError", + SqlTestReference => "SqlTestReferenceError", + StagedArtifactsChanged => "StagedArtifactsChangedError", + UnindexableScope => "UnindexableScopeError", + UnknownLineageDialect => "UnknownLineageDialectError", + UnsupportedLexicalSyntax => "UnsupportedLexicalSyntaxError", + } +} + +native_ids! { + /// A native function, class or value native code reaches through the module. + pub(crate) enum NativeAttr { + AnalyzeCompileProject => "analyze_compile_project", + AnalyzeNativeModelSql => "analyze_native_model_sql", + ApplyCommandWarehouse => "apply_command_warehouse", + ApplyConfiguredMicrobatchLimits => "apply_configured_microbatch_limits", + ApplyDeferredLocations => "apply_deferred_locations", + ApplyLintSuppressions => "apply_lint_suppressions", + ApplyMaximumStartPolicy => "apply_maximum_start_policy", + ApplyTypedCursorReplayPolicy => "apply_typed_cursor_replay_policy", + ApplyUnitTestSelectors => "apply_unit_test_selectors", + AssembleBasePlanOutput => "assemble_base_plan_output", + AssembleModelPlanEntry => "assemble_model_plan_entry", + AssembleNativeProjectResources => "assemble_native_project_resources", + AssembleNativeSqlTests => "assemble_native_sql_tests", + AssembleProject => "assemble_project", + AssertNoUnresolvedSqlMarkers => "assert_no_unresolved_sql_markers", + AttachChecks => "attach_checks", + AttachResources => "attach_resources", + AttachSqlbuildPlanOutput => "attach_sqlbuild_plan_output", + AuditIsSelected => "audit_is_selected", + AuditProgressReporter => "AuditProgressReporter", + BindingPositions => "BindingPositions", + BuildAuditCommandPlan => "build_audit_command_plan", + BuildCompileDbtManifestIndex => "build_compile_dbt_manifest_index", + BuildCompileInputs => "build_compile_inputs", + BuildCompiledFunctionFingerprintSql => "build_compiled_function_fingerprint_sql", + BuildCompiledProject => "build_compiled_project", + BuildCursorInputRelations => "build_cursor_input_relations", + BuildDagJson => "build_dag_json", + BuildDbtCombinedGraph => "build_dbt_combined_graph", + BuildDbtInitProject => "build_dbt_init_project", + BuildDbtInitProjectToml => "build_dbt_init_project_toml", + BuildDeclarationScope => "build_declaration_scope", + BuildDeferredLocations => "build_deferred_locations", + BuildDirectModelVersionIdentities => "build_direct_model_version_identities", + BuildDirectSemanticUses => "build_direct_semantic_uses", + BuildDisplayOnlySqlbuildPlan => "build_display_only_sqlbuild_plan", + BuildDownstreamDeps => "build_downstream_deps", + BuildEffectiveConnection => "build_effective_connection", + BuildEffectiveConnectionConfig => "build_effective_connection_config", + BuildEffectiveRuntimeConfig => "build_effective_runtime_config", + BuildEffectiveSettings => "build_effective_settings", + BuildEffectiveSettingsConfig => "build_effective_settings_config", + BuildEffectiveTargetConfig => "build_effective_target_config", + BuildEffectiveTargetNamespace => "build_effective_target_namespace", + BuildEffectiveVars => "build_effective_vars", + BuildExecutionEdgeOrigins => "build_execution_edge_origins", + BuildExecutionPlan => "build_execution_plan", + BuildExecutionUpstreamDeps => "build_execution_upstream_deps", + BuildFailedSqlbuildModelNames => "build_failed_sqlbuild_model_names", + BuildFunctionLocalHashes => "build_function_local_hashes", + BuildFunctionLocations => "build_function_locations", + BuildIdentity => "BUILD_IDENTITY", + BuildIntermediateSourceMap => "build_intermediate_source_map", + BuildLintExpansionContext => "build_lint_expansion_context", + BuildManifest => "build_manifest", + BuildMergedDbtExecutionArgv => "build_merged_dbt_execution_argv", + BuildMigrationFingerprint => "build_migration_fingerprint", + BuildModelInputs => "build_model_inputs", + BuildModelLocations => "build_model_locations", + BuildModelVersionIdentityMetadataJson => "build_model_version_identity_metadata_json", + BuildModelWarnings => "build_model_warnings", + BuildPhaseTimingTracker => "BuildPhaseTimingTracker", + BuildPlanEntries => "build_plan_entries", + BuildPlanOutput => "build_plan_output", + BuildPlannerEntryResults => "build_planner_entry_results", + BuildPlannerIdentityContext => "build_planner_identity_context", + BuildPlannerRelationsContext => "build_planner_relations_context", + BuildPlannerScope => "build_planner_scope", + BuildPlannerSourceFreshnessResult => "build_planner_source_freshness_result", + BuildProgressCallbacks => "BuildProgressCallbacks", + BuildProjectColumnLineage => "build_project_column_lineage", + BuildProjectGraph => "build_project_graph", + BuildProjectGraphFromCompiledProject => "build_project_graph_from_compiled_project", + BuildProjectGraphWithAnalysisSelection => "build_project_graph_with_analysis_selection", + BuildProjectRelationTargets => "build_project_relation_targets", + BuildPythonRelationTargets => "build_python_relation_targets", + BuildPythonRunPlanOutputs => "build_python_run_plan_outputs", + BuildPythonSqlRunLifecycle => "build_python_sql_run_lifecycle", + BuildRelationCommandPlan => "build_relation_command_plan", + BuildRelationFixtureCompletion => "build_relation_fixture_completion", + BuildRelationFixtureContext => "build_relation_fixture_context", + BuildRuleCatalogue => "build_rule_catalogue", + BuildRulesRequest => "build_rules_request", + BuildScenarioExecutionPlan => "build_scenario_execution_plan", + BuildScenarioHashIndex => "build_scenario_hash_index", + BuildScenarioRelationMap => "build_scenario_relation_map", + BuildSeedCommandPlan => "build_seed_command_plan", + BuildSeedIdentity => "build_seed_identity", + BuildSeedLocations => "build_seed_locations", + BuildSelectedAuditEntries => "build_selected_audit_entries", + BuildSourceLoadEntries => "build_source_load_entries", + BuildSourceLoadMap => "build_source_load_map", + BuildSourceReadMap => "build_source_read_map", + BuildSqlbuildPlanOutput => "build_sqlbuild_plan_output", + BuildStaleOutOfSelectionWarnings => "build_stale_out_of_selection_warnings", + BuildStaticPipelineResult => "build_static_pipeline_result", + BuildTestCommandPlan => "build_test_command_plan", + BuildUpstreamIntermediateSourceMap => "build_upstream_intermediate_source_map", + CheckBuildability => "check_buildability", + CheckCompileReuse => "check_compile_reuse", + CheckCursorTypeConsistency => "check_cursor_type_consistency", + CheckFullRebuildAllowed => "check_full_rebuild_allowed", + CheckMigrationCompatibility => "check_migration_compatibility", + CheckSelectedScopeBuildability => "check_selected_scope_buildability", + ClassifyCursorSqlType => "classify_cursor_sql_type", + CliStyle => "CliStyle", + CollectCompileDiagnostics => "collect_compile_diagnostics", + CollectLintProjectFiles => "collect_lint_project_files", + CommandWarehouseScopeContext => "_command_warehouse_scope_context", + CompareContracts => "compare_contracts", + CompileDbtInteropProject => "compile_dbt_interop_project", + CompileFrontier => "compile_frontier", + CompileInputsView => "compile_inputs_view", + CompileProjectPhase => "compile_project_phase", + CompileReuseExcludedRootDirectories => "COMPILE_REUSE_EXCLUDED_ROOT_DIRECTORIES", + CompileReuseRacyWindowNs => "COMPILE_REUSE_RACY_WINDOW_NS", + CompileStaticCommandContext => "compile_static_command_context", + CompiledCodeIdentity => "compiled_code_identity", + CompiledModelRecord => "compiled_model_record", + CompiledProjectFacts => "compiled_project_facts", + CompiledProjectRecord => "compiled_project_record", + CompiledRelationKeys => "compiled_relation_keys", + CompleteSemanticProject => "complete_semantic_project", + ComputeCursorBounds => "compute_cursor_bounds", + ComputeModelCursorBounds => "compute_model_cursor_bounds", + ComputeQueryHash => "compute_query_hash", + ConnectionProgressReporter => "ConnectionProgressReporter", + ContentDigest => "content_digest", + ContextStoreToken => "context_store_token", + CostArtifactError => "CostArtifactError", + CostContext => "CostContext", + CostOutputPayload => "cost_output_payload", + CurrentNativeProgressProjector => "current_native_progress_projector", + CurrentTerminalEventIndex => "current_terminal_event_index", + CursorCompare => "cursor_compare", + CursorExclusiveToInclusive => "cursor_exclusive_to_inclusive", + CursorInputsWithoutRows => "cursor_inputs_without_rows", + CursorObservedPartition => "cursor_observed_partition", + CursorParse => "cursor_parse", + CursorRender => "cursor_render", + CursorSentinelToToken => "cursor_sentinel_to_token", + CustomHostObserver => "custom_host_observer", + CustomRuleIdentity => "custom_rule_identity", + CustomRuleImplementationFingerprint => "custom_rule_implementation_fingerprint", + CustomRuleImportClosure => "custom_rule_import_closure", + DbtEventStream => "DbtEventStream", + DbtExecutionLsArgv => "dbt_execution_ls_argv", + DbtFailureDetail => "dbt_failure_detail", + DbtInitProgressReporter => "DbtInitProgressReporter", + DbtRunner => "DbtRunner", + DebugDbtFromProject => "debug_dbt_from_project", + DecideModelMigration => "decide_model_migration", + DeclarationRows => "declaration_rows", + DeclaredColumnAliases => "declared_column_aliases", + DeclaredRelationKeys => "declared_relation_keys", + DeclaredRelations => "declared_relations", + DescribeConnectionWarehouse => "describe_connection_warehouse", + DescribeFullRebuildCause => "describe_full_rebuild_cause", + DetectChanges => "detect_changes", + DetectModelChanges => "detect_model_changes", + DetectModelChangesInScope => "detect_model_changes_in_scope", + DetectStaleWarningChanges => "detect_stale_warning_changes", + DirectorySnapshotScope => "directory_snapshot_scope", + DiscoverEventExporterFunctions => "discover_event_exporter_functions", + DiscoverHookFunctions => "discover_hook_functions", + DiscoverMaterializationFiles => "discover_materialization_files", + DiscoverModelFiles => "discover_model_files", + DiscoverModelMigrations => "discover_model_migrations", + DiscoverModelSchemaFiles => "discover_model_schema_files", + DiscoverProjectInputs => "discover_project_inputs", + DiscoverProviderClasses => "discover_provider_classes", + DiscoverPythonHookFiles => "discover_python_hook_files", + DiscoverPythonModuleFiles => "discover_python_module_files", + DiscoverPythonNodeFunctions => "discover_python_node_functions", + DiscoverSchemaFiles => "discover_schema_files", + DiscoverSourceFiles => "discover_source_files", + DiscoverTestFiles => "discover_test_files", + DynamicContractProof => "dynamic_contract_proof", + EarlyLintExpansions => "early_lint_expansions", + EditContractModel => "edit_contract_model", + EditContractSource => "edit_contract_source", + EffectiveConfigConnection => "effective_config_connection", + EffectiveConfigSettings => "effective_config_settings", + EffectiveConfigVars => "effective_config_vars", + EffectiveTargetConfig => "effective_target_config", + EffectivelyFullRefreshedModelNames => "effectively_full_refreshed_model_names", + EmptyCursorWindow => "empty_cursor_window", + EmptyInputRebuildRefusal => "empty_input_rebuild_refusal", + EncodeSqlRuleFindings => "encode_sql_rule_findings", + EnforceModelExecutionLimit => "enforce_model_execution_limit", + EnforcePlanSafetyPolicies => "enforce_plan_safety_policies", + EvaluateModelContracts => "evaluate_model_contracts", + EvaluateRules => "evaluate_rules", + EvaluateRulesRequest => "evaluate_rules_request", + EventExporterCommandScope => "EventExporterCommandScope", + EventExporterCommandScopeFunction => "event_exporter_command_scope", + EventExporterDispatcher => "EventExporterDispatcher", + ExecuteDbtCommands => "execute_dbt_commands", + ExecuteDbtInteropFromProject => "execute_dbt_interop_from_project", + ExecuteDbtJsonEventStream => "execute_dbt_json_event_stream", + ExecuteDbtSqlbuildWork => "execute_dbt_sqlbuild_work", + ExecuteDbtWithoutStateTracking => "execute_dbt_without_state_tracking", + ExecutionEventWriter => "ExecutionEventWriter", + ExpandFileBodies => "expand_file_bodies", + ExpandProjectUpstreamKeys => "expand_project_upstream_keys", + ExpandSqlMacros => "expand_sql_macros", + ExpandSqlWithSpans => "expand_sql_with_spans", + ExplicitReferenceReport => "explicit_reference_report", + ExplicitReferencesHelp => "explicit_references_help", + ExternalIdentifiersForHeaders => "external_identifiers_for_headers", + ExternallyDescribedModels => "externally_described_models", + ExtractSeedColumns => "extract_seed_columns", + FinalizeRuleFindingsRows => "finalize_rule_findings_rows", + FindPythonStringLocations => "find_python_string_locations", + FinishRuleRun => "finish_rule_run", + FixtureColumnInferences => "FixtureColumnInferences", + FixtureReadCache => "_fixture_read_cache", + FormatAuditExecutionJson => "format_audit_execution_json", + FormatBuildExecutionJson => "format_build_execution_json", + FormatBuildFooter => "format_build_footer", + FormatCloneExecutionJson => "format_clone_execution_json", + FormatCompileErrorJson => "format_compile_error_json", + FormatCostBreakdown => "format_cost_breakdown", + FormatCostHistory => "format_cost_history", + FormatCostHistoryJson => "format_cost_history_json", + FormatCostJson => "format_cost_json", + FormatDegradableBuildExecutionJson => "format_degradable_build_execution_json", + FormatExecutionHeader => "format_execution_header", + FormatExpectationDetail => "format_expectation_detail", + FormatExpectationName => "format_expectation_name", + FormatLintSqlBodies => "format_lint_sql_bodies", + FormatLoadExecutionJson => "format_load_execution_json", + FormatNativeHeaders => "format_native_headers", + FormatPlan => "format_plan", + FormatPlanJson => "format_plan_json", + FormatRedundantFixtureNulls => "format_redundant_fixture_nulls", + FormatRule => "format_rule", + FormatRulesResult => "format_rules_result", + FormatScenarioExecutionJson => "format_scenario_execution_json", + FormatScenarioSnapshotExecutionJson => "format_scenario_snapshot_execution_json", + FormatSeedExecutionJson => "format_seed_execution_json", + FormatSkippedTypeProofRules => "format_skipped_type_proof_rules", + FormatTestExecutionJson => "format_test_execution_json", + GatherDeferredRelations => "gather_deferred_relations", + GatherPlannerWarehouseState => "gather_planner_warehouse_state", + GatherRedirectedCursorSnapshots => "gather_redirected_cursor_snapshots", + GatherSourceColumns => "gather_source_columns", + GatherSourceInspection => "gather_source_inspection", + GetConfigCursorBound => "get_config_cursor_bound", + GetConfigStr => "get_config_str", + GetMaterializationType => "get_materialization_type", + GetSemanticSourceColumns => "get_semantic_source_columns", + HardCodedRelationRemedy => "hard_coded_relation_remedy", + IdenticalRenames => "identical_renames", + InferFixtureColumnFacts => "infer_fixture_column_facts", + InferNativeExpressionSourceShapes => "infer_native_expression_source_shapes", + InferNativeQueryColumnRows => "infer_native_query_column_rows", + InferNativeQueryColumns => "infer_native_query_columns", + InputsWith => "inputs_with", + InstallRulesSkills => "install_rules_skills", + KnownDeclaredTypes => "known_declared_types", + KnownFunctionNames => "known_function_names", + LaunchRecordOffered => "_launch_record_offered", + LaunchRecordWithheld => "_launch_record_withheld", + LineageGraphNames => "lineage_graph_names", + LintNativeHeaders => "lint_native_headers", + LintNativeSqlBodies => "lint_native_sql_bodies", + LintStopScopeFunction => "lint_stop_scope", + ListCostRuns => "list_cost_runs", + LoadCompiledDbtManifest => "load_compiled_dbt_manifest", + LoadDbtManifestIndex => "load_dbt_manifest_index", + LoadLocalConfigFile => "load_local_config_file", + LoadOrBuildScopeIndex => "load_or_build_scope_index", + LoadProgressReporter => "LoadProgressReporter", + LoadProjectConfigFile => "load_project_config_file", + LoadRulesConfig => "load_rules_config", + LoadSqlRuleCache => "load_sql_rule_cache", + LoaderDestinationPartsFunction => "loader_destination_parts", + LocateModelBindingDiagnostics => "locate_model_binding_diagnostics", + LookupSqlRuleCache => "lookup_sql_rule_cache", + MachineOutputMode => "machine_output_mode", + MacroStoreToken => "macro_store_token", + MaintainSqlbuildSkills => "maintain_sqlbuild_skills", + MatchMigrationFingerprints => "match_migration_fingerprints", + MatchProjectRelation => "match_project_relation", + MatchProjectSelector => "match_project_selector", + MatchingDynamicFamilies => "matching_dynamic_families", + MaximumAllowedStart => "maximum_allowed_start", + MeasurementBodyRanges => "measurement_body_ranges", + MergeRecomputedModelChanges => "merge_recomputed_model_changes", + MicrobatchGuidance => "microbatch_guidance", + MigrationDestinationFingerprints => "migration_destination_fingerprints", + MigrationFingerprintCache => "MigrationFingerprintCache", + MigrationFingerprintRefNames => "migration_fingerprint_ref_names", + MigrationMissingOriginPolicy => "migration_missing_origin_policy", + MigrationPlanningDatabase => "migration_planning_database", + MigrationProjectSchemas => "migration_project_schemas", + MigrationRedirectedCursorSnapshots => "migration_redirected_cursor_snapshots", + MigrationRequireSameDatabase => "migration_require_same_database", + MigrationResolveOrigin => "migration_resolve_origin", + MigrationStateInspection => "MigrationStateInspection", + MigrationWithExecution => "migration_with_execution", + MissingUpstreamMessage => "missing_upstream_message", + ModelDefinitionHash => "model_definition_hash", + ModelHeaderFailure => "model_header_failure", + ModelOutputColumns => "model_output_columns", + ModelPlaceholders => "model_placeholders", + ModelPythonHookNames => "model_python_hook_names", + ModelSyntaxChecks => "model_syntax_checks", + NamedDeclarationRoleKind => "named_declaration_role_kind", + NativeCompiledModel => "NativeCompiledModel", + NativeCompiledProject => "NativeCompiledProject", + NativeCompiledSeed => "NativeCompiledSeed", + NativeDeclarationContexts => "NativeDeclarationContexts", + NativeModelConfigBuilder => "NativeModelConfigBuilder", + NativeModelInput => "NativeModelInput", + NativeModelLoop => "NativeModelLoop", + NativeModelValidator => "NativeModelValidator", + NativeProgressProjector => "NativeProgressProjector", + NativeProgressProjectorContext => "_native_progress_projector_context", + NestedCommandProgressCallbacks => "NestedCommandProgressCallbacks", + NeutralizeLintAuditParameters => "neutralize_lint_audit_parameters", + NeutralizeLintContextInterpolation => "neutralize_lint_context_interpolation", + NormalizeAnalysisSql => "normalize_analysis_sql", + NormalizeCursorSnapshotGrain => "normalize_cursor_snapshot_grain", + NormalizeDbtProfileConnection => "normalize_dbt_profile_connection", + OldNameClaimErrors => "old_name_claim_errors", + OldNameReferenceMessage => "old_name_reference_message", + OldNameSchemasToRead => "old_name_schemas_to_read", + OldNameViewSkipReason => "old_name_view_skip_reason", + ParseDbtLsJsonLines => "parse_dbt_ls_json_lines", + ParseHeaderValues => "parse_header_values", + ParseModelHeaders => "parse_model_headers", + ParseProjectSelector => "parse_project_selector", + ParseQueryShape => "parse_query_shape", + PlanColumnMigrations => "plan_column_migrations", + PlanCompiledSqlTestArtifacts => "plan_compiled_sql_test_artifacts", + PlanCompiledSqlTests => "plan_compiled_sql_tests", + PlanDbtInteropCommand => "plan_dbt_interop_command", + PlanDbtInteropFromProject => "plan_dbt_interop_from_project", + PlanHasExecutableWork => "plan_has_executable_work", + PlanModel => "plan_model", + PlanModelMigrations => "plan_model_migrations", + PlanOldNameViews => "plan_old_name_views", + PlanReferenceRenames => "plan_reference_renames", + PlanRetention => "plan_retention", + PlanRuleFixes => "plan_rule_fixes", + PlanRuleRun => "plan_rule_run", + PlanSelectedSqlTests => "plan_selected_sql_tests", + PlanSelectedSqlTestsInto => "plan_selected_sql_tests_into", + PlanSqlTests => "plan_sql_tests", + PlanSqlTestsNatively => "plan_sql_tests_natively", + PlanStaticSqlTests => "plan_static_sql_tests", + PlanTableTypes => "plan_table_types", + PlanningProgressReporter => "PlanningProgressReporter", + PrepareExpandedFiles => "prepare_expanded_files", + PrepareLintSql => "prepare_lint_sql", + PrepareLintSqlBatch => "prepare_lint_sql_batch", + PrepareModelHeaderTokens => "prepare_model_header_tokens", + PrepareNativeHeaderCache => "prepare_native_header_cache", + PrepareSqlRules => "prepare_sql_rules", + PreparedCompileArtifacts => "PreparedCompileArtifacts", + ProjectLineageViews => "project_lineage_views", + ProjectRelativePath => "project_relative_path", + ProjectWith => "project_with", + PromotionConflictDiagnostics => "promotion_conflict_diagnostics", + ProveNativeDynamicContracts => "prove_native_dynamic_contracts", + PrunePlannerExecutionScope => "prune_planner_execution_scope", + PublicDeclarationIndexes => "public_declaration_indexes", + PublicModelSchemaIndex => "public_model_schema_index", + PythonReadSourceNamesFor => "python_read_source_names_for", + ReadCostRun => "read_cost_run", + ReadLatestFingerprints => "read_latest_fingerprints", + ReadOldNameViewHistories => "read_old_name_view_histories", + ReadSelectedSeedFingerprints => "read_selected_seed_fingerprints", + ReadsOutsideTargetLineage => "reads_outside_target_lineage", + RebindDeclarationScope => "rebind_declaration_scope", + ReconcileExecutionChanges => "reconcile_execution_changes", + RecordCompileInputReads => "record_compile_input_reads", + RecordModelAnalyses => "record_model_analyses", + RecordSettingsClassRead => "record_settings_class_read", + RecordTemplateReads => "record_template_reads", + RejectUnitTestSelectors => "reject_unit_test_selectors", + RejectUnparseableHeaderRewrites => "reject_unparseable_header_rewrites", + RelationLineageFingerprint => "relation_lineage_fingerprint", + RemainingContractFindings => "remaining_contract_findings", + RenameHints => "rename_hints", + RenamesExplainChange => "renames_explain_change", + RenderAuditSql => "render_audit_sql", + RenderConstantDeclaration => "render_constant_declaration", + RenderDbtExecutionSummaryFooter => "render_dbt_execution_summary_footer", + RenderDbtInteropPlan => "render_dbt_interop_plan", + RenderDbtNodeResult => "render_dbt_node_result", + RenderDiffErrorJson => "render_diff_error_json", + RenderProjectVarText => "render_project_var_text", + RenderSourceReadRelation => "render_source_read_relation", + RenderSourceRelation => "render_source_relation", + ReplacesIncrementalRelation => "replaces_incremental_relation", + ReportCompileDiagnostic => "report_compile_diagnostic", + ResolveAnalysisModelNames => "resolve_analysis_model_names", + ResolveAttachmentKind => "resolve_attachment_kind", + ResolveBoundedCursorOverride => "resolve_bounded_cursor_override", + ResolveCloneBoundary => "resolve_clone_boundary", + ResolveCompileAnalysisSelection => "resolve_compile_analysis_selection", + ResolveCompiledSqlTestChains => "resolve_compiled_sql_test_chains", + ResolveConfigFilePath => "resolve_config_file_path", + ResolveConfiguredRuleSelection => "resolve_configured_rule_selection", + ResolveCostRun => "resolve_cost_run", + ResolveCursorInputRoles => "resolve_cursor_input_roles", + ResolveCursorIntrinsics => "resolve_cursor_intrinsics", + ResolveDbtConfig => "resolve_dbt_config", + ResolveDbtConnectionConfig => "resolve_dbt_connection_config", + ResolveDbtExecutable => "resolve_dbt_executable", + ResolveDbtExecutionInvocation => "resolve_dbt_execution_invocation", + ResolveDbtInteropPlan => "resolve_dbt_interop_plan", + ResolveDbtRefReferences => "resolve_dbt_ref_references", + ResolveDbtReferenceRelation => "resolve_dbt_reference_relation", + ResolveDeferredTargetConfig => "resolve_deferred_target_config", + ResolveEffectiveAdapterName => "resolve_effective_adapter_name", + ResolveEffectiveCollectionRendering => "resolve_effective_collection_rendering", + ResolveEffectiveRunScope => "resolve_effective_run_scope", + ResolveEffectiveScenarioConfig => "resolve_effective_scenario_config", + ResolveEffectiveTimestampGrain => "resolve_effective_timestamp_grain", + ResolveExternalSqlReferenceResolver => "resolve_external_sql_reference_resolver", + ResolveFunctionSql => "resolve_function_sql", + ResolveFutureCursorSafety => "resolve_future_cursor_safety", + ResolveGraphSelection => "resolve_graph_selection", + ResolveLineageReference => "resolve_lineage_reference", + ResolveMicrobatchLookback => "resolve_microbatch_lookback", + ResolveModelActions => "resolve_model_actions", + ResolveModelDeferralInputs => "resolve_model_deferral_inputs", + ResolveModelFullRefresh => "resolve_model_full_refresh", + ResolveModelHookEntries => "resolve_model_hook_entries", + ResolveModelPlanAction => "resolve_model_plan_action", + ResolveModelSql => "resolve_model_sql", + ResolvePlannerScopes => "resolve_planner_scopes", + ResolveProjectConnectionConfig => "resolve_project_connection_config", + ResolveProjectSelectorTokens => "resolve_project_selector_tokens", + ResolveProjectSelectors => "resolve_project_selectors", + ResolvePythonCheckExcludes => "resolve_python_check_excludes", + ResolveRawDbtProfileConnection => "resolve_raw_dbt_profile_connection", + ResolveRefReferences => "resolve_ref_references", + ResolveRuleIgnoreSelectors => "resolve_rule_ignore_selectors", + ResolveSchemaActions => "resolve_schema_actions", + ResolveSkippedViewChain => "resolve_skipped_view_chain", + ResolveSourceReferences => "resolve_source_references", + ResolveSqlTestModelChains => "resolve_sql_test_model_chains", + ResolveSqlbuildExecutionPlanOutput => "resolve_sqlbuild_execution_plan_output", + ResolveSqlbuildModelSelectorNames => "resolve_sqlbuild_model_selector_names", + ResolveStartCursorConfig => "resolve_start_cursor_config", + ResolveStaticCommandScope => "resolve_static_command_scope", + ResolveStaticRelationContext => "resolve_static_relation_context", + ResolveTableFunctionReferences => "resolve_table_function_references", + ResolveTargetConfig => "resolve_target_config", + ResolveTargetConnectionConfig => "resolve_target_connection_config", + ResolveTargetName => "resolve_target_name", + ResolveUdfReferences => "resolve_udf_references", + RouteDbtInteropArgs => "route_dbt_interop_args", + RuleSelected => "rule_selected", + RulesBulkCachePath => "rules_bulk_cache_path", + RunAuditCommand => "run_audit_command", + RunBuildCommand => "run_build_command", + RunCheckCommand => "run_check_command", + RunCloneCommand => "run_clone_command", + RunClonePipelineWithOptions => "run_clone_pipeline_with_options", + RunClonePlanning => "run_clone_planning", + RunCompile => "run_compile", + RunCompileCommand => "run_compile_command", + RunCompileOnlyPipeline => "run_compile_only_pipeline", + RunCompilePipeline => "run_compile_pipeline", + RunContractCommand => "run_contract_command", + RunCostCommand => "run_cost_command", + RunDagCommand => "run_dag_command", + RunDbtCommand => "run_dbt_command", + RunDbtInitCommand => "run_dbt_init_command", + RunDbtSqlbuildWork => "run_dbt_sqlbuild_work", + RunDebugCommand => "run_debug_command", + RunDiffCommand => "run_diff_command", + RunDiffPipeline => "run_diff_pipeline", + RunFormatCommand => "run_format_command", + RunFreshnessCommand => "run_freshness_command", + RunInitCommand => "run_init_command", + RunJanitorCommand => "run_janitor_command", + RunLineageCommand => "run_lineage_command", + RunLint => "run_lint", + RunLoadCommand => "run_load_command", + RunPlanCommand => "run_plan_command", + RunPlannedSqlRules => "run_planned_sql_rules", + RunPlaygroundCommand => "run_playground_command", + RunQueryCommand => "run_query_command", + RunRefactorCommand => "run_refactor_command", + RunRules => "run_rules", + RunRulesCommand => "run_rules_command", + RunScenarioCaptureCommand => "run_scenario_capture_command", + RunScenarioTestCommand => "run_scenario_test_command", + RunScopeCommand => "run_scope_command", + RunSeedCommand => "run_seed_command", + RunSkillsCommand => "run_skills_command", + RunTestCommand => "run_test_command", + RuntimeCursorProducerNames => "runtime_cursor_producer_names", + SafeFormatFiles => "safe_format_files", + ScanHeaders => "scan_headers", + SchemaValidations => "schema_validations", + ScopeMetadataProjection => "scope_metadata_projection", + SelectCatalogueRules => "select_catalogue_rules", + SelectLatestFingerprints => "select_latest_fingerprints", + SelectProjectGraph => "select_project_graph", + SelectedRuleCodes => "selected_rule_codes", + SelectedSqlRuleCodes => "selected_sql_rule_codes", + SessionOutcomeAnalysis => "session_outcome_analysis", + SettingHelp => "setting_help", + SharedTransientLineCoordinator => "shared_transient_line_coordinator", + SourceBindingDiagnostics => "source_binding_diagnostics", + SourceFreshnessInputError => "SourceFreshnessInputError", + SourceFreshnessObservationError => "SourceFreshnessObservationError", + SplitProjectUnitTestSelectors => "split_project_unit_test_selectors", + SplitSelectorExpansion => "split_selector_expansion", + SplitUnitTestSelectors => "split_unit_test_selectors", + SqlBodyRanges => "sql_body_ranges", + SqlCodeScanner => "SqlCodeScanner", + SqlFileRuleKey => "sql_file_rule_key", + SqlLintPreparation => "sql_lint_preparation", + SqlModelRuleKeys => "sql_model_rule_keys", + SqlRefKey => "sql_ref_key", + SqlRuleLintKey => "sql_rule_lint_key", + SqlTestModelChainNamesByKey => "sql_test_model_chain_names_by_key", + SqlTestScanCache => "SqlTestScanCache", + SqlValueValidationError => "SqlValueValidationError", + StandalonePivotModels => "standalone_pivot_models", + StartModelAnalysisSession => "start_model_analysis_session", + StaticSqlTestPlanningDiagnostics => "static_sql_test_planning_diagnostics", + StoredMigrationFingerprint => "stored_migration_fingerprint", + StoredOldNameViewColumns => "stored_old_name_view_columns", + TerminalEventIndex => "TerminalEventIndex", + TerminalEventIndexContext => "_terminal_event_index_context", + TerminalEventIndexScopeFunction => "terminal_event_index_scope", + TopologicallyOrderKeys => "topologically_order_keys", + TransientStatusReporter => "TransientStatusReporter", + TransitiveClosureMany => "transitive_closure_many", + TypeCursorOverrides => "type_cursor_overrides", + UnreadableCursorInputsError => "unreadable_cursor_inputs_error", + UpdateSqlbuildSkills => "update_sqlbuild_skills", + UpstreamLoaderDependencyNames => "upstream_loader_dependency_names", + ValidRulesConfig => "valid_rules_config", + ValidateAuthoredSqlTestCtes => "validate_authored_sql_test_ctes", + ValidateCappedWatermarkInputs => "validate_capped_watermark_inputs", + ValidateCliWarehouse => "validate_cli_warehouse", + ValidateCompileDbtModelNames => "validate_compile_dbt_model_names", + ValidateCompileDbtModelReference => "validate_compile_dbt_model_reference", + ValidateDbtInitProject => "validate_dbt_init_project", + ValidateHookSqlSyntax => "validate_hook_sql_syntax", + ValidateLocalSqlAnalysis => "validate_local_sql_analysis", + ValidatePreviewTarget => "validate_preview_target", + ValidateResourceIdentity => "validate_resource_identity", + ValidateSourceCursorInputColumns => "validate_source_cursor_input_columns", + ValidateSqlSyntax => "validate_sql_syntax", + WithBindingDiagnostics => "with_binding_diagnostics", + WithDeclaredSourceReads => "with_declared_source_reads", + WithDuplicateLiteralHints => "with_duplicate_literal_hints", + WithHonestModelWriteHashes => "with_honest_model_write_hashes", + WithMigrationFingerprints => "with_migration_fingerprints", + WithPlanMetadata => "with_plan_metadata", + WithPlanWarnings => "with_plan_warnings", + WithStoragePolicies => "with_storage_policies", + WithTypeRecovery => "with_type_recovery", + WithoutDeclaredColumnsHash => "without_declared_columns_hash", + WithoutDestinationCursor => "without_destination_cursor", + WriteAtomically => "write_atomically", + WriteCheckResults => "write_check_results", + WriteCompileTarget => "write_compile_target", + WriteCostRun => "write_cost_run", + WriteDbtExecutionPlanText => "write_dbt_execution_plan_text", + WriteDbtExecutionSummary => "write_dbt_execution_summary", + WriteExecutionHeader => "write_execution_header", + WriteExecutionJsonOutput => "write_execution_json_output", + WriteReusableCompile => "write_reusable_compile", + WriteRuntimeTarget => "write_runtime_target", + WriteSourceFreshnessRecords => "write_source_freshness_records", + WriteSqlRuleCache => "write_sql_rule_cache", + WriteSqlbuildSkipNotice => "write_sqlbuild_skip_notice", + YamlEntryLine => "yaml_entry_line", + } +} + +/// Any registered native name, for helpers that accept records, errors and module attributes. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum NativeItem { + Record(RecordId), + Error(ErrorId), + Attr(NativeAttr), +} + +impl RecordId { + /// The record registered under `name`, for class names a pure-Rust crate chose. + pub(crate) fn from_name(name: &str) -> Option { + Self::ALL.iter().copied().find(|id| id.name() == name) + } +} + +impl NativeAttr { + /// The attribute registered under `name`, for function names a pure-Rust crate chose. + pub(crate) fn from_name(name: &str) -> Option { + Self::ALL.iter().copied().find(|id| id.name() == name) + } +} + +impl From for NativeItem { + fn from(id: RecordId) -> Self { + Self::Record(id) + } +} + +impl From for NativeItem { + fn from(id: ErrorId) -> Self { + Self::Error(id) + } +} + +impl From for NativeItem { + fn from(id: NativeAttr) -> Self { + Self::Attr(id) + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs index 2bc36c8a91..7d5d3b8228 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs @@ -1,6 +1,8 @@ pub(crate) mod constants; pub(crate) mod error_classes; pub(crate) mod fields; +pub(crate) mod ids; pub(crate) mod models; pub(crate) mod registration; +pub(crate) mod registry; pub(crate) mod types; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs index 4c2b8657de..19c80e379a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs @@ -8,10 +8,12 @@ use pyo3::pyclass::CompareOp; use pyo3::types::{PyDict, PyTuple, PyType}; use pyo3::{IntoPyObjectExt, pyclass, pymethods}; +use crate::bindings::_helpers::native_host::models::HostId; use crate::bindings::_helpers::records::fields::{ build_values, cached_value, compared_values, frozen_error, ordered_comparison, record_spec, repr_text, store_cached, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::types::{ClassMethod, ClassProperty, RecordFactory}; /// How a record field is filled when the caller does not pass it. @@ -32,8 +34,14 @@ pub(crate) enum FieldDefault { EmptyFrozenset, /// `getattr(getattr(import(module), class), member)`. Member(&'static str, &'static str, &'static str), - /// `getattr(import(module), name)()`, evaluated for every instance. - Call(&'static str, &'static str), + /// A member of a native string enum. + NativeMember(RecordId, &'static str), + /// A member of a staying-Python enum reached through the host. + HostMember(HostId, &'static str), + /// A native record class called with no arguments, evaluated for every instance. + NativeCall(RecordId), + /// A native Rust class called with no arguments, evaluated for every instance. + NativeClassCall(NativeAttr), /// A native factory, evaluated for every instance. Factory(RecordFactory), /// `getattr(import(module), name)(text)`, evaluated for every instance. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs new file mode 100644 index 0000000000..abc0274b79 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs @@ -0,0 +1,165 @@ +//! Typed handles to what `sqlbuild._native` registers, checked against the registration tables at init. + +use std::collections::BTreeSet; + +use pyo3::exceptions::{PyAttributeError, PyImportError}; +use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyResult, Python}; +use pyo3::sync::PyOnceLock; +use pyo3::types::PyString; + +use crate::bindings::_helpers::records::constants::{ENUMS, ERRORS, NATIVE_MODULE}; +use crate::bindings::_helpers::records::fields::all_specs; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, NativeItem, RecordId}; + +/// The extension module, imported once for attribute lookups. +static MODULE: PyOnceLock> = PyOnceLock::new(); +/// Each `RecordId`'s interned attribute name, by variant position. +static CLASS_NAMES: PyOnceLock>> = PyOnceLock::new(); +/// Each `ErrorId`'s interned attribute name, by variant position. +static ERROR_NAMES: PyOnceLock>> = PyOnceLock::new(); +/// Each `NativeAttr`'s interned attribute name, by variant position. +static ATTRIBUTE_NAMES: PyOnceLock>> = PyOnceLock::new(); + +fn module(py: Python<'_>) -> PyResult<&Py> { + MODULE.get_or_try_init(py, || Ok(py.import(NATIVE_MODULE)?.unbind())) +} + +/// The module attribute at `index` of `names`, interning the names on first use. +fn attribute<'py, 'a>( + py: Python<'py>, + names: &PyOnceLock>>, + all: impl Iterator, + index: usize, +) -> PyResult> { + let names: &Vec> = names.get_or_init(py, || { + all.map(|name| PyString::intern(py, name).unbind()) + .collect() + }); + module(py)?.bind(py).getattr(names[index].bind(py)) +} + +/// Names that are in exactly one of two name sets, sorted. +fn mismatched(left: BTreeSet<&str>, right: BTreeSet<&str>) -> Vec { + left.symmetric_difference(&right) + .map(|name| (*name).to_owned()) + .collect() +} + +/// Fail loudly when the typed names and the registration tables disagree. +fn check_tables() -> PyResult<()> { + let records: BTreeSet<&str> = all_specs() + .map(|spec| spec.name) + .chain( + ENUMS + .iter() + .flat_map(|group| group.iter()) + .map(|spec| spec.name), + ) + .collect(); + let errors: BTreeSet<&str> = ERRORS + .iter() + .flat_map(|group| group.iter()) + .map(|spec| spec.name) + .collect(); + let mut problems: Vec = + mismatched(records, RecordId::ALL.iter().map(|id| id.name()).collect()); + problems.extend(mismatched( + errors, + ErrorId::ALL.iter().map(|id| id.name()).collect(), + )); + if problems.is_empty() { + return Ok(()); + } + Err(PyImportError::new_err(format!( + "native registry and registration tables disagree: {}", + problems.join(", ") + ))) +} + +/// Fail module init when the typed names and the registration tables disagree. +pub(crate) fn verify_tables() -> PyResult<()> { + check_tables() +} + +/// Fail module init when a `NativeAttr` names an attribute the finished module lacks. +pub(crate) fn verify_attributes(module: &Bound<'_, PyModule>) -> PyResult<()> { + let mut missing: Vec<&str> = Vec::new(); + for id in NativeAttr::ALL { + if !module.hasattr(id.name())? { + missing.push(id.name()); + } + } + if missing.is_empty() { + return Ok(()); + } + Err(PyImportError::new_err(format!( + "the native module does not register: {}", + missing.join(", ") + ))) +} + +/// The native record class or string enum `id`, read now so a replaced attribute is seen. +pub(crate) fn class(py: Python<'_>, id: RecordId) -> PyResult> { + attribute( + py, + &CLASS_NAMES, + RecordId::ALL.iter().map(|id| id.name()), + id as usize, + ) +} + +/// The native error class `id`, read now so a replaced attribute is seen. +pub(crate) fn error(py: Python<'_>, id: ErrorId) -> PyResult> { + attribute( + py, + &ERROR_NAMES, + ErrorId::ALL.iter().map(|id| id.name()), + id as usize, + ) +} + +/// The module attribute `id`, read now so a replaced attribute is seen. +pub(crate) fn native(py: Python<'_>, id: NativeAttr) -> PyResult> { + attribute( + py, + &ATTRIBUTE_NAMES, + NativeAttr::ALL.iter().map(|id| id.name()), + id as usize, + ) +} + +/// A member or class attribute of the native record class or string enum `id`. +pub(crate) fn member<'py>( + py: Python<'py>, + id: RecordId, + name: &str, +) -> PyResult> { + class(py, id)?.getattr(name) +} + +/// Any registered native name. +pub(crate) fn item(py: Python<'_>, id: impl Into) -> PyResult> { + match id.into() { + NativeItem::Record(id) => class(py, id), + NativeItem::Error(id) => error(py, id), + NativeItem::Attr(id) => native(py, id), + } +} + +/// The native record class or string enum a pure-Rust crate names, failing as `getattr` would. +pub(crate) fn record_named(name: &str) -> PyResult { + RecordId::from_name(name).ok_or_else(|| { + PyAttributeError::new_err(format!( + "module 'sqlbuild._native' has no attribute '{name}'" + )) + }) +} + +/// The native module attribute a pure-Rust crate names, failing as `getattr` would. +pub(crate) fn attribute_named(name: &str) -> PyResult { + NativeAttr::from_name(name).ok_or_else(|| { + PyAttributeError::new_err(format!( + "module 'sqlbuild._native' has no attribute '{name}'" + )) + }) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs index a8063b2f4d..1aeded45f9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs @@ -7,13 +7,13 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::text::main::python_casefold::python_casefold; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; -const ADOPTION_MODELS_MODULE: &str = "sqlbuild._native"; -const ADOPTION_TYPES_MODULE: &str = "sqlbuild._native"; -const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const DYNAMIC_FAMILIES_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const MODEL_RESOURCE: &str = "model"; const SOURCE_RESOURCE: &str = "source"; @@ -28,12 +28,8 @@ struct Vocabulary<'py> { impl<'py> Vocabulary<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - finding: py - .import(ADOPTION_MODELS_MODULE)? - .getattr("ContractFinding")?, - kinds: py - .import(ADOPTION_TYPES_MODULE)? - .getattr("ContractFindingKind")?, + finding: registry::class(py, RecordId::ContractFinding)?, + kinds: registry::class(py, RecordId::ContractFindingKind)?, replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, }) } @@ -104,7 +100,7 @@ fn column_infos<'py>( py: Python<'py>, columns: &Bound<'py, PyAny>, ) -> PyResult> { - let column_type: Bound<'py, PyAny> = py.import(ADAPTER_MODELS_MODULE)?.getattr("ColumnInfo")?; + let column_type: Bound<'py, PyAny> = host::entry(py, AdapterHost::ColumnInfo)?; let mut infos: Vec> = Vec::new(); for column in columns.try_iter()? { let column: Bound<'py, PyAny> = column?; @@ -140,8 +136,7 @@ fn model_evidence<'py>(model: &Bound<'py, PyAny>) -> PyResult> ) }; keyword_call( - &py.import(ADOPTION_MODELS_MODULE)? - .getattr("ContractEvidence")?, + ®istry::class(py, RecordId::ContractEvidence)?, vec![ ("resource_type", resource_type(py, MODEL_RESOURCE)?), ("resource_name", model.getattr("name")?), @@ -189,8 +184,7 @@ fn source_evidence<'py>( text(py, &relation) }; keyword_call( - &py.import(ADOPTION_MODELS_MODULE)? - .getattr("ContractEvidence")?, + ®istry::class(py, RecordId::ContractEvidence)?, vec![ ("resource_type", resource), ("resource_name", name), @@ -258,8 +252,7 @@ fn undeclared_finding<'py>( let name: String = physical.getattr("name")?.extract()?; let physical_type: Bound<'py, PyAny> = physical.getattr("type")?; let matching: Bound<'py, PyAny> = keyword_call( - &py.import(DYNAMIC_FAMILIES_MODULE)? - .getattr("matching_dynamic_families")?, + ®istry::native(py, NativeAttr::MatchingDynamicFamilies)?, vec![ ("families", item.getattr("dynamic_columns")?), ("column_name", text(py, &name)), @@ -462,7 +455,7 @@ fn compare_contract_evidence<'py>( fn relation_info<'py>(item: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = item.py(); keyword_call( - &py.import(ADAPTER_MODELS_MODULE)?.getattr("RelationInfo")?, + &host::entry(py, AdapterHost::RelationInfo)?, vec![ ("database", item.getattr("database")?), ("schema", item.getattr("schema")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs index 141dbbc21c..7d6ec9ae98 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs @@ -15,10 +15,9 @@ use sqlbuild_refactor::refactoring::models::{ use sqlbuild_refactor::refactoring::types::PhysicalColumn; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const ADOPTION_ERRORS_MODULE: &str = "sqlbuild._native"; -const ADOPTION_MODELS_MODULE: &str = "sqlbuild._native"; -const ADOPTION_TYPES_MODULE: &str = "sqlbuild._native"; /// Finding kinds an edit resolves without overwrite, then the ones overwrite also resolves. const RESOLVED_KINDS: ([&str; 1], [&str; 2]) = ( ["missing_declaration"], @@ -40,11 +39,7 @@ fn edit_row(result: Result) -> PyResult { /// `ContractAdoptionError(message)`. fn adoption_error(py: Python<'_>, message: String) -> PyErr { - match py - .import(ADOPTION_ERRORS_MODULE) - .and_then(|module| module.getattr("ContractAdoptionError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::ContractAdoption).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -175,15 +170,11 @@ fn remaining_contract_findings<'py>( keywords.set_item("resource_name", first.getattr("resource_name")?)?; keywords.set_item( "kind", - py.import(ADOPTION_TYPES_MODULE)? - .getattr("ContractFindingKind")? - .getattr("OWNERSHIP_CONFLICT")?, + registry::class(py, RecordId::ContractFindingKind)?.getattr("OWNERSHIP_CONFLICT")?, )?; keywords.set_item("message", conflict)?; - let ownership: Bound<'py, PyAny> = py - .import(ADOPTION_MODELS_MODULE)? - .getattr("ContractFinding")? - .call((), Some(&keywords))?; + let ownership: Bound<'py, PyAny> = + registry::class(py, RecordId::ContractFinding)?.call((), Some(&keywords))?; let mut remaining: Vec> = findings.clone(); remaining.push(ownership); return PyTuple::new(py, remaining); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs index 24d25ddda6..100c1e2f82 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs @@ -2,25 +2,15 @@ use std::path::Path; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::IntoPyObject; +use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyBool, PyString}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; -const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_ERRORS: &str = "sqlbuild._native"; -const PLANNER_ERRORS: &str = "sqlbuild._native"; -const RULES_ERRORS: &str = "sqlbuild._native"; -const SPEC_ERRORS: &str = "sqlbuild._native"; -const ENTRY_MODELS: &str = "sqlbuild._native"; -const COMMAND_TYPES: &str = "sqlbuild._native"; -const REFACTOR_MODELS: &str = "sqlbuild._native"; const DEFAULT_ERROR_CODE: &str = "E001"; /// One offline compile's project facts, or `None`, and every error that stopped or followed it. @@ -29,13 +19,13 @@ pub(crate) struct OfflineCompile<'py> { pub(crate) errors: Vec>, } -fn enum_member<'py>(py: Python<'py>, class: &str, member: &str) -> PyResult> { - shape_class_at(py, COMPILE_TYPES, class)?.getattr(member) +fn enum_member<'py>(py: Python<'py>, class: RecordId, member: &str) -> PyResult> { + registry::member(py, class, member) } -fn matches_any_class(py: Python<'_>, error: &PyErr, classes: &[(&str, &str)]) -> PyResult { - for (module, name) in classes { - if error.value(py).is_instance(&imported(py, module, name)?)? { +fn matches_any_class(py: Python<'_>, error: &PyErr, classes: &[ErrorId]) -> PyResult { + for class in classes { + if error.value(py).is_instance(®istry::error(py, *class)?)? { return Ok(true); } } @@ -56,10 +46,16 @@ fn compile_error_diagnostic<'py>(py: Python<'py>, error: &PyErr) -> PyResult(py: Python<'py>, error: &PyErr) -> PyResult(py: Python<'py>, error: &PyErr) -> PyResult> { let value: Bound<'py, PyAny> = error.value(py).clone().into_any(); let location: Bound<'py, PyAny> = - if value.is_instance(&imported(py, COMPILE_ERRORS, "SqlTestReferenceError")?)? { + if value.is_instance(®istry::error(py, ErrorId::SqlTestReference)?)? { value.getattr("location")? } else { py.None().into_bound(py) }; call_with( - &imported(py, COMPILE_MODELS, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ - ("phase", enum_member(py, "DiagnosticPhase", "TEST")?), - ("severity", enum_member(py, "DiagnosticSeverity", "ERROR")?), + ("phase", enum_member(py, RecordId::DiagnosticPhase, "TEST")?), + ( + "severity", + enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?, + ), ("code", value.getattr("code")?), ("message", value.str()?.into_any()), ("help", value.getattr("help")?), @@ -105,11 +104,11 @@ fn analyzed<'py>( no_cache: bool, ) -> PyResult> { let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", PyBool::new(py, no_cache).to_owned().into_any())], )?; call_with( - &imported(py, NATIVE_MODULE, "analyze_compile_project")?, + ®istry::native(py, NativeAttr::AnalyzeCompileProject)?, &[ ("project_dir", project_dir.into_pyobject(py)?.into_any()), ( @@ -119,12 +118,12 @@ fn analyzed<'py>( ("selected_target", py.None().into_bound(py)), ( "lineage_mode", - imported(py, COMMAND_TYPES, "CompileLineageMode")?.getattr("NONE")?, + registry::class(py, RecordId::CompileLineageMode)?.getattr("NONE")?, ), ("cli_vars", py.None().into_bound(py)), ( "profile_flags", - imported(py, ENTRY_MODELS, "CompileProfileFlags")?.call0()?, + registry::class(py, RecordId::CompileProfileFlags)?.call0()?, ), ("analysis_selection", selection), ("status", py.None().into_bound(py)), @@ -141,15 +140,8 @@ pub(crate) fn compile_for_refactor<'py>( let analysis: Bound<'py, PyAny> = match analyzed(py, project_dir, no_cache) { Ok(analysis) => analysis, Err(error) - if matches_any_class( - py, - &error, - &[ - ("builtins", "ValueError"), - (RULES_ERRORS, "RulesError"), - (SPEC_ERRORS, "SpecConfigError"), - ], - )? => + if error.is_instance_of::(py) + || matches_any_class(py, &error, &[ErrorId::Rules, ErrorId::SpecConfig])? => { return Ok(OfflineCompile { project: py.None().into_bound(py), @@ -161,7 +153,7 @@ pub(crate) fn compile_for_refactor<'py>( let mut errors: Vec> = error_diagnostics(analysis.getattr("diagnostics")?)?; if errors.is_empty() { let planned: PyResult> = call_with( - &imported(py, NATIVE_MODULE, "static_sql_test_planning_diagnostics")?, + ®istry::native(py, NativeAttr::StaticSqlTestPlanningDiagnostics)?, &[ ( "target_dir", @@ -177,10 +169,7 @@ pub(crate) fn compile_for_refactor<'py>( if matches_any_class( py, &error, - &[ - (COMPILE_ERRORS, "CompileInputError"), - (PLANNER_ERRORS, "PlannerInputError"), - ], + &[ErrorId::CompileInput, ErrorId::PlannerInput], )? => { errors.push(planning_error_diagnostic(py, &error)?); @@ -189,7 +178,7 @@ pub(crate) fn compile_for_refactor<'py>( } } let project: Bound<'py, PyAny> = call_with( - &imported(py, REFACTOR_MODELS, "RefactorProject")?, + ®istry::class(py, RecordId::RefactorProject)?, &[ ("project_dir", project_dir.into_pyobject(py)?.into_any()), ("graph", analysis.getattr("graph")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs index b28bc6ecb7..1532b42d39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs @@ -9,8 +9,9 @@ use sqlbuild_pysource::string_literals::main::string_literal_locations::string_l use sqlbuild_pysource::string_literals::models::StringQuery; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const REFACTORING_MODELS_MODULE: &str = "sqlbuild._native"; /// The discovered Python declarations whose files a refactoring searches. const PYTHON_DECLARATIONS: [&str; 8] = [ "loader_functions", @@ -68,9 +69,7 @@ fn find_python_string_locations<'py>( Some(context) => context.extract()?, None => None, }; - let location_type: Bound<'py, PyAny> = py - .import(REFACTORING_MODELS_MODULE)? - .getattr("ManualLocation")?; + let location_type: Bound<'py, PyAny> = registry::class(py, RecordId::ManualLocation)?; let version = py.version_info(); let mut locations: Vec> = Vec::new(); for (_, relative_path) in python_paths(&request.get("discovered")?)? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs index 854a7e8d8c..d1d5a10730 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs @@ -36,13 +36,13 @@ use crate::bindings::_helpers::boundary::panics::{NativeCompilerError, compiler_ use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::lineage::lineage_index::lineage_key; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::refactor::offline_compile::{OfflineCompile, compile_for_refactor}; use crate::bindings::_helpers::refactor::refactor_facts::{ declaration_placement, model_facts, refactor_facts, }; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REFACTOR_ERRORS_MODULE: &str = "sqlbuild._native"; const STAGING_PREFIX: &str = "sqb-refactor-"; const MOVE_COMMAND: &str = "mv"; const EDIT_ERROR_CODE: &str = "C957"; @@ -58,7 +58,7 @@ fn user_error(py: Python<'_>, (message, code, help): (String, &str, Option = PyDict::new(py); options.set_item("code", code)?; options.set_item("help", help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -68,10 +68,8 @@ fn user_error(py: Python<'_>, (message, code, help): (String, &str, Option, error: RefactorError) -> PyErr { - let class = |name: &str| -> PyErr { - match imported(py, REFACTOR_ERRORS_MODULE, name) - .and_then(|class| class.call1((error.message.clone(),))) - { + let class = |name: ErrorId| -> PyErr { + match registry::error(py, name).and_then(|class| class.call1((error.message.clone(),))) { Ok(raised) => PyErr::from_value(raised), Err(failure) => failure, } @@ -80,8 +78,8 @@ fn refactor_error(py: Python<'_>, error: RefactorError) -> PyErr { RefactorErrorKind::Input => user_error(py, (error.message, &error.code, error.help)), RefactorErrorKind::Edit => user_error(py, (error.message, EDIT_ERROR_CODE, None)), RefactorErrorKind::Write => user_error(py, (error.message, WRITE_ERROR_CODE, error.help)), - RefactorErrorKind::Value => class("RefactorValueError"), - RefactorErrorKind::Io => class("RefactorIOError"), + RefactorErrorKind::Value => class(ErrorId::RefactorValue), + RefactorErrorKind::Io => class(ErrorId::RefactorIO), RefactorErrorKind::Internal => NativeCompilerError::new_err(error.message), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs index 4bc156e557..9440b2cd1d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs @@ -11,14 +11,12 @@ use sqlbuild_refactor::refactoring::models::{ SchemaColumnFacts, SqlFileRole, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; use crate::bindings::_helpers::scopes::scope_reports::scope_move_preview; use crate::bindings::_helpers::scopes::scope_validation::scope_relocated_declarations; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CONFIG_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS: &str = "sqlbuild._native"; -const SCOPE_TYPES: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const MATERIALIZED_KEY: &str = "materialized"; const MIGRATE_FROM_KEY: &str = "migrate_from"; @@ -28,8 +26,8 @@ fn posix(path: &Bound<'_, PyAny>) -> PyResult { path.call_method0("as_posix")?.extract() } -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } fn discovered_files(files: &Bound<'_, PyAny>) -> PyResult> { @@ -123,9 +121,7 @@ fn model(model: &Bound<'_, PyAny>, expansion: Option<&Bound<'_, PyAny>>) -> PyRe let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("values", &values)?; options.set_item("key", MATERIALIZED_KEY)?; - let materialized: Option = py - .import(CONFIG_MODULE)? - .getattr("get_config_str")? + let materialized: Option = registry::native(py, NativeAttr::GetConfigStr)? .call((), Some(&options))? .extract()?; let migrate_from: Option> = values.get_item(MIGRATE_FROM_KEY)?; @@ -266,7 +262,7 @@ fn python_locations( )?; } let mut locations: Vec = Vec::new(); - for location in native(py, "find_python_string_locations")? + for location in native(py, NativeAttr::FindPythonStringLocations)? .call((), Some(&options))? .try_iter()? { @@ -335,15 +331,11 @@ pub(crate) fn declaration_placement( let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item( "kind", - py.import(SCOPE_TYPES)? - .getattr("ResourceKind")? - .getattr("MODEL")?, + registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?, )?; options.set_item("name", model)?; - let resource: Bound<'_, PyAny> = py - .import(SCOPE_MODELS)? - .getattr("ResourceIdentity")? - .call((), Some(&options))?; + let resource: Bound<'_, PyAny> = + registry::class(py, RecordId::ResourceIdentity)?.call((), Some(&options))?; let records: Option> = scope_relocated_declarations(py, &index, &resource, destination)?; if let Some(records) = records { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs index 137c2a04a2..963b752559 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs @@ -10,6 +10,8 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; use crate::bindings::_helpers::model_config::template_data::record_reads; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_expansion::{ DeclarationExpansion, ExpandedDeclarations, ExpansionClasses, ExpansionConsumer, SpanRow, expand_declarations, span_rows, @@ -19,12 +21,6 @@ use crate::bindings::_helpers::render::macro_expansion_result::expand_sql_macros use crate::bindings::_helpers::sqltext::declaration_references::declaration_scan; use crate::bindings::_helpers::sqltext::static_sql::{InterpolationSources, interpolation_batch}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; const FUNCTOOLS_MODULE: &str = "functools"; const DECLARATION_FIELDS: [&str; 4] = [ "enums", @@ -48,7 +44,7 @@ fn substituted<'py>( } else { Some(context_values.downcast()?.clone()) }, - imported(py, NATIVE_MODULE, "render_project_var_text")?, + registry::native(py, NativeAttr::RenderProjectVarText)?, ); let text: String = sql.extract()?; let path: String = file_path.str()?.extract()?; @@ -59,7 +55,7 @@ fn substituted<'py>( record_reads(py, reads.iter().map(|(kind, name)| (*kind, name.as_str())))?; if let Some(message) = error { return Err(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )); } Ok((rendered.unwrap_or(text), spans)) @@ -67,7 +63,7 @@ fn substituted<'py>( /// `ExpansionSpan` objects for span rows, in order. fn span_objects<'py>(py: Python<'py>, spans: &[SpanRow]) -> PyResult> { - let span_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?; + let span_class: Bound<'py, PyAny> = registry::class(py, RecordId::ExpansionSpan)?; let objects: Vec> = spans .iter() .map(|span| span_class.call1(*span)) @@ -94,7 +90,7 @@ fn declaration_context<'py>( }) .collect::>()?; call_with( - &imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?, + ®istry::class(py, RecordId::DeclarationResolutionContext)?, &fields, ) } @@ -116,7 +112,7 @@ fn declarations_expanded<'py>( } let context: Bound<'py, PyAny> = declaration_context(py, declarations)?; let render_constant: Bound<'py, PyAny> = imported(py, FUNCTOOLS_MODULE, "partial")?.call( - (imported(py, NATIVE_MODULE, "render_constant_declaration")?,), + (registry::native(py, NativeAttr::RenderConstantDeclaration)?,), Some(&keywords( py, &[ @@ -127,8 +123,8 @@ fn declarations_expanded<'py>( )?), )?; let classes = ExpansionClasses { - usage_record: imported(py, SCOPE_MODELS_MODULE, "UsageRecord")?, - compile_error: imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, + usage_record: registry::class(py, RecordId::UsageRecord)?, + compile_error: registry::error(py, ErrorId::CompileInput)?, }; let path: String = file_path.str()?.extract()?; let expanded: ExpandedDeclarations<'py> = expand_declarations(&DeclarationExpansion { @@ -213,7 +209,7 @@ fn path_declarations<'py>( ) -> PyResult> { let py: Python<'py> = context.py(); let resolution_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?; + registry::class(py, RecordId::DeclarationResolutionContext)?; let resolver: Bound<'py, PyAny> = context.getattr("declaration_resolver")?; let declarations: Bound<'py, PyAny> = if resolver.is_none() { call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs index 2c74115fff..6bfd127f0e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs @@ -2,23 +2,19 @@ use std::collections::HashMap; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; const DATACLASSES_MODULE: &str = "dataclasses"; /// `CompileInputError(message)`. fn compile_input_error(py: Python<'_>, message: String) -> PyErr { - match py - .import(COMPILE_ERRORS_MODULE) - .and_then(|module| module.getattr(COMPILE_INPUT_ERROR)) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::CompileInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs index b2aca182b7..13b5c6e3a5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs @@ -7,21 +7,15 @@ use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyInt, PyList, PyString, P use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; const NO_DECLARATION_KIND: u8 = 255; /// Declaration kinds by their native code: enum 0, constant 1, macro 2. const DECLARATION_KINDS: [&str; 3] = ["ENUM", "CONSTANT", "MACRO"]; -const SQL_VALUE_ERRORS_MODULE: &str = "sqlbuild.sql_values.exceptions"; const TYPES_MODULE: &str = "types"; const SCALAR_KINDS: [&str; 6] = ["string", "integer", "boolean", "float", "decimal", "null"]; const COLLECTION_KINDS: [&str; 2] = ["list", "set"]; @@ -50,8 +44,8 @@ fn runtime_declarations<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let classes = ( - imported(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, - imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?, + registry::class(py, RecordId::DeclarationIdentity)?, + registry::class(py, RecordId::DeclarationKind)?, ); let declarations: Bound<'py, PyDict> = PyDict::new(py); for (files, kind) in [("enum_files", "ENUM"), ("constant_files", "CONSTANT")] { @@ -64,10 +58,9 @@ fn runtime_declarations<'py>( } } } - let resource_identity: Bound<'py, PyAny> = - imported(py, SCOPE_MODELS_MODULE, "ResourceIdentity")?; + let resource_identity: Bound<'py, PyAny> = registry::class(py, RecordId::ResourceIdentity)?; let model_kind: Bound<'py, PyAny> = - imported(py, SCOPE_TYPES_MODULE, "ResourceKind")?.getattr("MODEL")?; + registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?; for model_file in discovered.getattr("model_files")?.try_iter()? { let model_file: Bound<'py, PyAny> = model_file?; let name: Bound<'py, PyAny> = model_file @@ -110,7 +103,7 @@ fn runtime_declarations<'py>( fn resource_specific<'py>(scope_index: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = scope_index.py(); let private: Bound<'py, PyAny> = - imported(py, SCOPE_TYPES_MODULE, "ScopeKind")?.getattr("PRIVATE")?; + registry::class(py, RecordId::ScopeKind)?.getattr("PRIVATE")?; let mut resources: Vec> = Vec::new(); for declaration in scope_index.getattr("declarations")?.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; @@ -139,7 +132,7 @@ pub(crate) fn declaration_scope_resolver<'py>( lookup.clone() }; let projection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeclarationRuntimeProjection")?, + ®istry::class(py, RecordId::DeclarationRuntimeProjection)?, &[( "declarations", imported(py, TYPES_MODULE, "MappingProxyType")?.call1((&declarations,))?, @@ -148,7 +141,7 @@ pub(crate) fn declaration_scope_resolver<'py>( let native_contexts: Bound<'py, PyAny> = native_declaration_contexts(&lookup, declarations.as_any())?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeclarationScopeResolver")?, + ®istry::class(py, RecordId::DeclarationScopeResolver)?, &[ ("project_dir", discovered.getattr("project_dir")?), ("lookup", lookup), @@ -204,15 +197,15 @@ pub(crate) fn native_declaration_contexts<'py>( declarations: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = lookup.py(); - let kind_type: Bound<'py, PyAny> = imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?; + let kind_type: Bound<'py, PyAny> = registry::class(py, RecordId::DeclarationKind)?; let kinds: Vec> = DECLARATION_KINDS .iter() .map(|kind| kind_type.getattr(*kind)) .collect::>()?; let value_classes: Vec> = vec![ - imported(py, DISCOVERY_MODELS_MODULE, "EnumDeclaration")?, - imported(py, DISCOVERY_MODELS_MODULE, "ConstantDeclaration")?, - imported(py, COMPILE_MODELS_MODULE, "LoadedMacro")?, + registry::class(py, RecordId::EnumDeclaration)?, + registry::class(py, RecordId::ConstantDeclaration)?, + registry::class(py, RecordId::LoadedMacro)?, ]; let index: Bound<'py, PyAny> = lookup.getattr("visibility_index")?; let lookup_declarations: Bound<'py, PyAny> = lookup.getattr("declarations")?; @@ -241,18 +234,18 @@ pub(crate) fn native_declaration_contexts<'py>( )?); } let reasons: Bound<'py, PyDict> = PyDict::new(py); - for reason in imported(py, SCOPE_TYPES_MODULE, "VisibilityReason")?.try_iter()? { + for reason in registry::class(py, RecordId::VisibilityReason)?.try_iter()? { let reason: Bound<'py, PyAny> = reason?; reasons.set_item(reason.getattr("value")?, reason)?; } let classes: Bound<'py, PyDict> = PyDict::new(py); classes.set_item( "visibility_record", - imported(py, SCOPE_MODELS_MODULE, "VisibilityRecord")?, + registry::class(py, RecordId::VisibilityRecord)?, )?; classes.set_item( "resolution_context", - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?, + registry::class(py, RecordId::DeclarationResolutionContext)?, )?; classes.set_item("reasons", reasons)?; let keys: Bound<'py, PyDict> = PyDict::new(py); @@ -268,29 +261,29 @@ pub(crate) fn native_declaration_contexts<'py>( lookup.getattr("index")?.getattr("declarations")?, )?; for (name, class) in [ - ("resource_record", "ResourceRecord"), - ("ownership_root", "OwnershipRoot"), - ("resource_identity", "ResourceIdentity"), - ("declaration_identity", "DeclarationIdentity"), + ("resource_record", RecordId::ResourceRecord), + ("ownership_root", RecordId::OwnershipRoot), + ("resource_identity", RecordId::ResourceIdentity), + ("declaration_identity", RecordId::DeclarationIdentity), ] { - lookups.set_item(name, imported(py, SCOPE_MODELS_MODULE, class)?)?; + lookups.set_item(name, registry::class(py, class)?)?; } lookups.set_item( "resource_kind", - imported(py, SCOPE_TYPES_MODULE, "ResourceKind")?, + registry::class(py, RecordId::ResourceKind)?, )?; lookups.set_item("declaration_kind", kind_type)?; lookups.set_item( "private_scope", - imported(py, SCOPE_TYPES_MODULE, "ScopeKind")?.getattr("PRIVATE")?, + registry::class(py, RecordId::ScopeKind)?.getattr("PRIVATE")?, )?; lookups.set_item( "identity_error", - imported(py, SCOPE_ERRORS_MODULE, "InvalidQualifiedIdentityError")?, + registry::error(py, ErrorId::InvalidQualifiedIdentity)?, )?; lookups.set_item( "path_error", - imported(py, SCOPE_ERRORS_MODULE, "InvalidScopePathError")?, + registry::error(py, ErrorId::InvalidScopePath)?, )?; let global_positions: Bound<'py, PyList> = PyList::new( py, @@ -299,7 +292,7 @@ pub(crate) fn native_declaration_contexts<'py>( .try_iter()? .collect::>>()?, )?; - imported(py, NATIVE_MODULE, "NativeDeclarationContexts")?.call1(( + registry::native(py, NativeAttr::NativeDeclarationContexts)?.call1(( PyList::new(py, positions)?, ( global_positions, @@ -365,7 +358,7 @@ fn rendered_constant<'py>( declaration.getattr("name")?.str()? ); return Err(PyErr::from_value( - imported(declaration.py(), NATIVE_MODULE, "SqlValueValidationError")? + registry::native(declaration.py(), NativeAttr::SqlValueValidationError)? .call1((format!( "{context} rendered value is {size} bytes; maximum is {MAX_SQL_VALUE_SIZE} bytes" ),))?, @@ -391,10 +384,9 @@ pub(crate) fn constant_declaration_sql<'py>( Ok(rendered) => return Ok(rendered), Err(error) => error, }; - let rendering_error: Bound<'py, PyAny> = - imported(py, SQL_VALUE_ERRORS_MODULE, "SqlValueRenderingError")?; + let rendering_error: Bound<'py, PyAny> = host::entry(py, ExecutorHost::SqlValueRenderingError)?; let validation_error: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "SqlValueValidationError")?; + registry::native(py, NativeAttr::SqlValueValidationError)?; let raised: Bound<'py, PyAny> = error.value(py).clone().into_any(); if !(raised.is_instance(&rendering_error)? || raised.is_instance(&validation_error)?) { return Err(error); @@ -405,7 +397,7 @@ pub(crate) fn constant_declaration_sql<'py>( _ => relative_path.clone(), }; let compile_error: Bound<'py, PyAny> = - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((format!( + registry::error(py, ErrorId::CompileInput)?.call1((format!( "{} constant '{}' could not be rendered in '{}' by adapter '{}' as {}: {}", relative_path.str()?, declaration.getattr("name")?.str()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs index 208e75a836..30dd1f7d1f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs @@ -10,13 +10,13 @@ use sqlbuild_render::macro_calls::main::splice_macro_calls::splice_macro_calls a use sqlbuild_render::macro_calls::models::{MacroCallScan, MacroScanFailure}; use crate::bindings::_helpers::boundary::panics::compiler_error; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::macro_call_memo::MacroCallMemo; use crate::bindings::_helpers::render::macro_call_rows::{SiteRow, SpanRow, site_row, span_row}; use crate::bindings::_helpers::render::macro_expansion::raised; use crate::bindings::types::CompilerDetach; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DECLARATION_CALLS: [&str; 2] = ["enum", "const"]; /// `(call start or None, call name or None, message)` of the error Python's scan raises. @@ -60,7 +60,7 @@ pub(crate) fn top_level_macro_call_names<'py>( .collect::>(), ), Err(error) => Err(raised( - &imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")?, + ®istry::error(py, ErrorId::CompileInput)?, error.message(), )), } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs index 230858dbbf..ee59c533ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs @@ -9,6 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::resolved_declaration_context; use crate::bindings::_helpers::render::macro_call_rows::span_row; use crate::bindings::_helpers::render::macro_expansion::{ @@ -16,23 +20,11 @@ use crate::bindings::_helpers::render::macro_expansion::{ }; use crate::bindings::_helpers::sqltext::sql_references::extract_references; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const FRONTIER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const SCOPES_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPES_TYPES_MODULE: &str = "sqlbuild._native"; -const REF_MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const REF_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; const TYPED_REFERENCE_FUNCTIONS: [(&str, &str); 3] = [ ("model", "__ref"), ("source", "__source"), ("seed", "__seed"), ]; -const USER_MACROS_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; -const ACTIVE_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.main.active_macro_bridge"; /// The call prefix of every reference function, in `SqlReferenceKind` order. const GENERATED_REFERENCE_MARKERS: [&str; 6] = [ "__ref(", @@ -42,9 +34,6 @@ const GENERATED_REFERENCE_MARKERS: [&str; 6] = [ "__udf(", "__table_fn(", ]; -const DIAGNOSTICS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SQL_ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; const BUILTINS_MODULE: &str = "builtins"; const MACRO_TOKEN: &str = "@"; const GENERATED_ALLOWED: &str = "macro-generated references"; @@ -64,8 +53,7 @@ struct ExpansionRequest<'py> { } fn generated_classes(py: Python<'_>) -> PyResult> { - let native: Bound<'_, PyAny> = py.import(NATIVE_MODULE)?.into_any(); - let help: Bound<'_, PyAny> = native.getattr("explicit_references_help")?.call( + let help: Bound<'_, PyAny> = registry::native(py, NativeAttr::ExplicitReferencesHelp)?.call( (), Some(&keywords( py, @@ -78,23 +66,23 @@ fn generated_classes(py: Python<'_>) -> PyResult> { &[ ( "compiler_diagnostic", - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + registry::class(py, RecordId::CompilerDiagnostic)?, ), ( "report_diagnostic", - imported(py, DIAGNOSTICS_MODULE, "report_compile_diagnostic")?, + registry::native(py, NativeAttr::ReportCompileDiagnostic)?, ), ( "source_location", - imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, ), ( "resource_identity", - imported(py, SCOPES_MODELS_MODULE, "ResourceIdentity")?, + registry::class(py, RecordId::ResourceIdentity)?, ), ( "generic_syntax", - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")? + registry::class(py, RecordId::SqlLexicalSyntax)? .call0()? .getattr("native_mapping")?, ), @@ -105,9 +93,8 @@ fn generated_classes(py: Python<'_>) -> PyResult> { } fn built_classes(py: Python<'_>) -> PyResult> { - let native: Bound<'_, PyAny> = py.import(NATIVE_MODULE)?.into_any(); - let declaration_kind: Bound<'_, PyAny> = imported(py, SCOPES_TYPES_MODULE, "DeclarationKind")?; - let ref_kind: Bound<'_, PyAny> = imported(py, REF_TYPES_MODULE, "SqlResourceRefKind")?; + let declaration_kind: Bound<'_, PyAny> = registry::class(py, RecordId::DeclarationKind)?; + let ref_kind: Bound<'_, PyAny> = host::entry(py, UserCodeHost::SqlResourceRefKind)?; let ref_kinds: Bound<'_, PyDict> = PyDict::new(py); let ref_functions: Bound<'_, PyDict> = PyDict::new(py); for (kind, function) in TYPED_REFERENCE_FUNCTIONS { @@ -117,61 +104,61 @@ fn built_classes(py: Python<'_>) -> PyResult> { let store_tokens: Bound<'_, PyDict> = keywords( py, &[ - ("macro_token", native.getattr("macro_store_token")?), - ("context_token", native.getattr("context_store_token")?), + ( + "macro_token", + registry::native(py, NativeAttr::MacroStoreToken)?, + ), + ( + "context_token", + registry::native(py, NativeAttr::ContextStoreToken)?, + ), ], )?; keywords( py, &[ - ( - "compile_error", - imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")?, - ), + ("compile_error", registry::error(py, ErrorId::CompileInput)?), ( "macro_argument_error", - imported(py, COMPILE_EXCEPTIONS_MODULE, "MacroArgumentError")?, + registry::error(py, ErrorId::MacroArgument)?, ), ( "mismatch_error", - imported(py, FRONTIER_EXCEPTIONS_MODULE, "NativeStageMismatchError")?, + registry::error(py, ErrorId::NativeStageMismatch)?, ), ( "declaration_identity", - imported(py, SCOPES_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, ), ("macro_kind", declaration_kind.getattr("MACRO")?), ("declaration_kind", declaration_kind), - ( - "usage_record", - imported(py, SCOPES_MODELS_MODULE, "UsageRecord")?, - ), + ("usage_record", registry::class(py, RecordId::UsageRecord)?), ( "runtime_usage", - imported(py, SCOPES_TYPES_MODULE, "UsageKind")?.getattr("RUNTIME")?, + registry::class(py, RecordId::UsageKind)?.getattr("RUNTIME")?, ), ( "resource_identity", - imported(py, SCOPES_MODELS_MODULE, "ResourceIdentity")?, + registry::class(py, RecordId::ResourceIdentity)?, ), ( "model_kind", - imported(py, SCOPES_TYPES_MODULE, "ResourceKind")?.getattr("MODEL")?, + registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?, ), ( "resource_ref", - imported(py, REF_MODELS_MODULE, "SqlResourceRef")?, + host::entry(py, UserCodeHost::SqlResourceRef)?, ), ("ref_kinds", ref_kinds.into_any()), ("ref_functions", ref_functions.into_any()), ("store_tokens", store_tokens.into_any()), ( "relation_renderer", - imported(py, REF_MODELS_MODULE, "RELATION_RENDERER")?, + host::entry(py, UserCodeHost::RelationRenderer)?, ), ( "invocation_context", - imported(py, USER_MACROS_MODULE, "macro_invocation_context")?, + host::entry(py, UserCodeHost::MacroInvocationContext)?, ), ("generated", generated_classes(py)?.into_any()), ("int_type", imported(py, BUILTINS_MODULE, "int")?), @@ -200,10 +187,7 @@ fn result_model<'py>( py: Python<'py>, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with( - &imported(py, NATIVE_RECORDS_MODULE, "MacroExpansionResult")?, - pairs, - ) + call_with(®istry::class(py, RecordId::MacroExpansionResult)?, pairs) } fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult> { @@ -238,10 +222,9 @@ fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult = Bound::new(py, expansion)?; - let bridge: Bound<'py, PyAny> = - imported(py, ACTIVE_BRIDGE_MODULE, "active_macro_bridge")?.call0()?; + let bridge: Bound<'py, PyAny> = host::entry(py, UserCodeHost::ActiveMacroBridge)?.call0()?; let output: ExpansionOutput = expanded(&expansion, request.sql.to_str()?, &bridge)?; - let span_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?; + let span_class: Bound<'py, PyAny> = registry::class(py, RecordId::ExpansionSpan)?; let mut spans: Vec> = Vec::with_capacity(output.spans.len()); for span in output.spans { let (source_start, source_end, output_start, output_end) = span_row(span); @@ -263,8 +246,7 @@ fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult = - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")?.call0()?; + let syntax: Bound<'py, PyAny> = registry::class(py, RecordId::SqlLexicalSyntax)?.call0()?; extract_references(&calls.join(" "), &syntax)?.into_any() }; result_model( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs index 9c892671f1..cf0ecef542 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs @@ -7,10 +7,10 @@ use sqlbuild_core::build_aggregate::main::aggregate_build_counts::aggregate_buil use sqlbuild_core::build_aggregate::models::{BuildCounts, BuildResultFacts, ModelResultFacts}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const BUILD_MODELS_MODULE: &str = "sqlbuild.executor.build.models"; -const BUILD_TYPES_MODULE: &str = "sqlbuild.executor.build.types"; const RESULT_FIELDS: [&str; 7] = [ "model_results", "seed_results", @@ -68,7 +68,7 @@ pub(crate) fn aggregated_build_result<'py>( sections: &[Bound<'py, PyAny>], ) -> PyResult> { let counts: BuildCounts = aggregate_build_counts(&facts(sections)?); - let status: Bound<'py, PyAny> = imported(py, BUILD_TYPES_MODULE, "BuildStatus")? + let status: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BuildStatus)? .getattr(if counts.failed { "FAILED" } else { "SUCCESS" })?; let mut keywords: Vec<(&str, Bound<'py, PyAny>)> = vec![("status", status)]; keywords.extend(RESULT_FIELDS.iter().copied().zip(sections.iter().cloned())); @@ -83,7 +83,7 @@ pub(crate) fn aggregated_build_result<'py>( ), ]); call_with( - &imported(py, BUILD_MODELS_MODULE, "BuildExecutionResult")?, + &host::entry(py, ExecutorHost::BuildExecutionResult)?, &keywords, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs index 4b5798fdde..836e7da804 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs @@ -4,8 +4,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_outputs::json_report::indented_json; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::values::{any, dict, items, text}; use crate::bindings::_helpers::result_output::aggregation::aggregated_from_lists; use crate::bindings::_helpers::result_output::assets::{ @@ -44,7 +46,6 @@ const DEGRADABLE_BUILD_OPTIONS: (&[&str], &[&str]) = ( "cost", ], ); -const OBSERVABILITY_ERRORS: &str = "sqlbuild.runtime.observability.exceptions"; const SCENARIO_OPTIONS: (&[&str], &[&str]) = ( &["results"], &["local", "run_namespace", "namespace_source", "duration_ms"], @@ -244,7 +245,7 @@ fn format_degradable_build_execution_json<'py>( || error.is_instance_of::(py) || error.is_instance( py, - &imported(py, OBSERVABILITY_ERRORS, "ObservabilityValidationError")?, + &host::entry(py, ExecutorHost::ObservabilityValidationError)?, ); if !degradable { return Err(error); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs index b98ab9a191..5ad6ed5ace 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs @@ -9,17 +9,15 @@ use sqlbuild_core::integration_results::errors::IntegrationRecordError; use sqlbuild_core::integration_results::main::encode_integration_value::encode_integration_value; use sqlbuild_core::json::models::{JsonInteger, JsonValue}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const OBSERVABILITY_ERRORS: &str = "sqlbuild.runtime.observability.exceptions"; -const VALIDATION_ERROR: &str = "ObservabilityValidationError"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; /// A contract failure, or a Python failure while reading the values. pub(crate) type Checked = PyResult>; /// The `ObservabilityValidationError` Python raised for the same contract failure. pub(crate) fn validation_error(py: Python<'_>, error: &IntegrationRecordError) -> PyErr { - match imported(py, OBSERVABILITY_ERRORS, VALIDATION_ERROR) + match host::entry(py, ExecutorHost::ObservabilityValidationError) .and_then(|class| class.call1((error.message.as_str(),))) { Ok(instance) => PyErr::from_value(instance), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs index 1cc2df689e..a2fdde3fb4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs @@ -3,19 +3,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyRef, PyResult, Python}; use pyo3::types::{PyFloat, PyInt}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::values::text; use crate::bindings::_helpers::result_output::resource_ids::{audit_identity, sql_test_identity}; use crate::bindings::_helpers::result_output::terminal_index::{TerminalEventIndex, current_index}; -const RUN_MODELS: &str = "sqlbuild.executor.run.models"; -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const LOAD_MODELS: &str = "sqlbuild.executor.load.models"; -const PYTHON_NODE_MODELS: &str = "sqlbuild.executor.python_nodes.models"; -const TESTING_MODELS: &str = "sqlbuild.executor.testing.models"; -const AUDITING_MODELS: &str = "sqlbuild.executor.auditing.models"; -const CLONE_MODELS: &str = "sqlbuild.executor.clone.models"; - /// The executor result types that result output distinguishes. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum ResultKind { @@ -34,35 +27,25 @@ pub(crate) enum ResultKind { /// The kind of one executor result, by its Python class. pub(crate) fn result_kind(result: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = result.py(); - let kinds: [(&str, &str, ResultKind); 9] = [ - (RUN_MODELS, "ModelExecutionResult", ResultKind::Model), - (BUILD_MODELS, "SeedExecutionResult", ResultKind::Seed), - ( - BUILD_MODELS, - "FunctionExecutionResult", - ResultKind::Function, - ), - (LOAD_MODELS, "LoadExecutionResult", ResultKind::Load), + let kinds: [(ExecutorHost, ResultKind); 9] = [ + (ExecutorHost::ModelExecutionResult, ResultKind::Model), + (ExecutorHost::SeedExecutionResult, ResultKind::Seed), + (ExecutorHost::FunctionExecutionResult, ResultKind::Function), + (ExecutorHost::LoadExecutionResult, ResultKind::Load), ( - PYTHON_NODE_MODELS, - "PythonNodeExecutionResult", + ExecutorHost::PythonNodeExecutionResult, ResultKind::PythonNode, ), ( - PYTHON_NODE_MODELS, - "PythonCheckExecutionResult", + ExecutorHost::PythonCheckExecutionResult, ResultKind::PythonCheck, ), - ( - TESTING_MODELS, - "SqlTestExecutionResult", - ResultKind::SqlTest, - ), - (AUDITING_MODELS, "AuditExecutionResult", ResultKind::Audit), - (CLONE_MODELS, "CloneItemResult", ResultKind::CloneItem), + (ExecutorHost::SqlTestExecutionResult, ResultKind::SqlTest), + (ExecutorHost::AuditExecutionResult, ResultKind::Audit), + (ExecutorHost::CloneItemResult, ResultKind::CloneItem), ]; - for (module, class_name, kind) in kinds { - if result.is_instance(&imported(py, module, class_name)?)? { + for (class, kind) in kinds { + if result.is_instance(&host::entry(py, class)?)? { return Ok(kind); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs index 5df6e3d505..df2a6ce8d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs @@ -11,6 +11,8 @@ use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_core::terminal_events::main::claim_resource_terminal::claim_resource_terminal; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::terminal_events::main::consume_terminal_fact::consume_terminal_fact; use sqlbuild_core::terminal_events::main::latest_resource_terminal::latest_resource_terminal; use sqlbuild_core::terminal_events::main::lifecycle_terminal::lifecycle_terminal; @@ -19,7 +21,6 @@ use sqlbuild_core::terminal_events::models::{TerminalFact, TerminalIndex, Termin /// The module attribute holding the `ContextVar` of the installed index. const CONTEXT_ATTRIBUTE: &str = "_terminal_event_index_context"; const CONTEXT_NAME: &str = "sqlbuild_terminal_event_index"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const CLAIM_OPTIONS: (&[&str], &[&str]) = ( &["resource_name"], &["resource_id", "resource_attempt_id", "attempt_number"], @@ -229,7 +230,7 @@ pub(crate) struct TerminalEventIndexScope { } fn context_variable(py: Python<'_>) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(CONTEXT_ATTRIBUTE) + registry::native(py, NativeAttr::TerminalEventIndexContext) } #[pymethods] diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs index 7eb07bb0da..670af04e79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs @@ -1,5 +1,6 @@ //! Layouts of the rule engine value records hosted natively. +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_RULE_GUIDANCE: RecordSpec = RecordSpec { @@ -297,15 +298,12 @@ const R_RULES_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("retired_source_tokens", FieldDefault::EmptyDict), FieldSpec::new( "sql_tests", - FieldDefault::Call("sqlbuild._native", "SqlTestRulesConfig"), - ), - FieldSpec::new( - "layout", - FieldDefault::Call("sqlbuild._native", "LayoutConfig"), + FieldDefault::NativeCall(RecordId::SqlTestRulesConfig), ), + FieldSpec::new("layout", FieldDefault::NativeCall(RecordId::LayoutConfig)), FieldSpec::new( "cache", - FieldDefault::Call("sqlbuild._native", "RulesCacheConfig"), + FieldDefault::NativeCall(RecordId::RulesCacheConfig), ), ], }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs index 3a0cfaa261..9317352ed2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs @@ -1,6 +1,5 @@ //! The built-in rule catalogue returned as the Python `Rule` models. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -11,9 +10,9 @@ use sqlbuild_rules::rules::main::selected_sql_codes::selected_sql_codes; use sqlbuild_rules::rules::models::{BuiltinOption, BuiltinOptionKind}; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; const FAMILY_SUFFIX_LENGTH: usize = 3; fn keywords<'py>( @@ -51,19 +50,18 @@ fn option<'py>( } fn rule<'py>( - models: &Bound<'py, PyModule>, + py: Python<'py>, subjects: &Bound<'py, PyAny>, check: &Bound<'py, PyAny>, metadata: RuleMetadata, ) -> PyResult> { - let py: Python<'py> = models.py(); - let option_class: Bound<'py, PyAny> = models.getattr("RuleOption")?; + let option_class: Bound<'py, PyAny> = registry::class(py, RecordId::RuleOption)?; let options: Vec> = builtin_options(&metadata.code) .into_iter() .map(|declared| option(&option_class, declared)) .collect::>()?; let guidance: Bound<'py, PyAny> = match metadata.guidance { - Some(guidance) => shape_class(models.as_any(), "RuleGuidance")?.call( + Some(guidance) => registry::class(py, RecordId::RuleGuidance)?.call( (), Some(&keywords( py, @@ -112,7 +110,7 @@ fn rule<'py>( ("guidance", guidance), ], )?; - models.getattr("Rule")?.call((), Some(&arguments)) + registry::class(py, RecordId::Rule)?.call((), Some(&arguments)) } /// Every built-in rule as a `Rule` whose `check` is the given placeholder. @@ -120,11 +118,10 @@ pub(crate) fn builtin_rules<'py>( py: Python<'py>, check: Bound<'py, PyAny>, ) -> PyResult>> { - let models: Bound<'py, PyModule> = py.import(MODELS_MODULE)?; - let subjects: Bound<'py, PyAny> = py.import(TYPES_MODULE)?.getattr("RuleSubject")?; + let subjects: Bound<'py, PyAny> = registry::class(py, RecordId::RuleSubject)?; compiler_guard(|| Ok(catalogue()))? .into_iter() - .map(|metadata| rule(&models, &subjects, &check, metadata)) + .map(|metadata| rule(py, &subjects, &check, metadata)) .collect() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs index d872186f32..5452d97113 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs @@ -14,13 +14,14 @@ use sqlbuild_rules::rule_catalogue::models::{CatalogueRule, OptionKind, OptionSp use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::builtin_catalogue::builtin_rules; use crate::bindings::_helpers::rules::custom_rule_evidence::verify_custom_rules as verify_rule_sources; use crate::bindings::_helpers::rules::selection::{rules_error, selected_rule_codes}; -const CUSTOM_CATALOGUE_MODULE: &str = "sqlbuild.rule_engine._helpers.engine._custom_catalogue"; -const CUSTOM_PAYLOADS_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.custom_payloads"; -const PLANNER_SCOPE_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const JSON_MODULE: &str = "json"; const BUILTINS_MODULE: &str = "builtins"; @@ -186,7 +187,7 @@ fn custom_rules<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult( let payloads: Bound<'py, PyAny> = match custom_payloads { Some(payloads) if !payloads.is_none() => payloads, _ => call_with( - &imported(py, CUSTOM_PAYLOADS_MODULE, "custom_rule_payloads")?, + &host::entry(py, UserCodeHost::CustomRulePayloads)?, &[ ("catalogue", PyTuple::new(py, catalogue)?.into_any()), ("project_dir", project_dir.clone()), @@ -336,7 +337,7 @@ pub(crate) fn resolved_ignore_selectors<'py>( return Ok(config.clone()); } let replace: Bound<'py, PyAny> = imported(py, DATACLASSES_MODULE, "replace")?; - let scope: Bound<'py, PyAny> = imported(py, PLANNER_SCOPE_MODULE, "build_planner_scope")?; + let scope: Bound<'py, PyAny> = registry::native(py, NativeAttr::BuildPlannerScope)?; let path_by_key: Bound<'py, PyDict> = resource_paths(project)?; let mut resolved: Vec> = Vec::with_capacity(ignores.len()); for ignore in ignores { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs index 613e955c91..dc8359a764 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs @@ -26,6 +26,3 @@ pub(crate) const CUSTOM_HOST_CANCELLED_MARKER: &str = "cancelled"; pub(crate) const TYPE_PROOF_RULE_CODES: [&str; 1] = ["SQBRCONTRACT105"]; /// Runtime identity custom-rule hosts and their cache entries record. pub(crate) const CUSTOM_HOST_RUNTIME_VERSION: &str = "sqlbuild-rules-custom-v2"; -/// Module a custom-rule host process runs. -pub(crate) const CUSTOM_HOST_LAUNCH_MODULE: &str = - "sqlbuild.rule_engine._helpers.host.custom_host_launch"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs index 6908890048..6d32501eee 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs @@ -19,6 +19,10 @@ use sqlbuild_rules::custom_rule_cache::types::ReadSet; use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::{ CUSTOM_HOST_RUNTIME_VERSION, CUSTOM_RULES_CACHE_FILE, }; @@ -27,13 +31,6 @@ use crate::bindings::_helpers::rules::custom_hosts::{ }; use crate::bindings::_helpers::rules::custom_rule_cache::CustomRuleCache; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const RULE_CONTEXT_MODULE: &str = "sqlbuild.rule_engine.classes.rule_context"; -const FACT_REPLAY_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.fact_replay"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; const PROJECT_SUBJECT: &str = "project"; const MODEL_SUBJECT: &str = "model"; /// Project fields no rule fact reads, cleared before digests and host payloads use the project. @@ -109,7 +106,7 @@ pub(crate) fn evaluate_custom_rules_cached<'py>( let cache_path: Option = if enabled { Some( call_with( - &imported(py, NATIVE_MODULE, "rules_bulk_cache_path")?, + ®istry::native(py, NativeAttr::RulesBulkCachePath)?, &[ ("project_dir", run.project_dir.clone()), ( @@ -182,7 +179,7 @@ pub(crate) fn outcome<'py>( (custom_ms, custom_cpu_ms): (i64, i64), ) -> PyResult> { call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CustomRulesOutcome")?, + ®istry::class(py, RecordId::CustomRulesOutcome)?, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ("cache_hits", hits.into_bound_py_any(py)?), @@ -265,7 +262,7 @@ impl<'py> FactDigests<'py> { project_dir: project_dir.clone(), dialect: dialect.to_owned(), views: None, - shared: imported(project.py(), FACT_REPLAY_MODULE, "shared_fact_encoder")?.call0()?, + shared: host::entry(project.py(), UserCodeHost::SharedFactEncoder)?.call0()?, digests: HashMap::new(), }) } @@ -276,7 +273,7 @@ impl<'py> FactDigests<'py> { } let py: Python<'py> = self.project.py(); let views: Bound<'py, PyAny> = call_with( - &imported(py, RULE_CONTEXT_MODULE, "build_rule_fact_views")?, + &host::entry(py, UserCodeHost::BuildRuleFactViews)?, &[ ("project", self.project.clone()), ("project_dir", self.project_dir.clone()), @@ -295,7 +292,7 @@ impl<'py> FactDigests<'py> { let py: Python<'py> = self.project.py(); let views: Bound<'py, PyAny> = self.views()?; let digest: String = call_with( - &imported(py, FACT_REPLAY_MODULE, "fact_outcome_digest")?, + &host::entry(py, UserCodeHost::FactOutcomeDigest)?, &[ ("views", views), ("key", PyTuple::new(py, key)?.into_any()), @@ -326,7 +323,7 @@ impl<'py> FactDigests<'py> { observed: &BTreeMap>, ) -> PyResult> { let py: Python<'py> = self.project.py(); - let is_tree_fact: Bound<'py, PyAny> = imported(py, FACT_REPLAY_MODULE, "is_tree_fact")?; + let is_tree_fact: Bound<'py, PyAny> = host::entry(py, UserCodeHost::IsTreeFact)?; for key in readset { if is_tree_fact.call1((PyTuple::new(py, key)?,))?.is_truthy()? && let Some(seen) = observed.get(key) @@ -341,7 +338,7 @@ impl<'py> FactDigests<'py> { /// Keys covering every fact a custom rule can observe through its context. fn full_fact_keys(&mut self) -> PyResult> { let py: Python<'py> = self.project.py(); - imported(py, FACT_REPLAY_MODULE, "full_fact_keys")? + host::entry(py, UserCodeHost::FullFactKeys)? .call1((self.views()?,))? .extract() } @@ -361,7 +358,7 @@ impl<'py> FactDigests<'py> { } fn fact_digest_error(py: Python<'_>, error: &PyErr) -> PyResult { - let class: Bound<'_, PyAny> = imported(py, RULES_ERRORS_MODULE, "FactDigestError")?; + let class: Bound<'_, PyAny> = registry::error(py, ErrorId::FactDigest)?; error.value(py).is_instance(&class) } @@ -380,8 +377,8 @@ fn host_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> let py: Python<'py> = project.py(); let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("include_sql_expansions", false)?; - let record: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "compiled_project_record")?.call((project,), Some(&options))?; + let record: Bound<'py, PyAny> = registry::native(py, NativeAttr::CompiledProjectRecord)? + .call((project,), Some(&options))?; let changes: Bound<'py, PyDict> = PyDict::new(py); for (name, cleared) in HOST_PROJECT_CLEARED { match cleared { @@ -392,7 +389,7 @@ fn host_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> } changes.set_item( "scope_index", - imported(py, SCOPE_MODELS_MODULE, "ScopeIndex")?.call0()?, + registry::class(py, RecordId::ScopeIndex)?.call0()?, )?; imported(py, "dataclasses", "replace")?.call((record,), Some(&changes)) } @@ -448,14 +445,14 @@ fn rule_identity( Some(fingerprint) => fingerprint.into_bound_py_any(py)?, None => { let closure: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "custom_rule_import_closure")?, + ®istry::native(py, NativeAttr::CustomRuleImportClosure)?, &[ ("rule", rule.clone()), ("project_dir", run.project_dir.clone()), ], )?; call_with( - &imported(py, NATIVE_MODULE, "custom_rule_implementation_fingerprint")?, + ®istry::native(py, NativeAttr::CustomRuleImplementationFingerprint)?, &[ ("rule", rule.clone()), ("project_dir", run.project_dir.clone()), @@ -466,7 +463,7 @@ fn rule_identity( }; let subject: Option = subject_value(rule)?; call_with( - &imported(py, NATIVE_MODULE, "custom_rule_identity")?, + ®istry::native(py, NativeAttr::CustomRuleIdentity)?, &[ ( "code_identity", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs index bd84c81f04..cf0cc98ebf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs @@ -9,9 +9,9 @@ use pyo3::prelude::{ use pyo3::types::PyTuple; use pyo3::{pyclass, pymethods}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The `sqlbuild._native` attribute tests set to a `CustomHostObserver` to observe host runs. const OBSERVER_ATTRIBUTE: &str = "custom_host_observer"; @@ -60,7 +60,7 @@ pub(crate) struct ObservedHosts { /// The installed observer's overrides and launch log; nothing when no test observes hosts. pub(crate) fn observed_hosts(py: Python<'_>) -> PyResult { - let installed: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, OBSERVER_ATTRIBUTE)?; + let installed: Bound<'_, PyAny> = registry::native(py, NativeAttr::CustomHostObserver)?; if installed.is_none() { return Ok(ObservedHosts { cores: None, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs index a72344175d..033e2c85ad 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs @@ -26,17 +26,16 @@ use sqlbuild_rules::custom_host_pool::types::{HostInvocation, HostPlan}; use sqlbuild_rules::engine::main::custom_host::run_custom_host_json; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::constants::CUSTOM_HOST_LAUNCH_MODULE; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::{ - CUSTOM_HOST_ABANDONED_SUFFIX, CUSTOM_HOST_CANCELLED_MARKER, CUSTOM_HOST_LAUNCH_MODULE, - CUSTOM_HOST_RUNTIME_VERSION, + CUSTOM_HOST_ABANDONED_SUFFIX, CUSTOM_HOST_CANCELLED_MARKER, CUSTOM_HOST_RUNTIME_VERSION, }; use crate::bindings::_helpers::rules::custom_host_observation::{ LaunchLog, ObservedHosts, before_publish, observed_hosts, record_launch, }; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const HOST_INPUTS: [&str; 3] = ["target", "rules-cache", "host-inputs"]; /// The model fields hosts decode; every other model field is dropped from their payload. const PAYLOAD_MODEL_FIELDS: [&str; 18] = [ @@ -398,7 +397,7 @@ pub(crate) fn host_payload_project<'py>( project: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project.py(); - let model_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?; + let model_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledModel)?; let mut models: Vec> = Vec::new(); for model in project.getattr("models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; @@ -409,7 +408,7 @@ pub(crate) fn host_payload_project<'py>( models.push(model_class.call((), Some(&fields))?); } let record: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "compiled_project_record")?.call1((project,))?; + registry::native(py, NativeAttr::CompiledProjectRecord)?.call1((project,))?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("models", PyTuple::new(py, models)?)?; options.set_item("native_project", py.None())?; @@ -476,8 +475,7 @@ fn text(path: &Path) -> String { } fn rules_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, RULES_ERRORS_MODULE, "RulesError").and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::Rules).and_then(|class| class.call1((message,))) { Ok(instance) => PyErr::from_value(instance), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs index d935f4bc93..a1afcc0327 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs @@ -23,11 +23,10 @@ use sqlbuild_rules::custom_rule_cache::types::{ReadSet, StoredRules, SubjectResu use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::findings::{FindingClasses, invalid_finding}; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const RULES_ERROR: &str = "RulesError"; - /// One custom rule's cache identity from its metadata, source fingerprint and settings. #[pyfunction] #[pyo3(signature = (**request))] @@ -50,7 +49,7 @@ fn custom_rule_identity(request: Option<&Bound<'_, PyDict>>) -> PyResult } fn rules_error(py: Python<'_>, message: String) -> PyResult { - let class: Bound<'_, PyAny> = py.import(RULES_ERRORS_MODULE)?.getattr(RULES_ERROR)?; + let class: Bound<'_, PyAny> = registry::error(py, ErrorId::Rules)?; Ok(pyo3::PyErr::from_value(class.call1((message,))?)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs index 3c61e9b37e..75cbfb4da9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs @@ -17,14 +17,15 @@ use sqlbuild_pysource::rule_evidence::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; const PATHLIB_MODULE: &str = "pathlib"; const INSPECT_MODULE: &str = "inspect"; const HASHLIB_MODULE: &str = "hashlib"; const AST_MODULE: &str = "ast"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_ERROR: &str = "RulesError"; -const NON_HERMETIC_RULE_ERROR: &str = "NonHermeticRuleError"; +const RULES_ERROR: ErrorId = ErrorId::Rules; +const NON_HERMETIC_RULE_ERROR: ErrorId = ErrorId::NonHermeticRule; fn python_version(py: Python<'_>) -> (u8, u8) { let version = py.version_info(); @@ -131,8 +132,8 @@ fn custom_rule_test_evidence<'py>( } /// `error_class(message)` from the native module. -fn rules_error(py: Python<'_>, error_class: &str, message: String) -> PyErr { - match imported(py, NATIVE_MODULE, error_class).and_then(|class| class.call1((message,))) { +fn rules_error(py: Python<'_>, error_class: ErrorId, message: String) -> PyErr { + match registry::error(py, error_class).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs index e7af596e65..41f064c542 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs @@ -20,11 +20,9 @@ use sqlbuild_rules::dsl_headers::models::{ use sqlbuild_rules::dsl_headers::types::HeaderParser; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_TYPES_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const SENTINEL_PATH: &str = ""; const ENGINE: &str = "sqlbuild"; @@ -45,10 +43,10 @@ struct PyHeaderParser<'py> { impl<'py> PyHeaderParser<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - parse: imported(py, NATIVE_MODULE, "parse_header_values")?, + parse: registry::native(py, NativeAttr::ParseHeaderValues)?, path: imported(py, PATHLIB_MODULE, "Path")?.call1((SENTINEL_PATH,))?, - syntax_error: imported(py, DISCOVERY_ERRORS_MODULE, "ModelSqlParseError")?, - discovery_error: imported(py, DISCOVERY_ERRORS_MODULE, "DiscoveryError")?, + syntax_error: registry::error(py, ErrorId::ModelSqlParse)?, + discovery_error: registry::error(py, ErrorId::Discovery)?, rejected: RefCell::new(None), }) } @@ -129,8 +127,8 @@ struct LintClasses<'py> { impl<'py> LintClasses<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: imported(py, LINT_MODELS_MODULE, "LintViolation")?, - severity: imported(py, LINT_TYPES_MODULE, "LintSeverity")?, + violation: registry::class(py, RecordId::LintViolation)?, + severity: registry::class(py, RecordId::LintSeverity)?, }) } @@ -211,7 +209,7 @@ fn scan_headers<'py>( first_only: bool, ) -> PyResult> { let py: Python<'py> = contents.py(); - let class: Bound<'py, PyAny> = imported(py, LINT_MODELS_MODULE, "HeaderSpan")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::HeaderSpan)?; let headers: Vec> = scan(&text(contents)?, first_only) .into_iter() .map(|header| { @@ -320,7 +318,7 @@ fn prepare_native_header_cache(files: &Bound<'_, PyDict>) -> PyResult<()> { ); } } - imported(files.py(), NATIVE_MODULE, "prepare_model_header_tokens")?.call1((headers,))?; + registry::native(files.py(), NativeAttr::PrepareModelHeaderTokens)?.call1((headers,))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs index f372189d29..a44077bd9b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs @@ -11,12 +11,9 @@ use pyo3::{IntoPyObject, IntoPyObjectExt, pyclass, pyfunction, pymethods, wrap_p use sqlbuild_rules::rule_cache::main::rules_bulk_cache_path::rules_bulk_cache_path; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULE_THREADS_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; /// The lint thresholds used when the project leaves them unset. const DEFAULT_MAX_LITERAL_LENGTH: i64 = 100; const DEFAULT_MAX_RANKING_ORDER_BY: i64 = 5; @@ -47,7 +44,7 @@ pub(crate) fn sql_lint_config<'py>( thresholds.call_method1("get", (key, default)) }; call_with( - &imported(py, LINT_MODELS_MODULE, "LintConfig")?, + ®istry::class(py, RecordId::LintConfig)?, &[ ("dialect", dialect.clone()), ("enabled_native_rules", PyTuple::new(py, codes)?.into_any()), @@ -74,7 +71,7 @@ fn sql_lint_preparation<'py>( dialect: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = inputs.py(); - let native = |name: &str| imported(py, NATIVE_MODULE, name); + let native = |name: NativeAttr| registry::native(py, name); let project_dir: Bound<'py, PyAny> = inputs .getattr("discovered_inputs")? .getattr("project_dir")?; @@ -87,7 +84,7 @@ fn sql_lint_preparation<'py>( } } let config: Bound<'py, PyAny> = call_with( - &native("valid_rules_config")?, + &native(NativeAttr::ValidRulesConfig)?, &[("project_dir", project_dir.clone())], )?; if config.is_none() || !config.getattr("select")?.is_truthy()? { @@ -95,7 +92,7 @@ fn sql_lint_preparation<'py>( } if config.getattr("cache")?.getattr("enabled")?.is_truthy()? && call_with( - &native("load_sql_rule_cache")?, + &native(NativeAttr::LoadSqlRuleCache)?, &[("path", sql_rule_cache_path(&project_dir)?)], )? .is_truthy()? @@ -103,7 +100,7 @@ fn sql_lint_preparation<'py>( return Ok(None); } let codes: Vec = call_with( - &native("selected_sql_rule_codes")?, + &native(NativeAttr::SelectedSqlRuleCodes)?, &[ ( "select", @@ -146,7 +143,7 @@ fn sql_lint_preparation<'py>( sources.push(source?.getattr("source_entry")?); } let relation_keys: Bound<'py, PyAny> = call_with( - &native("declared_relation_keys")?, + &native(NativeAttr::DeclaredRelationKeys)?, &[ ("model_rows", PyList::new(py, model_rows)?.into_any()), ("seeds", PyList::new(py, seeds)?.into_any()), @@ -255,7 +252,7 @@ impl EarlySqlLint { } let py: Python<'_> = inputs.py(); let preparation: Bound<'_, PyAny> = call_with( - &imported(py, RULE_THREADS_MODULE, "prepare_sql_rules")?, + ®istry::native(py, NativeAttr::PrepareSqlRules)?, &[ ("inputs", inputs.clone()), ("executor", slf.executor.bind(py).clone()), @@ -268,7 +265,7 @@ impl EarlySqlLint { declaration_scope } else { call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlExpansionReuse")?, + ®istry::class(py, RecordId::SqlExpansionReuse)?, &[ ("discovered_inputs", inputs.getattr("discovered_inputs")?), ("declaration_scope", declaration_scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs index 2fd567c7ae..3f1681e55a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs @@ -1,16 +1,14 @@ //! Rule findings converted between the Python `Finding` model and native rows and payloads. +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use serde_json::Value; use sqlbuild_rules::models::Fault; -const MODELS_MODULE: &str = "sqlbuild._native"; -const FINDING_CLASS: &str = "Finding"; const PATHLIB_MODULE: &str = "pathlib"; const PATH_CLASS: &str = "Path"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const RULES_ERROR: &str = "RulesError"; const INVALID_FINDING: &str = "native rules engine returned an invalid finding"; /// The Python `Finding` and `Path` classes, imported once per conversion batch. @@ -22,7 +20,7 @@ pub(crate) struct FindingClasses<'py> { impl<'py> FindingClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - finding: py.import(MODELS_MODULE)?.getattr(FINDING_CLASS)?, + finding: registry::class(py, RecordId::Finding)?, path: py.import(PATHLIB_MODULE)?.getattr(PATH_CLASS)?, }) } @@ -98,7 +96,7 @@ pub(crate) fn finding_fault(finding: &Bound<'_, PyAny>) -> PyResult { /// The `RulesError` a malformed native or custom-rule finding raises. pub(crate) fn invalid_finding(py: Python<'_>) -> PyErr { let raised = || -> PyResult { - let class: Bound<'_, PyAny> = py.import(ERRORS_MODULE)?.getattr(RULES_ERROR)?; + let class: Bound<'_, PyAny> = registry::error(py, ErrorId::Rules)?; Ok(PyErr::from_value(class.call1((INVALID_FINDING,))?)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs index 43c7b97d6e..519b5f4f15 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs @@ -4,9 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const LINT_MODELS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Index a compilation by model key and diagnostic owner without changing its fact views. #[pyfunction] @@ -54,7 +54,7 @@ pub(crate) fn compile_facts<'py>( counts.set_item(identity, count + 1)?; } call_with( - &imported(py, LINT_MODELS, "CompileFacts")?, + ®istry::class(py, RecordId::CompileFacts)?, &[ ("models", models.into_any()), ("model_paths", model_paths.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs index 261eb0bf41..0c5cd5437c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs @@ -11,18 +11,15 @@ use sqlbuild_rules::fix_verification::main::refusal::refusal; use sqlbuild_rules::fix_verification::main::with_edits::with_edits; use sqlbuild_rules::fix_verification::models::{FixEdit, FixEligibility}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::SAFE_RULE_FIX_CODES; use crate::bindings::_helpers::rules::fix_facts::compile_facts; use crate::bindings::_helpers::rules::fix_verdict::fix_verdict; -const NATIVE: &str = "sqlbuild._native"; -const MODELS: &str = "sqlbuild._native"; -const ADAPTER: &str = "sqlbuild.adapter.contract.classes.base_adapter"; -const COMPILE_ERRORS: &str = "sqlbuild._native"; -const LINT_ERRORS: &str = "sqlbuild._native"; const VERIFICATION_FAILURE: &str = "Compilation could not prove unchanged output columns, types, dependencies and lineage"; const APPLIED_REASON: &str = "Meaning-preserving rewrite; compiler invariants verified"; @@ -77,9 +74,9 @@ pub(crate) fn plan_rule_fixes<'py>( } let adapter = args.value("adapter")?; let inputs = args.value("discovered_inputs")?; - if !adapter.is_instance(&imported(py, ADAPTER, "BaseAdapter")?)? || inputs.is_none() { + if !adapter.is_instance(&host::entry(py, AdapterHost::BaseAdapter)?)? || inputs.is_none() { return Err(PyErr::from_value( - imported(py, LINT_ERRORS, "ProjectCompileError")? + registry::error(py, ErrorId::ProjectCompile)? .call1(("format --fix requires discovered compiler inputs and an adapter",))?, )); } @@ -156,7 +153,7 @@ impl<'py> FixSession<'py> { ], )?; let lint = call_with( - &imported(py, NATIVE, "run_lint")?, + ®istry::native(py, NativeAttr::RunLint)?, &[ ("project_dir", self.project_dir.clone()), ("config", config), @@ -346,7 +343,7 @@ impl<'py> FixSession<'py> { return Ok(baseline.clone()); } let baseline = call_with( - &imported(self.inputs.py(), NATIVE, "build_compiled_project")?, + ®istry::native(self.inputs.py(), NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", self.inputs.clone()), ("adapter", self.adapter.clone()), @@ -361,17 +358,15 @@ impl<'py> FixSession<'py> { let baseline = match self.baseline() { Ok(value) => value, Err(error) - if error.value(py).is_instance(&imported( - py, - COMPILE_ERRORS, - "CompileInputError", - )?)? => + if error + .value(py) + .is_instance(®istry::error(py, ErrorId::CompileInput)?)? => { return Ok(PyDict::new(py).into_any()); } Err(error) => return Err(error), }; - imported(py, NATIVE, "compiled_relation_keys")?.call1((baseline,)) + registry::native(py, NativeAttr::CompiledRelationKeys)?.call1((baseline,)) } fn verdict(&mut self, files: &Bound<'py, PyDict>) -> PyResult>> { @@ -390,7 +385,7 @@ impl<'py> FixSession<'py> { self.baseline_facts = Some(compile_facts(py, &baseline, &self.project_dir)?); } let compiled = call_with( - &imported(py, NATIVE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", updated_inputs(&self.inputs, files)?), ("adapter", self.adapter.clone()), @@ -584,7 +579,7 @@ fn report<'py>( ) -> PyResult> { let py = source.py(); call_with( - &imported(py, MODELS, "RuleFixResult")?, + ®istry::class(py, RecordId::RuleFixResult)?, &[ ("file_path", source.getattr("file_path")?), ("code", source.getattr("code")?), @@ -614,7 +609,7 @@ fn violation<'py>( ) -> PyResult> { let py = path.py(); call_with( - &imported(py, MODELS, "LintViolation")?, + ®istry::class(py, RecordId::LintViolation)?, &[ ("file_path", path.clone()), ("line", 1_i64.into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs index 4c88ccdba1..3f66d44e87 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs @@ -8,9 +8,9 @@ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use sqlbuild_rules::fix_verification::main::compare::compare; use sqlbuild_rules::fix_verification::models::{DiagnosticComparison, ModelComparison}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const LINT_MODELS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Compare compile facts using native decisions and the existing Python boundary models. #[pyfunction] @@ -70,7 +70,7 @@ pub(crate) fn fix_verdict<'py>( failing.set_item(owners.get_item(owner)?, reason)?; } call_with( - &imported(py, LINT_MODELS, "FixVerdict")?, + ®istry::class(py, RecordId::FixVerdict)?, &[ ("failing", failing.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs index 9fde98d0f3..6ab29ec336 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs @@ -8,15 +8,13 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_attachment::sql_expansion_context::build_sql_expansion_context; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RESOLVE_ADAPTER_MODULE: &str = "sqlbuild.adapter.discovery.main.resolve_adapter"; -const ADAPTER_EXCEPTIONS_MODULE: &str = "sqlbuild.adapter.contract.exceptions"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const LINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const COMPILE_FIRST_MESSAGE: &str = "compiler-integrated Rules check the SQL your project \ actually produces, so the project must compile first"; @@ -53,7 +51,7 @@ fn expansion_context<'py>( if discovered.is_none() { let no: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); discovered = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("sql_analysis_enabled_override", no.clone()), @@ -64,14 +62,14 @@ fn expansion_context<'py>( let mut renderer: Bound<'py, PyAny> = arguments.value("value_renderer")?; if !renderer.is_truthy()? { let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), ], )?; renderer = call_with( - &imported(py, RESOLVE_ADAPTER_MODULE, "resolve_adapter")?, + &host::entry(py, AdapterHost::ResolveAdapter)?, &[ ("adapter_name", adapter_name), ("project_dir", project_dir.clone()), @@ -99,26 +97,15 @@ fn expansion_context<'py>( /// Adapter, compile and discovery errors as `ProjectCompileError`; any other error unchanged. fn compile_first_error(py: Python<'_>, error: PyErr) -> PyResult { let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); - let reported: bool = value.is_instance(&imported( - py, - ADAPTER_EXCEPTIONS_MODULE, - "AdapterUserError", - )?)? || value.is_instance(&imported( - py, - COMPILE_EXCEPTIONS_MODULE, - "CompileInputError", - )?)? || value.is_instance(&imported( - py, - DISCOVERY_EXCEPTIONS_MODULE, - "DiscoveryError", - )?)?; + let reported: bool = value.is_instance(&host::entry(py, AdapterHost::AdapterUserError)?)? + || value.is_instance(®istry::error(py, ErrorId::CompileInput)?)? + || value.is_instance(®istry::error(py, ErrorId::Discovery)?)?; if !reported { return Ok(error); } let message: String = format!("{COMPILE_FIRST_MESSAGE}: {}", value.str()?); - let raised: PyErr = PyErr::from_value( - imported(py, LINT_EXCEPTIONS_MODULE, "ProjectCompileError")?.call1((message,))?, - ); + let raised: PyErr = + PyErr::from_value(registry::error(py, ErrorId::ProjectCompile)?.call1((message,))?); raised.set_cause(py, Some(error)); Ok(raised) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs index 06c6b34fd9..0d34892772 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs @@ -4,12 +4,9 @@ use pyo3::prelude::{Bound, IntoPyObject, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::PyDict; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE: &str = "sqlbuild._native"; -const MODELS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Classify a lint path, retaining the caller's supplied relative path when available. #[pyfunction] @@ -27,17 +24,17 @@ pub(crate) fn lint_file_role<'py>( .call_method1("is_relative_to", (project_dir,))? .is_truthy()? { - return imported(py, MODELS, "LintFileRole")?.call0(); + return registry::class(py, RecordId::LintFileRole)?.call0(); } file_path.call_method1("relative_to", (project_dir,))? }; call_with( - &imported(py, MODELS, "LintFileRole")?, + ®istry::class(py, RecordId::LintFileRole)?, &[ ( "declaration_kind", call_with( - &imported(py, NATIVE, "named_declaration_role_kind")?, + ®istry::native(py, NativeAttr::NamedDeclarationRoleKind)?, &[("relative_path", relative.clone())], )?, ), @@ -75,7 +72,7 @@ pub(crate) fn lint_body_ranges<'py>( && (kind.eq("audit")? || kind.eq("singular_audit")?) { let ranges = call_with( - &imported(py, NATIVE, "measurement_body_ranges")?, + ®istry::native(py, NativeAttr::MeasurementBodyRanges)?, &[("contents", args.value("contents")?)], )?; if ranges.is_truthy()? { @@ -83,7 +80,7 @@ pub(crate) fn lint_body_ranges<'py>( } } call_with( - &imported(py, NATIVE, "sql_body_ranges")?, + ®istry::native(py, NativeAttr::SqlBodyRanges)?, &[ ("contents", args.value("contents")?), ("headers", args.value("headers")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs index 4adab35a94..d571ba353b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs @@ -7,18 +7,13 @@ use sqlbuild_rules::lint_text::main::dependency_sites::dependency_sites; use sqlbuild_rules::lint_text::models::InterpolationSite; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_headers::{lint_body_ranges, lint_file_role}; use crate::bindings::_helpers::rules::lint_stop::check_stop; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE: &str = "sqlbuild._native"; -const MODELS: &str = "sqlbuild._native"; -const COMPILE_ERRORS: &str = "sqlbuild._native"; -const LINT_ERRORS: &str = "sqlbuild._native"; - /// Expand a single body and finish its native SQL preparation. #[pyfunction] #[pyo3(signature = (**options))] @@ -28,7 +23,7 @@ pub(crate) fn prepare_lint_body<'py>( ) -> PyResult> { let pending = expand_lint_body(py, options)?; let prepared = - imported(py, NATIVE, "prepare_lint_sql")?.call1((pending.getattr("request")?,))?; + registry::native(py, NativeAttr::PrepareLintSql)?.call1((pending.getattr("request")?,))?; finish_body(&pending, &prepared) } @@ -73,16 +68,16 @@ pub(crate) fn expand_lint_body<'py>( let kind = role.getattr("declaration_kind")?; let in_project = role.getattr("in_project")?.is_truthy()?; let neutralizer = if in_project && kind.eq("audit")? { - Some("neutralize_lint_audit_parameters") + Some(NativeAttr::NeutralizeLintAuditParameters) } else if role.getattr("in_hook_directory")?.is_truthy()? || (in_project && kind.eq("sql_hook")?) { - Some("neutralize_lint_context_interpolation") + Some(NativeAttr::NeutralizeLintContextInterpolation) } else { None }; if let Some(neutralizer) = neutralizer { - let neutralized = imported(py, NATIVE, neutralizer)?.call1((&body,))?; + let neutralized = registry::native(py, neutralizer)?.call1((&body,))?; input = neutralized.get_item(0)?; sites = site_objects(&neutralized.get_item(1)?)?; } @@ -106,7 +101,7 @@ pub(crate) fn expand_lint_body<'py>( passes = PyTuple::empty(py).into_any(); } else { let value = call_with( - &imported(py, NATIVE, "expand_sql_with_spans")?, + ®istry::native(py, NativeAttr::ExpandSqlWithSpans)?, &[ ("sql", input.clone()), ("file_path", args.value("file_path")?), @@ -136,7 +131,7 @@ pub(crate) fn expand_lint_body<'py>( PyTuple::empty(py).into_any() }; call_with( - &imported(py, MODELS, "PendingLintBody")?, + ®istry::class(py, RecordId::PendingLintBody)?, &[ ("file_path", args.value("file_path")?), ("body_start", start.into_pyobject(py)?.into_any()), @@ -167,7 +162,7 @@ fn expansion_error(py: Python<'_>, args: &KeywordArguments<'_>, error: PyErr) -> let wrapped = (|| -> PyResult> { if !error .value(py) - .is_instance(&imported(py, COMPILE_ERRORS, "CompileInputError")?)? + .is_instance(®istry::error(py, ErrorId::CompileInput)?)? { return Ok(None); } @@ -177,7 +172,7 @@ fn expansion_error(py: Python<'_>, args: &KeywordArguments<'_>, error: PyErr) -> error.value(py).str()? ); Ok(Some(PyErr::from_value( - imported(py, LINT_ERRORS, "ProjectCompileError")?.call1((message,))?, + registry::error(py, ErrorId::ProjectCompile)?.call1((message,))?, ))) })(); match wrapped { @@ -191,7 +186,7 @@ fn expansion_error(py: Python<'_>, args: &KeywordArguments<'_>, error: PyErr) -> } fn site_objects<'py>(sites: &Bound<'py, PyAny>) -> PyResult> { - let class = imported(sites.py(), MODELS, "InterpolationSite")?; + let class = registry::class(sites.py(), RecordId::InterpolationSite)?; let values: Vec> = sites .try_iter()? .map(|site| class.call1(site?.cast_into::()?)) @@ -205,7 +200,7 @@ fn sentinel_spans<'py>(sites: &Bound<'py, PyAny>) -> PyResult> for site in sites.try_iter()? { let site = site?; spans.push(call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?, + ®istry::class(py, RecordId::ExpansionSpan)?, &[ ("source_start", site.getattr("original_start")?), ("source_end", site.getattr("original_end")?), @@ -224,7 +219,7 @@ fn finish_body<'py>( let py = pending.py(); if prepared.is_none() { return Err(PyErr::from_value( - imported(py, LINT_ERRORS, "NativeLintError")?.call1((format!( + registry::error(py, ErrorId::NativeLint)?.call1((format!( "{} could not be prepared for native SQL lint", pending.getattr("file_path")?.str()? ),))?, @@ -286,7 +281,7 @@ fn finish_body<'py>( ] { values.set_item(name, pending.getattr(name)?)?; } - imported(py, MODELS, "LintBody")?.call((), Some(&values)) + registry::class(py, RecordId::LintBody)?.call((), Some(&values)) } fn prepare_batch<'py>( @@ -300,15 +295,15 @@ fn prepare_batch<'py>( .iter() .map(|body| body.getattr("request")) .collect::>()?; - let results = - imported(py, NATIVE, "prepare_lint_sql_batch")?.call1((PyList::new(py, requests)?,))?; + let results = registry::native(py, NativeAttr::PrepareLintSqlBatch)? + .call1((PyList::new(py, requests)?,))?; let mut prepared: Vec> = Vec::new(); for (body, result) in pending.iter().zip(results.try_iter()?) { let result = result?; prepared.push(if result.get_item(0)?.is_truthy()? { result.get_item(1)? } else { - imported(py, NATIVE, "prepare_lint_sql")?.call1((body.getattr("request")?,))? + registry::native(py, NativeAttr::PrepareLintSql)?.call1((body.getattr("request")?,))? }); } if prepared.len() != pending.len() || results.len()? != pending.len() { @@ -361,7 +356,7 @@ pub(crate) fn expand_file_bodies<'py>( py.None().into_bound(py) }; let external = call_with( - &imported(py, NATIVE, "external_identifiers_for_headers")?, + ®istry::native(py, NativeAttr::ExternalIdentifiersForHeaders)?, &[ ("contents", args.value("contents")?), ("headers", args.value("headers")?), @@ -434,14 +429,14 @@ pub(crate) fn expand_file_bodies<'py>( if let Err(error) = expansion { if !error .value(py) - .is_instance(&imported(py, LINT_ERRORS, "ProjectCompileError")?)? + .is_instance(®istry::error(py, ErrorId::ProjectCompile)?)? { return Err(error); } failure = error.value(py).clone().into_any(); } call_with( - &imported(py, MODELS, "ExpandedLintFile")?, + ®istry::class(py, RecordId::ExpandedLintFile)?, &[ ("file_path", path), ("bodies", PyTuple::new(py, bodies)?.into_any()), @@ -493,7 +488,7 @@ pub(crate) fn prepare_expanded_files<'py>( } } call_with( - &imported(py, MODELS, "PreparedLintFiles")?, + ®istry::class(py, RecordId::PreparedLintFiles)?, &[ ("bodies", PyTuple::new(py, bodies)?.into_any()), ("unexpandable", unexpandable.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs index cd59b9bc4a..3020f23ec0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs @@ -9,11 +9,11 @@ use sqlbuild_rules::lint_text::models::{ }; use crate::bindings::_helpers::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_TYPES_MODULE: &str = "sqlbuild._native"; const NATIVE_ENGINE: &str = "native"; fn spans(passes: &Bound<'_, PyAny>) -> PyResult>> { @@ -94,9 +94,9 @@ struct ViolationClasses<'py> { impl<'py> ViolationClasses<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: imported(py, LINT_MODELS_MODULE, "LintViolation")?, - edit: imported(py, LINT_MODELS_MODULE, "LintEdit")?, - severity: imported(py, LINT_TYPES_MODULE, "LintSeverity")?, + violation: registry::class(py, RecordId::LintViolation)?, + edit: registry::class(py, RecordId::LintEdit)?, + severity: registry::class(py, RecordId::LintSeverity)?, }) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs index 6c0a379e09..3f0801a4dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs @@ -9,9 +9,10 @@ use pyo3::sync::PyOnceLock; use pyo3::types::PyTuple; use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{context_variable, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::context_variable; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const LINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const STOP_VARIABLE: &str = "active_lint_stop"; static ACTIVE_STOP: PyOnceLock> = PyOnceLock::new(); @@ -26,7 +27,7 @@ pub(crate) fn check_stop(py: Python<'_>) -> PyResult<()> { let stop: Bound<'_, PyAny> = active_stop_variable(py)?.call_method0("get")?; if !stop.is_none() && stop.call_method0("is_set")?.is_truthy()? { return Err(PyErr::from_value( - imported(py, LINT_EXCEPTIONS_MODULE, "LintStoppedError")?.call0()?, + registry::error(py, ErrorId::LintStopped)?.call0()?, )); } Ok(()) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs index ccc75757da..6ae4847acc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs @@ -23,12 +23,11 @@ use sqlbuild_rules::lint_text::models::{ }; use crate::bindings::_helpers::boundary::panics::{compiler_guard, value_error}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; const NATIVE_ENGINE: &str = "native"; const FORMAT_UNSAFE_CODE: &str = "format-unsafe"; const FORMAT_UNSAFE_REMEDIATION: &str = "Rewrite the unsupported SQL shape or report a minimal formatter reproduction. No header, SQL body, or fixture changes were written to this file."; @@ -130,7 +129,7 @@ fn named_declaration_role_kind<'py>( let parts: Vec = relative_path.getattr("parts")?.extract()?; match role_kind(&parts) { Some(kind) => Ok(Some( - imported(relative_path.py(), SCOPE_TYPES_MODULE, "DeclarationKind")?.call1((kind,))?, + registry::class(relative_path.py(), RecordId::DeclarationKind)?.call1((kind,))?, )), None => Ok(None), } @@ -197,9 +196,9 @@ struct FindingClasses<'py> { impl<'py> FindingClasses<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: imported(py, LINT_MODELS_MODULE, "LintViolation")?, - edit: imported(py, LINT_MODELS_MODULE, "LintEdit")?, - severity: imported(py, LINT_TYPES_MODULE, "LintSeverity")?, + violation: registry::class(py, RecordId::LintViolation)?, + edit: registry::class(py, RecordId::LintEdit)?, + severity: registry::class(py, RecordId::LintSeverity)?, }) } @@ -292,9 +291,8 @@ fn format_lint_sql_bodies<'py>( for (index, text) in result.formatted { formatted.set_item(&files[index].0, text)?; } - let violation: Bound<'py, PyAny> = imported(py, LINT_MODELS_MODULE, "LintViolation")?; - let fault: Bound<'py, PyAny> = - imported(py, LINT_TYPES_MODULE, "LintSeverity")?.getattr("FAULT")?; + let violation: Bound<'py, PyAny> = registry::class(py, RecordId::LintViolation)?; + let fault: Bound<'py, PyAny> = registry::class(py, RecordId::LintSeverity)?.getattr("FAULT")?; let faults: Vec> = result .faults .into_iter() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs index 62a6f2b4dd..4fceef9822 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs @@ -17,11 +17,11 @@ use sqlbuild_rules::project_tree::main::project_tree_glob::project_tree_glob; use sqlbuild_rules::project_tree::main::tracked_text_path::tracked_text_path; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::rule_api::{usage_error_for, validate_project_relative}; const PATHLIB_MODULE: &str = "pathlib"; -const RULE_USAGE_ERROR: &str = "RuleUsageError"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// `pathlib.Path(value)`, raising exactly as `Path` does for an unsupported value. fn python_path<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -126,7 +126,7 @@ fn not_beneath(path: &Bound<'_, PyAny>, under: &Bound<'_, PyAny>) -> PyErr { .add(under_text) }); match message.and_then(|message| { - imported(py, NATIVE_MODULE, RULE_USAGE_ERROR).and_then(|class| class.call1((message,))) + registry::error(py, ErrorId::RuleUsage).and_then(|class| class.call1((message,))) }) { Ok(error) => PyErr::from_value(error), Err(error) => error, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs index 0c8e28ffef..b922e1e5ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs @@ -32,15 +32,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::compiled_project::records::declaration_rows; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::findings::{FindingClasses, finding_fault}; use crate::bindings::_helpers::rules::selection::rules_error; use crate::bindings::_helpers::rules::sql_test_objects::{scenario_fact, sql_test_fact}; use crate::bindings::_helpers::type_system::equality::types_equal; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const NATIVE_RULES_CACHE_FILE: &str = "native.json"; const NATIVE_RULES_MEMO_FILE: &str = "native-response.json"; @@ -116,7 +115,7 @@ fn built_request(options: &Request<'_, '_>) -> PyResult { "dialect": dialect.extract::()?, "config": config_value(&config)?, "scope_index": plain_value(&call_with( - &imported(py, NATIVE_MODULE, "scope_metadata_projection")?, + ®istry::native(py, NativeAttr::ScopeMetadataProjection)?, &[("index", project.getattr("scope_index")?)], )?)?, "defer_suppressions": true, @@ -164,7 +163,7 @@ fn built_request(options: &Request<'_, '_>) -> PyResult { dialect, }; let facts: Bound<'_, NativeCompiledProject> = - imported(py, NATIVE_MODULE, "compiled_project_facts")? + registry::native(py, NativeAttr::CompiledProjectFacts)? .call1((&project,))? .downcast_into()?; let mut names: Vec = Vec::new(); @@ -243,7 +242,7 @@ fn evaluated<'py>( .map(|fault| classes.finding_of_fault(fault, false)) .collect::>()?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NativeRulesEvaluation")?, + ®istry::class(py, RecordId::NativeRulesEvaluation)?, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs index 415d36751c..04c5f89a14 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs @@ -20,10 +20,9 @@ use sqlbuild_rules::project_tree::main::tracked_text_suffixes::tracked_text_suff use sqlbuild_rules::project_tree::models::ProjectTreeEntry; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULE_USAGE_ERROR: &str = "RuleUsageError"; -const PROJECT_PATH_CLASS: &str = "ProjectPath"; const BUILTINS_MODULE: &str = "builtins"; const DEFAULT_POSITION: i64 = 1; #[cfg(unix)] @@ -33,7 +32,7 @@ const EMBEDDED_NUL_MESSAGE: &str = "lstat: embedded null character in path"; /// `RuleUsageError(message)`. fn usage_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, NATIVE_MODULE, RULE_USAGE_ERROR).and_then(|class| class.call1((message,))) { + match registry::error(py, ErrorId::RuleUsage).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -46,7 +45,7 @@ pub(crate) fn usage_error_for(prefix: &str, value: &Bound<'_, PyAny>) -> PyErr { .str() .and_then(|text| PyString::new(py, prefix).as_any().add(text)); match message.and_then(|message| { - imported(py, NATIVE_MODULE, RULE_USAGE_ERROR).and_then(|class| class.call1((message,))) + registry::error(py, ErrorId::RuleUsage).and_then(|class| class.call1((message,))) }) { Ok(error) => PyErr::from_value(error), Err(error) => error, @@ -187,7 +186,7 @@ fn unreadable(py: Python<'_>, path: PathBuf, code: i32) -> PyResult { fn project_tree_paths<'py>(root: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = root.py(); let root: PathBuf = root.extract()?; - let class: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, PROJECT_PATH_CLASS)?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::ProjectPath)?; let mut paths: Vec<(Bound<'py, PyAny>, bool)> = project_tree_entries(&root) .into_iter() .map(|ProjectTreeEntry { relative, is_file }| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs index 9ef1718718..5146f908a7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs @@ -16,8 +16,12 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, within, + KeywordArguments, call_with, within, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::TYPE_PROOF_RULE_CODES; use crate::bindings::_helpers::rules::custom_evaluation::{ CustomRun, evaluate_custom_rules_cached, outcome, @@ -25,11 +29,6 @@ use crate::bindings::_helpers::rules::custom_evaluation::{ use crate::bindings::_helpers::rules::custom_hosts::HostCancel; use crate::bindings::_helpers::rules::custom_rule_evidence::verify_custom_rules; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_MODELS_MODULE: &str = "sqlbuild._native"; -const CUSTOM_PAYLOADS_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.custom_payloads"; const CUSTOM_THREAD: &str = "sqlbuild-custom-rules"; const GENERIC_DIALECT: &str = "generic"; const PROJECT_RULES_OPTIONS: (&[&str], &[&str]) = ( @@ -141,11 +140,11 @@ fn evaluate_project_rules<'py>( let dialect: String = dialect_argument(&arguments)?; let verify_determinism: bool = arguments.flag("verify_determinism")?; let effective: Bound<'py, PyAny> = call_with( - &native(py, "resolve_rule_ignore_selectors")?, + &native(py, NativeAttr::ResolveRuleIgnoreSelectors)?, &[("config", config), ("project", project.clone())], )?; let catalogue: Bound<'py, PyAny> = call_with( - &native(py, "build_rule_catalogue")?, + &native(py, NativeAttr::BuildRuleCatalogue)?, &[ ("config", effective.clone()), ("project_dir", project_dir.clone()), @@ -157,7 +156,7 @@ fn evaluate_project_rules<'py>( } if custom { let rules: Bound<'py, PyAny> = call_with( - &native(py, "select_catalogue_rules")?, + &native(py, NativeAttr::SelectCatalogueRules)?, &[ ("catalogue", catalogue.clone()), ("config", effective.clone()), @@ -252,7 +251,7 @@ pub(crate) fn evaluate_rules<'py>( let graph: Bound<'py, PyAny> = arguments.value("graph")?; let dialect: Bound<'py, PyAny> = arguments.value("dialect")?; let plan: Bound<'py, PyAny> = call_with( - &native(py, "plan_rule_run")?, + &native(py, NativeAttr::PlanRuleRun)?, &[ ("graph", graph.clone()), ("config", arguments.value("config")?), @@ -307,7 +306,7 @@ pub(crate) fn evaluate_rules<'py>( }) })?; let sql_result: PyResult> = call_with( - &native(py, "run_planned_sql_rules")?, + &native(py, NativeAttr::RunPlannedSqlRules)?, &[ ("plan", plan.clone()), ("graph", graph), @@ -322,7 +321,7 @@ pub(crate) fn evaluate_rules<'py>( let sql_result: Bound<'py, PyAny> = sql_result?; let result: Bound<'py, PyAny> = result?; call_with( - &native(py, "finish_rule_run")?, + &native(py, NativeAttr::FinishRuleRun)?, &[ ("plan", plan), ("sql_result", sql_result), @@ -342,7 +341,7 @@ pub(crate) fn prepare_sql_rules<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let preparation: Bound<'py, PyAny> = call_with( - &native(py, "sql_lint_preparation")?, + &native(py, NativeAttr::SqlLintPreparation)?, &[("inputs", inputs.clone()), ("dialect", dialect.clone())], )?; if preparation.is_none() { @@ -351,7 +350,7 @@ pub(crate) fn prepare_sql_rules<'py>( let (project_dir, codes, config): (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = preparation.extract()?; let expansions: Bound<'py, PyAny> = - native(py, "early_lint_expansions")?.call1((inputs.getattr("model_inputs")?,))?; + native(py, NativeAttr::EarlyLintExpansions)?.call1((inputs.getattr("model_inputs")?,))?; let work: Bound<'py, PyDict> = PyDict::new(py); work.set_item("project_dir", &project_dir)?; work.set_item("config", config)?; @@ -368,7 +367,7 @@ pub(crate) fn prepare_sql_rules<'py>( Some(&work), )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PreparedSqlLint")?, + ®istry::class(py, RecordId::PreparedSqlLint)?, &[ ("codes", codes), ("project_dir", project_dir), @@ -389,10 +388,10 @@ fn prepare_early_sql_lint<'py>( let project_dir: Bound<'py, PyAny> = arguments.value("project_dir")?; let discovered_inputs: Bound<'py, PyAny> = arguments.value("discovered_inputs")?; let scope: Bound<'py, PyAny> = - native(py, "lint_stop_scope")?.call1((arguments.value("stop")?,))?; + native(py, NativeAttr::LintStopScopeFunction)?.call1((arguments.value("stop")?,))?; within(&scope, |_| { let context: Bound<'py, PyAny> = call_with( - &native(py, "build_lint_expansion_context")?, + &native(py, NativeAttr::BuildLintExpansionContext)?, &[ ("project_dir", project_dir.clone()), ("discovered_inputs", discovered_inputs.clone()), @@ -403,7 +402,7 @@ fn prepare_early_sql_lint<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &native(py, "run_lint")?, + &native(py, NativeAttr::RunLint)?, &[ ("project_dir", project_dir.clone()), ("config", arguments.value("config")?), @@ -416,7 +415,7 @@ fn prepare_early_sql_lint<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PreparedSqlLintResult")?, + ®istry::class(py, RecordId::PreparedSqlLintResult)?, &[("result", result), ("context", context)], ) }) @@ -443,8 +442,8 @@ fn evaluate_native<'py>( (CustomSource::Deferred, Err(_)) => None, }; let (_, request): (Bound<'py, PyAny>, Bound<'py, PyAny>) = prepared?; - let evaluated: PyResult> = - native(py, "evaluate_rules_request").and_then(|evaluate| evaluate.call1((request,))); + let evaluated: PyResult> = native(py, NativeAttr::EvaluateRulesRequest) + .and_then(|evaluate| evaluate.call1((request,))); cancel_on_interrupt(py, &evaluated, &run.cancel); let custom: PyResult> = match custom { Some(handle) => joined(py, handle, &run.cancel), @@ -463,7 +462,7 @@ fn evaluate_native<'py>( .into_any(); if !defer_suppressions { findings = call_with( - &native(py, "finalize_rule_findings_rows")?, + &native(py, NativeAttr::FinalizeRuleFindingsRows)?, &[ ("config", run.config.clone()), ("project_dir", run.project_dir.clone()), @@ -478,7 +477,7 @@ fn evaluate_native<'py>( let custom_hits: i64 = custom.getattr("cache_hits")?.extract()?; let custom_misses: i64 = custom.getattr("cache_misses")?.extract()?; call_with( - &imported(py, RULES_MODELS_MODULE, "RulesResult")?, + ®istry::class(py, RecordId::RulesResult)?, &[ ("findings", findings), ( @@ -521,7 +520,7 @@ fn native_request<'py>( let payloads: Bound<'py, PyAny> = match custom_payloads { Some(payloads) => payloads, None => call_with( - &imported(py, CUSTOM_PAYLOADS_MODULE, "custom_rule_payloads")?, + &host::entry(py, UserCodeHost::CustomRulePayloads)?, &[ ("catalogue", run.catalogue.clone()), ("project_dir", run.project_dir.clone()), @@ -532,7 +531,7 @@ fn native_request<'py>( for code in TYPE_PROOF_RULE_CODES { include_type_proof = include_type_proof || call_with( - &native(py, "rule_selected")?, + &native(py, NativeAttr::RuleSelected)?, &[ ("config", run.config.clone()), ("code", pyo3::types::PyString::new(py, code).into_any()), @@ -541,7 +540,7 @@ fn native_request<'py>( .is_truthy()?; } let request: Bound<'py, PyAny> = call_with( - &native(py, "build_rules_request")?, + &native(py, NativeAttr::BuildRulesRequest)?, &[ ("project", run.project.clone()), ("config", run.config.clone()), @@ -575,7 +574,7 @@ fn start_custom_rules( let selected: Bound<'_, PyFrozenSet> = PyFrozenSet::new( py, call_with( - &native(py, "selected_rule_codes")?, + &native(py, NativeAttr::SelectedRuleCodes)?, &[ ("config", run.config.clone()), ("custom_payloads", payloads.clone()), @@ -683,8 +682,8 @@ fn cancel_on_interrupt(py: Python<'_>, result: &PyResult>, canc } } -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, NATIVE_MODULE, name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs index 9efa1652f1..a46162f049 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs @@ -6,6 +6,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::catalogue::{ references_custom_rules, resolved_ignore_selectors, rule_catalogue, select_rules, verify_custom_rules, @@ -13,10 +17,6 @@ use crate::bindings::_helpers::rules::catalogue::{ use crate::bindings::_helpers::rules::constants::TYPE_PROOF_RULE_CODES; use crate::bindings::_helpers::rules::rule_run::freed_memory_released; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CUSTOM_PAYLOADS_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.custom_payloads"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const DATACLASSES_MODULE: &str = "dataclasses"; const PATHLIB_MODULE: &str = "pathlib"; @@ -38,7 +38,7 @@ fn plan_rule_run<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult = PyTuple::new(py, &catalogue)?; let custom_payloads: Bound<'py, PyAny> = call_with( - &imported(py, CUSTOM_PAYLOADS_MODULE, "custom_rule_payloads")?, + &host::entry(py, UserCodeHost::CustomRulePayloads)?, &[ ("catalogue", catalogue_tuple.clone().into_any()), ("project_dir", project_dir.clone()), @@ -100,7 +100,7 @@ fn plan_rule_run<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult( } } call_with( - &imported(py, NATIVE_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project.clone()), ("models", PyTuple::new(py, models)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs index 02667e3faf..ba678efecb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs @@ -12,12 +12,11 @@ use sqlbuild_rules::rule_results::models::RuleFinding; use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::rule_finding; use crate::bindings::_helpers::rules::rows::finalize_rule_findings_rows; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const CTYPES_MODULE: &str = "ctypes"; @@ -27,7 +26,7 @@ fn integer(object: &Bound<'_, PyAny>, field: &str) -> PyResult { fn python_finding<'py>(py: Python<'py>, finding: RuleFinding) -> PyResult> { call_with( - &imported(py, MODELS_MODULE, "Finding")?, + ®istry::class(py, RecordId::Finding)?, &[ ("code", finding.code.into_bound_py_any(py)?), ( @@ -96,7 +95,7 @@ fn finish_rule_run<'py>( (integer(result, field)? + integer(sql_result, field)?).into_bound_py_any(py) }; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "RulesRunResult")?, + ®istry::class(py, RecordId::RulesRunResult)?, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs index b6c0de35f1..87c7f7e583 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs @@ -13,34 +13,24 @@ use sqlbuild_rules::configuration::main::load_rules_config::load_rules_config as use sqlbuild_rules::models::RulesConfig; use crate::bindings::_helpers::boundary::panics::compiler_guard; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -/// The rules configuration models that stay Python for the custom-rule API. -const PYTHON_CLASSES: &[&str] = &["RulesConfig", "LayoutConfig"]; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; struct Models<'py> { - module: Bound<'py, PyModule>, - native: Bound<'py, PyModule>, + py: Python<'py>, } impl<'py> Models<'py> { fn build( &self, - class: &str, + class: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - let arguments: Bound<'py, PyDict> = PyDict::new(self.module.py()); + let arguments: Bound<'py, PyDict> = PyDict::new(self.py); for (name, value) in fields { arguments.set_item(name, value)?; } - let module: &Bound<'py, PyModule> = if PYTHON_CLASSES.contains(&class) { - &self.module - } else { - &self.native - }; - module.getattr(class)?.call((), Some(&arguments)) + registry::class(self.py, class)?.call((), Some(&arguments)) } } @@ -128,13 +118,10 @@ fn tables<'py, T>( } fn config_object<'py>(py: Python<'py>, config: &RulesConfig) -> PyResult> { - let models = Models { - module: py.import(MODELS_MODULE)?, - native: py.import(NATIVE_MODULE)?, - }; + let models = Models { py }; let threshold_overrides = tables(py, &config.threshold_overrides, |item| { models.build( - "ThresholdOverride", + RecordId::ThresholdOverride, &[ ("paths", strings(py, &item.paths)?), ("thresholds", integers(py, &item.thresholds)?), @@ -144,7 +131,7 @@ fn config_object<'py>(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult, class: &str, message: String, help: Option) -> PyErr { +fn rules_error(py: Python<'_>, class: ErrorId, message: String, help: Option) -> PyErr { let raised = || -> PyResult { - let error_class: Bound<'_, PyAny> = py.import(ERRORS_MODULE)?.getattr(class)?; + let error_class: Bound<'_, PyAny> = registry::error(py, class)?; let error: Bound<'_, PyAny> = match help { Some(help) => { let arguments: Bound<'_, PyDict> = PyDict::new(py); @@ -258,11 +245,11 @@ pub(crate) fn load_rules_config(py: Python<'_>, project_dir: PathBuf) -> PyResul .canonicalize() .or_else(|_| std::path::absolute(&project_dir))?; let config: RulesConfig = compiler_guard(|| Ok(load_config(&project_dir)))? - .map_err(|message| rules_error(py, "RulesError", message, None))?; + .map_err(|message| rules_error(py, ErrorId::Rules, message, None))?; if let Some(error) = forbidden_exceptions(&config) { return Err(rules_error( py, - "RulesConfigError", + ErrorId::RulesConfig, error.message, Some(error.help), )); @@ -276,9 +263,7 @@ pub(crate) fn load_rules_config(py: Python<'_>, project_dir: PathBuf) -> PyResul fn valid_rules_config(py: Python<'_>, project_dir: PathBuf) -> PyResult>> { match load_rules_config(py, project_dir) { Ok(config) => Ok(Some(config)), - Err(error) if error.is_instance(py, &py.import(ERRORS_MODULE)?.getattr("RulesError")?) => { - Ok(None) - } + Err(error) if error.is_instance(py, ®istry::error(py, ErrorId::Rules)?) => Ok(None), Err(error) => Err(error), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs index 74cc787ae7..a041f3769e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs @@ -14,15 +14,13 @@ use sqlbuild_core::format_command::main::collect_lint_files::collect_lint_files; use sqlbuild_rules::lint_text::main::first_header_only::first_header_only; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_expansion::build_lint_expansion_context; use crate::bindings::_helpers::rules::lint_stop::check_stop; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXPANSION_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; - fn resolved(path: &Path) -> PathBuf { fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } @@ -104,7 +102,7 @@ fn scan_file<'py>( file_path.call_method1("relative_to", (&run.project_dir,))?; let relative_parts: Vec = relative_path.getattr("parts")?.extract()?; let headers: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "scan_headers")?, + ®istry::native(py, NativeAttr::ScanHeaders)?, &[ ("contents", contents.clone()), ( @@ -118,7 +116,7 @@ fn scan_file<'py>( let header_violations: Bound<'py, PyAny> = if run.config.getattr("header_rules_enabled")?.is_truthy()? { call_with( - &imported(py, NATIVE_MODULE, "lint_native_headers")?, + ®istry::native(py, NativeAttr::LintNativeHeaders)?, &[ ("contents", contents.clone()), ("file_path", file_path.clone()), @@ -133,7 +131,7 @@ fn scan_file<'py>( return Ok((header_violations, None)); } let expanded: Bound<'py, PyAny> = call_with( - &imported(py, EXPANSION_MODULE, "expand_file_bodies")?, + ®istry::native(py, NativeAttr::ExpandFileBodies)?, &[ ("file_path", file_path.clone()), ("contents", contents.clone()), @@ -230,7 +228,7 @@ fn lint_files<'py>( } let skip: Bound<'py, PyAny> = PyBool::new(py, run.skip_unexpandable).to_owned().into_any(); let prepared: Bound<'py, PyAny> = call_with( - &imported(py, EXPANSION_MODULE, "prepare_expanded_files")?, + ®istry::native(py, NativeAttr::PrepareExpandedFiles)?, &[ ( "expanded_files", @@ -247,7 +245,7 @@ fn lint_files<'py>( if bodies.is_truthy()? && run.config.getattr("native_enabled")?.is_truthy()? { check_stop(py)?; let body_list: Vec> = bodies.extract()?; - let found: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "lint_native_sql_bodies")? + let found: Bound<'py, PyAny> = registry::native(py, NativeAttr::LintNativeSqlBodies)? .call1((body_list, files, &run.config))?; for violation in found.try_iter()? { violations.push(violation?); @@ -303,11 +301,11 @@ fn run_lint<'py>( let context: Bound<'py, PyAny> = expansion_context(&run, &arguments)?; let (violations, unexpandable) = lint_files(&run, &files, &context)?; let allow_suppressions: Bound<'py, PyAny> = run.config.getattr("allow_suppressions")?; - let retained: Vec> = imported(py, NATIVE_MODULE, "apply_lint_suppressions")? + let retained: Vec> = registry::native(py, NativeAttr::ApplyLintSuppressions)? .call1((violations, &files, allow_suppressions))? .extract()?; call_with( - &imported(py, LINT_MODELS_MODULE, "LintRunResult")?, + ®istry::class(py, RecordId::LintRunResult)?, &[ ("files_checked", files.len().into_pyobject(py)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs index 1e98140149..9243c00e77 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs @@ -9,9 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_rules::rules::main::selected_codes::selected_codes_json; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::RULES_NATIVE_API_VERSION; -const RULES_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const JSON_MODULE: &str = "json"; const BUILTINS_MODULE: &str = "builtins"; @@ -81,10 +82,7 @@ fn rule_selected(config: &Bound<'_, PyAny>, code: &str) -> PyResult { /// `RulesError(message)`, caused by `cause` when given. pub(crate) fn rules_error(py: Python<'_>, message: &str, cause: Option) -> PyErr { let raised = || -> PyResult { - let error: Bound<'_, PyAny> = py - .import(RULES_EXCEPTIONS_MODULE)? - .getattr("RulesError")? - .call1((message,))?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::Rules)?.call1((message,))?; Ok(PyErr::from_value(error)) }; let error: PyErr = raised().unwrap_or_else(|error| error); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs index d8cf5699fb..6d9ca62c97 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs @@ -12,14 +12,11 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::SAFE_RULE_FIX_CODES; use crate::bindings::_helpers::rules::early_sql_lint::{sql_lint_config, sql_rule_cache_path}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const JSON_MODULE: &str = "json"; const BUILTINS_MODULE: &str = "builtins"; @@ -28,8 +25,8 @@ const SQL_RULE_SUPPRESSION_CODE: &str = "SQBRSQL000"; const DEFAULT_REMEDIATION: &str = "Update the authored SQL to satisfy this rule."; /// `native.name`, looked up at call time. -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, NATIVE_MODULE, name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } /// The SQL rule codes among `rules`, in order. @@ -79,7 +76,7 @@ fn run_planned_sql_rules<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult< &codes, &effective_config.getattr("thresholds")?, ( - native(py, "compiled_relation_keys")?.call1((graph.getattr("project")?,))?, + native(py, NativeAttr::CompiledRelationKeys)?.call1((graph.getattr("project")?,))?, effective_config.getattr("allow_exceptions")?, ), )?; @@ -112,7 +109,7 @@ fn run_planned_sql_rules<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult< evaluated_sql_rules(&run, (&lint_key, identities), project_files)?; let sql_ms: i64 = (started.elapsed().as_secs_f64() * 1000.0).round_ties_even() as i64; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlRulesEvaluation")?, + ®istry::class(py, RecordId::SqlRulesEvaluation)?, &[ ("findings", findings), ("cache_hits", hits.into_bound_py_any(py)?), @@ -124,7 +121,7 @@ fn run_planned_sql_rules<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult< fn lint_key(lint_config: &Bound<'_, PyAny>) -> PyResult { call_with( - &native(lint_config.py(), "sql_rule_lint_key")?, + &native(lint_config.py(), NativeAttr::SqlRuleLintKey)?, &[("config", lint_config.clone())], )? .extract() @@ -133,7 +130,7 @@ fn lint_key(lint_config: &Bound<'_, PyAny>) -> PyResult { fn collected_project_files<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = project_dir.py(); call_with( - &native(py, "collect_lint_project_files")?, + &native(py, NativeAttr::CollectLintProjectFiles)?, &[ ("project_dir", project_dir.clone()), ("selected_paths", py.None().into_bound(py)), @@ -195,7 +192,7 @@ fn model_rule_identities(run: &SqlRun<'_, '_>, lint_key: &str) -> PyResult = call_with( - &native(py, "sql_model_rule_keys")?, + &native(py, NativeAttr::SqlModelRuleKeys)?, &[ ("lint_key", PyString::new(py, lint_key).into_any()), ("codes", PyList::new(py, run.codes)?.into_any()), @@ -238,7 +235,7 @@ fn cached_findings<'py>( ) -> PyResult> { let py: Python<'py> = bucket.py(); Ok(call_with( - &native(py, "lookup_sql_rule_cache")?, + &native(py, NativeAttr::LookupSqlRuleCache)?, &[ ("entries", bucket.clone()), ("keys", keys.into_bound_py_any(py)?), @@ -296,7 +293,7 @@ fn evaluated_sql_rules<'py>( let cache_path: Bound<'py, PyAny> = sql_rule_cache_path(project_dir)?; let bucket: Bound<'py, PyAny> = if run.cache_enabled { call_with( - &native(py, "load_sql_rule_cache")?, + &native(py, NativeAttr::LoadSqlRuleCache)?, &[("path", cache_path.clone())], )? } else { @@ -345,7 +342,7 @@ fn evaluated_sql_rules<'py>( .call_method0("as_posix")? .extract()?; let key: Option = call_with( - &native(py, "sql_file_rule_key")?, + &native(py, NativeAttr::SqlFileRuleKey)?, &[ ("lint_key", PyString::new(py, lint_key).into_any()), ("codes", PyList::new(py, run.codes)?.into_any()), @@ -418,7 +415,7 @@ fn evaluated_sql_rules<'py>( write_cache(&bucket, (&cache_path, written), &evaluated)?; } let hinted: Bound<'py, PyAny> = call_with( - &native(py, "with_duplicate_literal_hints")?, + &native(py, NativeAttr::WithDuplicateLiteralHints)?, &[ ("findings", PyTuple::new(py, findings.iter())?.into_any()), ("project_dir", project_dir.clone()), @@ -434,7 +431,7 @@ fn write_cache<'py>( evaluated: &[Bound<'py, PyAny>], ) -> PyResult<()> { let py: Python<'py> = bucket.py(); - let encode: Bound<'py, PyAny> = native(py, "encode_sql_rule_findings")?; + let encode: Bound<'py, PyAny> = native(py, NativeAttr::EncodeSqlRuleFindings)?; let mut by_path: HashMap>> = HashMap::new(); for finding in evaluated { let path: String = finding @@ -450,7 +447,7 @@ fn write_cache<'py>( bucket.set_item(path, (identity, encoded))?; } let _ = call_with( - &native(py, "write_sql_rule_cache")?, + &native(py, NativeAttr::WriteSqlRuleCache)?, &[("path", cache_path.clone()), ("entries", bucket.clone())], )?; Ok(()) @@ -464,7 +461,7 @@ fn prepared_lint_violations<'py>( source_files: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = run.project_dir.py(); - let run_lint: Bound<'py, PyAny> = native(py, "run_lint")?; + let run_lint: Bound<'py, PyAny> = native(py, NativeAttr::RunLint)?; let compiled_expansions: Bound<'py, PyAny> = run.project.getattr("sql_expansions")?; let enabled_codes: Bound<'py, PyAny> = run.lint_config.getattr("enabled_native_rules")?; let enabled: Vec = if enabled_codes.is_none() { @@ -551,7 +548,7 @@ fn lint_expansion_context<'py>(run: &SqlRun<'_, 'py>) -> PyResult( model_paths: &BTreeSet, ) -> PyResult>> { let py: Python<'py> = run.project_dir.py(); - let engine_error: String = imported(py, LINT_EXCEPTIONS_MODULE, "NativeLintError")? + let engine_error: String = registry::error(py, ErrorId::NativeLint)? .getattr("code")? .extract()?; let safe_fixes: Bound<'py, PyAny> = PyFrozenSet::new(py, SAFE_RULE_FIX_CODES)?.into_any(); @@ -637,7 +634,7 @@ fn lint_finding<'py>( && safe_fixes.contains(&code)? && fixable_paths.contains(&path.call_method0("as_posix")?.extract::()?); call_with( - &imported(py, RULES_MODELS_MODULE, "Finding")?, + ®istry::class(py, RecordId::Finding)?, &[ ("code", code), ("path", path), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs index cd2e98c6ce..903a68e7d3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs @@ -13,9 +13,9 @@ use sqlbuild_rules::models::{ use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::compiled_project::records::plain_typed_value; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; -const MODEL_PAYLOAD_CLASS: &str = "CompiledModelSqlTestPayload"; const DESCRIPTION_KEY: &str = "description"; fn posix(value: &Bound<'_, PyAny>) -> PyResult { @@ -87,10 +87,8 @@ fn tested_resources(test: &Bound<'_, PyAny>) -> PyResult> { fn with_payload(test: &Bound<'_, PyAny>, mut fact: SqlTestFact) -> PyResult { let payload: Bound<'_, PyAny> = test.getattr("payload")?; - let model_payload: Bound<'_, PyAny> = test - .py() - .import(NATIVE_RECORDS_MODULE)? - .getattr(MODEL_PAYLOAD_CLASS)?; + let model_payload: Bound<'_, PyAny> = + registry::class(test.py(), RecordId::CompiledModelSqlTestPayload)?; if !payload.is_instance(&model_payload)? { return Ok(fact); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs index 27b0c40a09..833b74060d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs @@ -9,7 +9,9 @@ use sqlbuild_core::scenario_command::main::snapshot_capture_warning::snapshot_ca use sqlbuild_core::scenario_command::main::sql_analysis_required_error::sql_analysis_required_error; use sqlbuild_core::scenario_command::models::SqlAnalysisCommand; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, activity, begin_section, connection_hooks, progress_relay, @@ -22,7 +24,6 @@ use crate::bindings::_helpers::scenario_command::setup::{ write, write_headers, }; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; const EXECUTION_COMMAND: &str = "sqb scenario capture"; fn count(label: &str, value: usize) -> SummaryCount { @@ -106,7 +107,7 @@ fn run_scenario_capture_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> P ), )?; let results: Vec> = call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_scenario_capture_pipeline")?, + &host::entry(py, ExecutorHost::RunScenarioCapturePipeline)?, &[ ("project_dir", project.project_dir.clone()), ("pipeline_result", pipeline), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs index 67c07cfe32..997eebc2fe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs @@ -15,7 +15,11 @@ use sqlbuild_core::scenario_command::models::{ NamedStatements, RetainedInputs, RetainedRelation, ScenarioResultRow, WarehouseRuntimeInputs, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const SUCCESS_STATUS: &str = "success"; const FAILED_STATUS: &str = "failed"; @@ -25,9 +29,6 @@ const FAIL_STATUS: &str = "FAIL"; const RUN_DIR: &str = "run"; const SCENARIOS_DIR: &str = "scenarios"; const LOCAL_DIR: &str = "local"; -const PLANNER_TYPES: &str = "sqlbuild._native"; -const QUALIFIED_NAME_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; fn items<'py>(value: &Bound<'py, PyAny>) -> PyResult>> { value.try_iter()?.collect() @@ -91,7 +92,7 @@ fn assertion_row(assertion: &Bound<'_, PyAny>) -> PyResult { fn retained_relations(relation_map: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = relation_map.py(); - let kinds: Bound<'_, PyAny> = imported(py, PLANNER_TYPES, "ScenarioArtifactKind")?; + let kinds: Bound<'_, PyAny> = registry::class(py, RecordId::ScenarioArtifactKind)?; items(&relation_map.getattr("artifacts")?)? .iter() .map(|artifact| { @@ -250,11 +251,7 @@ fn expectation_sql( let mut expectations: ExpectationSql = ExpectationSql::default(); for expected in items(&scenario_plan.getattr("expected_expectations")?)? { let actual_relation: String = call_with( - &imported( - py, - QUALIFIED_NAME_MODULE, - "resolve_relation_location_qualified_name", - )?, + &host::entry(py, AdapterHost::ResolveRelationLocationQualifiedName)?, &[ ("adapter", adapter.clone()), ("location", expected.getattr("actual_destination")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs index 89fd26f640..a699b2b376 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs @@ -13,16 +13,18 @@ use sqlbuild_core::scenario_command::main::scenario_activity_message::scenario_a use sqlbuild_core::scenario_command::models::ScenarioNamespace; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scenario_command::conversions::{ capture_row, scenario_result_row, write_local_runtime_target, write_warehouse_runtime_target, }; use crate::bindings::_helpers::scenario_command::setup::{ - NATIVE_MODULE, boolean, flush, stream_isatty, text, write, + boolean, flush, stream_isatty, text, write, }; -const RUNTIME_CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; - /// How a finished scenario is reported. #[derive(Clone, Debug)] pub(crate) enum CompletionKind { @@ -159,7 +161,7 @@ pub(crate) fn connection_hooks<'py>( ) -> PyResult> { let py: Python<'py> = stream.py(); let reporter: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", text(py, adapter_name)), ("blank_line_after_complete", boolean(py, true)), @@ -174,7 +176,7 @@ pub(crate) fn connection_hooks<'py>( }, )?; call_with( - &imported(py, RUNTIME_CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_connection_start", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs index 2dfdfeddb7..91e0b19097 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs @@ -21,20 +21,14 @@ use sqlbuild_core::scenario_command::models::{ }; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; use crate::bindings::_helpers::discovery::guidance::sql_analysis_guidance; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const SCENARIO_CONFIG_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const SCENARIO_MODELS: &str = "sqlbuild.executor.scenario.models"; const METADATA_MODULE: &str = "importlib.metadata"; const DISTRIBUTION_NAME: &str = "sqlbuild"; const UNKNOWN_VERSION: &str = "unknown"; @@ -53,7 +47,7 @@ pub(crate) fn cli_user_error(py: Python<'_>, error: ScenarioCommandError) -> PyE let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; options.set_item("help", error.help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -120,13 +114,13 @@ pub(crate) fn discover_project<'py>( _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let selectors: Bound<'py, PyAny> = request.getattr("selectors")?; let exclude: Bound<'py, PyAny> = request.getattr("exclude")?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", selectors.clone()), ("exclude", exclude.clone()), @@ -192,7 +186,7 @@ pub(crate) fn require_sql_analysis( pub(crate) fn project_adapter_name(project: &ScenarioProject<'_>) -> PyResult { let py: Python<'_> = project.discovered.py(); call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ( "project_config", @@ -227,7 +221,7 @@ pub(crate) fn project_connection_config<'py>( ) -> PyResult> { let py: Python<'py> = project.discovered.py(); call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", project.discovered.clone()), ("project_dir", project.project_dir.clone()), @@ -252,7 +246,7 @@ pub(crate) fn write_headers<'py>( write(stream, &render_namespace_header(&project.namespace))?; let selectors: Vec = project.selectors.extract()?; let planning: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", boolean(py, style.use_color)), @@ -261,7 +255,7 @@ pub(crate) fn write_headers<'py>( write(stream, "\n")?; let target: Option = (!selectors.is_empty()).then(|| selectors.join(" ")); call_with( - &imported(py, NATIVE_MODULE, "write_execution_header")?, + ®istry::native(py, NativeAttr::WriteExecutionHeader)?, &[ ("stream", stream.clone()), ("command", text(py, command)), @@ -292,11 +286,7 @@ pub(crate) fn compile<'py>( } = inputs; let py: Python<'py> = project.discovered.py(); let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", project.project_dir.clone()), ("discovered_inputs", project.discovered.clone()), @@ -305,11 +295,11 @@ pub(crate) fn compile<'py>( let mut pairs: Vec<(&str, Bound<'py, PyAny>)> = options.to_vec(); pairs.push(("external_sql_reference_resolver", resolver)); let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &pairs, )?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", text(py, "project")), ("operation_name", text(py, "project_compile")), @@ -317,7 +307,7 @@ pub(crate) fn compile<'py>( )?; within(&lifecycle, |_| { call_with( - &imported(py, NATIVE_MODULE, "run_compile_only_pipeline")?, + ®istry::native(py, NativeAttr::RunCompileOnlyPipeline)?, &[ ("discovered_inputs", project.discovered.clone()), ("adapter", adapter.clone()), @@ -373,11 +363,7 @@ pub(crate) fn capture_limits<'py>( let py: Python<'py> = request.py(); let inputs: Bound<'py, PyAny> = request.getattr("limit_inputs")?; let configured: Bound<'py, PyAny> = call_with( - &imported( - py, - SCENARIO_CONFIG_MODULE, - "resolve_effective_scenario_config", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveScenarioConfig)?, &[ ( "project_config", @@ -403,7 +389,7 @@ pub(crate) fn capture_limits<'py>( }, ); call_with( - &imported(py, SCENARIO_MODELS, "ScenarioSnapshotCaptureLimits")?, + &host::entry(py, ExecutorHost::ScenarioSnapshotCaptureLimits)?, &[ ( "max_rows_per_relation", @@ -443,7 +429,7 @@ pub(crate) fn capture_settings<'py>( i64::try_from(elapsed.as_secs()).unwrap_or(i64::MAX) }); call_with( - &imported(py, SCENARIO_MODELS, "ScenarioCaptureSettings")?, + &host::entry(py, ExecutorHost::ScenarioCaptureSettings)?, &[ ("captured_at", text(py, &format_captured_at(epoch_seconds))), ("capture_adapter", text(py, capture_adapter)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs index c58c3c8270..dc35415aa3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs @@ -7,19 +7,21 @@ use sqlbuild_core::cli_text::models::SummaryCount; use sqlbuild_core::scenario_command::main::snapshot_capture_warning::snapshot_capture_warning; use sqlbuild_core::scenario_command::models::ScenarioNamespace; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, begin_section, connection_hooks, progress_relay, }; use crate::bindings::_helpers::scenario_command::setup::{ - CompileInputs, NATIVE_MODULE, boolean, capture_limits, capture_settings, compile, flush, - optional_text, project_connection_config, selected_scenarios, text, write, + CompileInputs, boolean, capture_limits, capture_settings, compile, flush, optional_text, + project_connection_config, selected_scenarios, text, write, }; use crate::bindings::_helpers::scenario_command::test_command::TestRun; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; - /// The outcome of syncing local snapshots: its exit code and capture results. pub(crate) struct LocalSync<'py> { pub(crate) exit_code: i32, @@ -36,7 +38,7 @@ impl LocalSync<'_> { ) -> PyResult { let py: Python<'_> = request.py(); let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_scenario_snapshot_execution_json")?, + ®istry::native(py, NativeAttr::FormatScenarioSnapshotExecutionJson)?, &[ ( "run_namespace", @@ -48,7 +50,7 @@ impl LocalSync<'_> { ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -81,11 +83,7 @@ pub(crate) fn sync_local_snapshots<'py>( .getattr("project_config")? .getattr("name")?; let names: Vec = call_with( - &imported( - py, - EXECUTOR_RUN_MODULE, - "select_scenario_snapshot_capture_candidates", - )?, + &host::entry(py, ExecutorHost::SelectScenarioSnapshotCaptureCandidates)?, &[ ("project_dir", run.project.project_dir.clone()), ("pipeline_result", local_pipeline.clone()), @@ -112,7 +110,7 @@ pub(crate) fn sync_local_snapshots<'py>( }); } let planning: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", run.stream.clone()), ("use_color", boolean(py, run.style.use_color)), @@ -172,7 +170,7 @@ pub(crate) fn sync_local_snapshots<'py>( let results: Bound<'py, PyTuple> = PyTuple::new( py, call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_scenario_capture_pipeline")?, + &host::entry(py, ExecutorHost::RunScenarioCapturePipeline)?, &[ ("project_dir", run.project.project_dir.clone()), ("pipeline_result", pipeline), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs index 05eefdb218..624d65d4bd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs @@ -13,23 +13,23 @@ use sqlbuild_core::scenario_command::models::{ ScenarioNamespace, ScenarioTestFlags, SqlAnalysisCommand, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, activity, begin_section, connection_hooks, progress_relay, write_completion, }; use crate::bindings::_helpers::scenario_command::setup::{ - CompileInputs, NATIVE_MODULE, ScenarioProject, boolean, cli_user_error, compile, - discover_project, flush, int, optional_text, project_adapter_name, project_connection_config, - require_capture_dialect, require_sql_analysis, resolve_adapter, selected_scenarios, style, - text, write, write_headers, + CompileInputs, ScenarioProject, boolean, cli_user_error, compile, discover_project, flush, int, + optional_text, project_adapter_name, project_connection_config, require_capture_dialect, + require_sql_analysis, resolve_adapter, selected_scenarios, style, text, write, write_headers, }; use crate::bindings::_helpers::scenario_command::snapshot_sync::{LocalSync, sync_local_snapshots}; -const EFFECTIVE_SETTINGS_MODULE: &str = "sqlbuild._native"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; -const SCENARIO_MODELS: &str = "sqlbuild.executor.scenario.models"; const LOCAL_ADAPTER: &str = "duckdb"; const LOCAL_STATUSES: [&str; 4] = ["PASS", "FAIL", "ERROR", "SKIP"]; const TARGET_DIR: &str = "target"; @@ -96,11 +96,7 @@ fn prepare<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { call_with( - &imported( - py, - EFFECTIVE_SETTINGS_MODULE, - "build_effective_settings_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", project.discovered.clone())], )? .getattr("concurrency")? @@ -150,7 +146,7 @@ fn finish( write_completion(&run.stream, (&run.project.namespace, succeeded))?; let namespace: &ScenarioNamespace = &run.project.namespace; let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_scenario_execution_json")?, + ®istry::native(py, NativeAttr::FormatScenarioExecutionJson)?, &[ ("results", results.clone().into_any()), ("local", boolean(py, run.local)), @@ -166,7 +162,7 @@ fn finish( ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -231,9 +227,9 @@ fn run_scenarios( relay.getattr("on_scenario_complete")?, ), ]; - let runner: &str = if run.local { + let runner: ExecutorHost = if run.local { let replay_source: Bound<'_, PyAny> = call_with( - &imported(py, SCENARIO_MODELS, "ScenarioLocalReplaySource")?, + &host::entry(py, ExecutorHost::ScenarioLocalReplaySource)?, &[ ( "lexical_syntax", @@ -248,7 +244,7 @@ fn run_scenarios( ("strict", request.getattr("strict")?), ("replay_source", replay_source), ]); - "run_scenario_local_test_pipeline" + ExecutorHost::RunScenarioLocalTestPipeline } else { arguments.extend([ ("connection_config", run.connection_config.clone()), @@ -259,11 +255,11 @@ fn run_scenarios( connection_hooks(&run.stream, (&run.adapter_name, run.style))?, ), ]); - "run_scenario_test_pipeline" + ExecutorHost::RunScenarioTestPipeline }; let results: Bound<'_, PyTuple> = PyTuple::new( py, - call_with(&imported(py, EXECUTOR_RUN_MODULE, runner)?, &arguments)? + call_with(&host::entry(py, runner)?, &arguments)? .try_iter()? .collect::>>()?, )?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs index 1e77649b84..103028b1ff 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs @@ -1,5 +1,6 @@ //! Layouts of the declaration scope value records hosted natively. +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_RESOURCE_IDENTITY: RecordSpec = RecordSpec { @@ -23,7 +24,7 @@ const R_OWNERSHIP_ROOT: RecordSpec = RecordSpec { FieldSpec::required("path"), FieldSpec::new( "kind", - FieldDefault::Member("sqlbuild._native", "OwnershipRootKind", "RESOURCE"), + FieldDefault::NativeMember(RecordId::OwnershipRootKind, "RESOURCE"), ), FieldSpec::new("resource_kind", FieldDefault::None), ], @@ -94,7 +95,7 @@ const R_USAGE_RECORD: RecordSpec = RecordSpec { FieldSpec::required("declaration"), FieldSpec::new( "kind", - FieldDefault::Member("sqlbuild._native", "UsageKind", "RUNTIME"), + FieldDefault::NativeMember(RecordId::UsageKind, "RUNTIME"), ), FieldSpec::new("through", FieldDefault::None), FieldSpec::new("enum_member", FieldDefault::None), @@ -109,7 +110,7 @@ const R_GRANT_RECORD: RecordSpec = RecordSpec { FieldSpec::required("through"), FieldSpec::new( "kind", - FieldDefault::Member("sqlbuild._native", "GrantKind", "EXPECTED_MODEL"), + FieldDefault::NativeMember(RecordId::GrantKind, "EXPECTED_MODEL"), ), ], }; @@ -140,7 +141,7 @@ const R_SCOPE_DIAGNOSTIC: RecordSpec = RecordSpec { FieldSpec::required("message"), FieldSpec::new( "severity", - FieldDefault::Member("sqlbuild._native", "ScopeDiagnosticSeverity", "ERROR"), + FieldDefault::NativeMember(RecordId::ScopeDiagnosticSeverity, "ERROR"), ), FieldSpec::new("path", FieldDefault::None), FieldSpec::new("line", FieldDefault::None), @@ -175,7 +176,7 @@ const R_SCOPE_INDEX: RecordSpec = RecordSpec { FieldSpec::new("diagnostics", FieldDefault::EmptyTuple), FieldSpec::new( "completeness", - FieldDefault::Call("sqlbuild._native", "ScopeCompleteness"), + FieldDefault::NativeCall(RecordId::ScopeCompleteness), ), ], }; @@ -402,7 +403,7 @@ const R_SCOPE_REPORT: RecordSpec = RecordSpec { FieldSpec::new("nearby_unavailable", FieldDefault::EmptyTuple), FieldSpec::new( "filters", - FieldDefault::Call("sqlbuild._native", "ScopeReportFilters"), + FieldDefault::NativeCall(RecordId::ScopeReportFilters), ), FieldSpec::new("sections", FieldDefault::EmptyTuple), FieldSpec::new("explanation", FieldDefault::None), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs index ec33f64641..a72d78c342 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs @@ -19,11 +19,9 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::compiled_project::model_loop_named::scope_path_message; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const INACCESSIBLE_DECLARATION_CODE: &str = "S006"; const SCHEMA_USAGE_KINDS: [&str; 2] = ["schema", "enum"]; @@ -145,13 +143,11 @@ pub(crate) fn model_schema_scope_usages<'py>( let (usages, diagnostics, error) = scope_schema_usages(schemas, record_rows)?; if let Some(message) = error { return Err(PyErr::from_value( - py.import(SCOPE_ERRORS_MODULE)? - .getattr("InvalidScopePathError")? - .call1((message,))?, + registry::error(py, ErrorId::InvalidScopePath)?.call1((message,))?, )); } report_inaccessible(py, diagnostics)?; - let usage_record = py.import(SCOPE_MODELS_MODULE)?.getattr("UsageRecord")?; + let usage_record = registry::class(py, RecordId::UsageRecord)?; let mut built: Vec> = Vec::with_capacity(usages.len()); for (consumer, used) in usages { let fields: Bound<'py, PyDict> = PyDict::new(py); @@ -190,12 +186,8 @@ fn report_inaccessible(py: Python<'_>, diagnostics: Vec) -> PyRes if diagnostics.is_empty() { return Ok(()); } - let report = py - .import(COLLECTOR_MODULE)? - .getattr("report_compile_diagnostic")?; - let compiler_diagnostic = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?; + let report = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; + let compiler_diagnostic = registry::class(py, RecordId::CompilerDiagnostic)?; let path_class = py.import("pathlib")?.getattr("Path")?; for (key, message, path, help) in diagnostics { let fields: Bound<'_, PyDict> = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs index 9ac4b234e4..ecce43864f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs @@ -13,13 +13,11 @@ use sqlbuild_scopes::relationship_names::models::{ use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; - /// One scan's error, or its values. type Scanned = (Option, Vec); @@ -105,16 +103,12 @@ fn extract_sql_test_ctes<'py>( let ctes: Vec<(String, String)> = match outcome { TopLevelCtes::Scanned(ctes) => ctes, TopLevelCtes::Failed(message) => { - let error: Bound<'py, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr("CompileInputError")? - .call1((message,))?; + let error: Bound<'py, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call1((message,))?; return Err(PyErr::from_value(error)); } }; - let cte_class: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("CompileSqlTestCte")?; + let cte_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompileSqlTestCte)?; let mut built: Vec> = Vec::with_capacity(ctes.len()); for (name, body) in ctes { let arguments: Bound<'py, PyDict> = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs index 817073f23a..f9ce48b8b1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs @@ -14,6 +14,8 @@ use sqlbuild_scopes::scope_text::main::scope_result_failed::scope_result_failed; use sqlbuild_scopes::scope_text::models::ScopeTextRequest; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; use crate::bindings::_helpers::scopes::scope_objects::ScopeClasses; use crate::bindings::_helpers::scopes::scope_report_objects::read_result; @@ -22,10 +24,6 @@ use crate::bindings::_helpers::scopes::scope_reports::{ serialize_scope_report, }; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const TARGET_ERROR_CODE: &str = "C960"; const BARE_USAGE: &str = "bare names work for models, sources, seeds and functions; write macros, \ enums, constants, tests and audits with their prefix, for example macro:"; @@ -48,10 +46,8 @@ fn user_error(py: Python<'_>, message: String) -> PyResult { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", TARGET_ERROR_CODE)?; options.set_item("help", BARE_USAGE)?; - let error: Bound<'_, PyAny> = py - .import(ERRORS_MODULE)? - .getattr("CliUserError")? - .call((message,), Some(&options))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options))?; Ok(PyErr::from_value(error)) } @@ -163,7 +159,7 @@ fn report_filters<'py>( py: Python<'py>, request: &Bound<'py, PyAny>, ) -> PyResult> { - let kind_class: Bound<'_, PyAny> = py.import(TYPES_MODULE)?.getattr("DeclarationKind")?; + let kind_class: Bound<'_, PyAny> = registry::class(py, RecordId::DeclarationKind)?; let mut kinds: Vec> = Vec::new(); for kind in request.getattr("kinds")?.try_iter()? { kinds.push(kind_class.call1((kind?,))?); @@ -183,9 +179,7 @@ fn report_filters<'py>( ("globals", request.getattr("globals")?), ], )?; - py.import(MODELS_MODULE)? - .getattr("ScopeReportFilters")? - .call((), Some(&options)) + registry::class(py, RecordId::ScopeReportFilters)?.call((), Some(&options)) } /// `dataclasses.replace(report, **changes)`. @@ -341,14 +335,13 @@ fn run_scope_command( load_scope_index: Option>, output_stream: Option>, ) -> PyResult { - let native: Bound<'_, PyModule> = py.import(NATIVE_MODULE)?; let project_dir: Bound<'_, PyAny> = match request.getattr("project_dir")? { directory if !directory.is_none() => directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; let loader: Bound<'_, PyAny> = match load_scope_index { Some(loader) => loader, - None => native.getattr("load_or_build_scope_index")?, + None => registry::native(py, NativeAttr::LoadOrBuildScopeIndex)?, }; let options: Bound<'_, PyDict> = keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs index 56c42b1674..ecc9d77def 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs @@ -1,21 +1,17 @@ //! Report a scope index's error findings as individual compile diagnostics. +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const ERROR_SEVERITY: &str = "error"; /// `report_scope_index_errors`: each error-severity finding of `index`, keyed for the collector. pub(crate) fn report_scope_index_errors(index: &Bound<'_, PyAny>) -> PyResult<()> { let py: Python<'_> = index.py(); - let report = py - .import(COLLECTOR_MODULE)? - .getattr("report_compile_diagnostic")?; - let compiler_diagnostic = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?; + let report = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; + let compiler_diagnostic = registry::class(py, RecordId::CompilerDiagnostic)?; let path_class = py.import("pathlib")?.getattr("Path")?; for item in index.getattr("diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs index 742be7540d..21bd4d6cfc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs @@ -20,6 +20,8 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::relationship_scan::{ RelationshipScanSettings, ScenarioRow, TestFileRow, scanned_relationships, }; @@ -32,7 +34,6 @@ use crate::bindings::types::CompilerDetach; /// Why `UnindexableScopeError` is raised; discovery only yields normalized project paths. const UNINDEXABLE_SCOPE_MESSAGE: &str = "Native scope indexing could not place a discovered path or resource kind. Discovery always yields normalized project-relative paths, so this is a SQLBuild bug; please file an issue with the project layout"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const INSPECT_MODULE: &str = "inspect"; const CODE_TYPE_MODULE: &str = "types"; const PATHLIB_MODULE: &str = "pathlib"; @@ -145,8 +146,8 @@ impl<'py> Walk<'py> { ) -> PyResult<()> { let (path, root) = location; self.resource_identities - .push(self.classes.model("ResourceIdentity")?.call1(( - self.classes.member("ResourceKind", kind.as_str())?, + .push(self.classes.model(RecordId::ResourceIdentity)?.call1(( + self.classes.member(RecordId::ResourceKind, kind.as_str())?, name.as_str(), ))?); self.resource_roots.push((kind, root)); @@ -223,8 +224,9 @@ impl<'py> Walk<'py> { name: &Bound<'py, PyAny>, owner: Option<&Bound<'py, PyAny>>, ) -> PyResult> { - self.classes.model("DeclarationIdentity")?.call1(( - self.classes.member("DeclarationKind", kind.as_str())?, + self.classes.model(RecordId::DeclarationIdentity)?.call1(( + self.classes + .member(RecordId::DeclarationKind, kind.as_str())?, name, owner, )) @@ -246,12 +248,12 @@ impl<'py> Walk<'py> { } fn enum_metadata(&self, declaration: &Bound<'py, PyAny>) -> PyResult> { - let member_class: Bound<'py, PyAny> = self.classes.model("EnumMemberMetadata")?; + let member_class: Bound<'py, PyAny> = self.classes.model(RecordId::EnumMemberMetadata)?; let members: Vec> = items(declaration, "members")? .iter() .map(|member| member_class.call1((member.getattr("name")?,))) .collect::>()?; - self.classes.model("EnumMetadata")?.call1(( + self.classes.model(RecordId::EnumMetadata)?.call1(( PyTuple::new(declaration.py(), members)?, declaration.getattr("scalar_type")?, )) @@ -271,7 +273,7 @@ impl<'py> Walk<'py> { let render_as: Option> = optional_item(declaration, "render_as")? .map(|rendering| rendering.getattr("value")) .transpose()?; - self.classes.model("ConstantMetadata")?.call1(( + self.classes.model(RecordId::ConstantMetadata)?.call1(( declaration .getattr("logical_type")? .getattr("display_name")?, @@ -295,14 +297,15 @@ impl<'py> Walk<'py> { model_name.clone(), (&path, ResourceRoot::Kind), )?; - let owner: Bound<'py, PyAny> = self.classes.model("ResourceIdentity")?.call1(( - self.classes - .member("ResourceKind", ResourceKind::Model.as_str())?, - model_name, - ))?; + let owner: Bound<'py, PyAny> = + self.classes.model(RecordId::ResourceIdentity)?.call1(( + self.classes + .member(RecordId::ResourceKind, ResourceKind::Model.as_str())?, + model_name, + ))?; let private: Bound<'py, PyAny> = self .classes - .member("ScopeKind", ScopeKind::Private.as_str())?; + .member(RecordId::ScopeKind, ScopeKind::Private.as_str())?; for declaration in items(&model_file, "enum_declarations")? { let identity: Bound<'py, PyAny> = self.declaration_identity( DeclarationKind::Enum, @@ -501,7 +504,7 @@ impl<'py> Walk<'py> { Some(file) => file.getattr("scope_kind")?, None => self .classes - .member("ScopeKind", ScopeKind::Global.as_str())?, + .member(RecordId::ScopeKind, ScopeKind::Global.as_str())?, }; let dependencies: Vec> = items(export, "dependencies")?; let placement: Placement = Placement { @@ -539,7 +542,7 @@ impl<'py> Walk<'py> { .try_iter()? .collect::>()?; let raw_source: String = field(loaded, "raw_source")?; - let metadata: Bound<'py, PyAny> = self.classes.model("MacroMetadata")?.call1(( + let metadata: Bound<'py, PyAny> = self.classes.model(RecordId::MacroMetadata)?.call1(( PyTuple::new(py, parameters)?, loaded.getattr("dependencies")?, macro_source_digest(&raw_source), @@ -577,11 +580,12 @@ impl<'py> Walk<'py> { for export in static_macros.try_iter()? { let export: Bound<'py, PyAny> = export?; let key: String = normalized(py, &export.getattr("relative_path")?)?; - let metadata: Bound<'py, PyAny> = self.classes.model("MacroMetadata")?.call1(( - export.getattr("parameters")?, - export.getattr("dependencies")?, - export.getattr("source_digest")?, - ))?; + let metadata: Bound<'py, PyAny> = + self.classes.model(RecordId::MacroMetadata)?.call1(( + export.getattr("parameters")?, + export.getattr("dependencies")?, + export.getattr("source_digest")?, + ))?; let (values, placement) = self.macro_values( macro_files.get(&key), (&export, field(&export, "line")?, metadata), @@ -603,9 +607,7 @@ fn normalized(py: Python<'_>, path: &Bound<'_, PyAny>) -> PyResult { fn unindexable(py: Python<'_>) -> PyResult { Ok(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr("UnindexableScopeError")? - .call1((UNINDEXABLE_SCOPE_MESSAGE,))?, + registry::error(py, ErrorId::UnindexableScope)?.call1((UNINDEXABLE_SCOPE_MESSAGE,))?, )) } @@ -631,15 +633,17 @@ fn resource_root<'py>( Some((_, path)) => (path, "resource", kind), None => { return Err(PyKeyError::new_err( - classes.member("ResourceKind", kind.as_str())?.unbind(), + classes + .member(RecordId::ResourceKind, kind.as_str())? + .unbind(), )); } }, }; - classes.model("OwnershipRoot")?.call1(( + classes.model(RecordId::OwnershipRoot)?.call1(( path, - classes.member("OwnershipRootKind", root_kind)?, - classes.member("ResourceKind", resource_kind.as_str())?, + classes.member(RecordId::OwnershipRootKind, root_kind)?, + classes.member(RecordId::ResourceKind, resource_kind.as_str())?, )) } @@ -653,7 +657,7 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult(walk: Walk<'py>, native: ScopeIndex) -> PyResult = classes.model("UsageRecord")?; + let usage_class: Bound<'py, PyAny> = classes.model(RecordId::UsageRecord)?; let dependency_kind: Bound<'py, PyAny> = - classes.member("UsageKind", "declaration_dependency")?; + classes.member(RecordId::UsageKind, "declaration_dependency")?; let mut usages: Vec> = Vec::with_capacity(native.usages.len()); for usage in &native.usages { usages.push(usage_class.call1(( @@ -691,7 +695,7 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult = classes.model("ScopeDiagnostic")?; + let diagnostic_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeDiagnostic)?; let mut diagnostics: Vec> = Vec::with_capacity(native.diagnostics.len()); for diagnostic in &native.diagnostics { let keywords: Bound<'py, PyDict> = PyDict::new(py); @@ -714,7 +718,7 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult(walk: Walk<'py>, native: ScopeIndex) -> PyResult>()?; let completeness: Bound<'py, PyAny> = { @@ -743,14 +749,14 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult], order: &[usize]| -> PyResult> { PyTuple::new(py, order.iter().map(|position| records[*position].clone())) }; - let index: Bound<'py, PyAny> = classes.model("ScopeIndex")?.call1(( + let index: Bound<'py, PyAny> = classes.model(RecordId::ScopeIndex)?.call1(( PyTuple::new(py, roots)?, ordered(&resources, &native.resource_order)?, ordered(&declarations, &native.declaration_order)?, @@ -841,8 +847,8 @@ impl NativeScopeIndex { let mut records: Vec> = Vec::with_capacity(grants.len()); for grant in &grants { let through: Bound<'_, PyAny> = match &grant.through { - GrantThrough::Model(name) => classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", ResourceKind::Model.as_str())?, + GrantThrough::Model(name) => classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, ResourceKind::Model.as_str())?, name.as_str(), ))?, GrantThrough::Declaration(position) => { @@ -850,16 +856,16 @@ impl NativeScopeIndex { } }; records.push( - classes.model("GrantRecord")?.call1(( - classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", grant.resource.kind.as_str())?, + classes.model(RecordId::GrantRecord)?.call1(( + classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, grant.resource.kind.as_str())?, grant.resource.name.as_str(), ))?, self.declarations[grant.declaration] .bind(py) .getattr("identity")?, through, - classes.member("GrantKind", grant.kind.as_str())?, + classes.member(RecordId::GrantKind, grant.kind.as_str())?, ))?, ); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs index 53daffc840..05a24599f4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs @@ -9,9 +9,14 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::scope_snapshot::discover_scope_snapshot; use crate::bindings::_helpers::frontier::cache_directory::cache_directory; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_cache::{ project_fingerprint, read_scope_index_cache, write_scope_index_cache, }; @@ -20,17 +25,8 @@ use crate::bindings::_helpers::scopes::scope_tolerant::scope_tolerant_index; use crate::bindings::_helpers::scopes::scope_validation::scope_index_with_compile_usages; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_EXPECTED_MODELS_SCAN_ALGORITHM; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const SCOPE_CACHE_DIRECTORY_NAME: &str = "declaration-scopes-v2"; const SCOPE_CACHE_FILENAME: &str = "scope-index.json"; -const SQL_ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const COLLECTION_RENDERING_MODULE: &str = "sqlbuild._native"; -const RESOLVE_ADAPTER_MODULE: &str = "sqlbuild.adapter.discovery.main.resolve_adapter"; -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const MACRO_INVENTORY_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; /// Where the scope index cache file of a project lives: `(directory, file name)`. fn cache_location(project_dir: &Bound<'_, PyAny>) -> PyResult<(PathBuf, String)> { @@ -55,17 +51,17 @@ fn project_adapter<'py>( let discover_keywords: Bound<'py, PyDict> = PyDict::new(py); discover_keywords.set_item("project_dir", project_dir)?; discover_keywords.set_item("extract_output_column_locations", false)?; - let discovered: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "discover_project_inputs")? + let discovered: Bound<'py, PyAny> = registry::native(py, NativeAttr::DiscoverProjectInputs)? .call((), Some(&discover_keywords))?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), ], )?; let adapter: Bound<'py, PyAny> = call_with( - &imported(py, RESOLVE_ADAPTER_MODULE, "resolve_adapter")?, + &host::entry(py, AdapterHost::ResolveAdapter)?, &[("adapter_name", adapter_name)], )?; Ok((discovered, adapter)) @@ -80,18 +76,14 @@ fn compiled_index<'py>( let py: Python<'py> = discovered.py(); let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let collection_rendering: Bound<'py, PyAny> = call_with( - &imported( - py, - COLLECTION_RENDERING_MODULE, - "resolve_effective_collection_rendering", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveCollectionRendering)?, &[ ("project_config", project_config), ("declaration_override", py.None().into_bound(py)), ], )?; let adapter_context: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAdapterContext")?, + ®istry::class(py, RecordId::CompileAdapterContext)?, &[ ("value_renderer", adapter.clone()), ("collection_rendering", collection_rendering), @@ -110,16 +102,15 @@ fn compiled_index<'py>( stage_keywords.set_item("defer_model_sql_validation", true)?; stage_keywords.set_item("no_cache", true)?; let python_stage: Bound<'py, PyAny> = py.import("functools")?.getattr("partial")?.call( - (imported(py, NATIVE_MODULE, "build_compile_inputs")?,), + (registry::native(py, NativeAttr::BuildCompileInputs)?,), Some(&stage_keywords), )?; let compile_inputs: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "compile_frontier")?, + ®istry::native(py, NativeAttr::CompileFrontier)?, &[ ( "until", - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")? - .getattr("COMPILE_PROJECT_INPUTS")?, + registry::class(py, RecordId::CompilerStage)?.getattr("COMPILE_PROJECT_INPUTS")?, ), ("python_stage", python_stage), ], @@ -136,7 +127,7 @@ fn tolerant_index<'py>( let snapshot: Bound<'py, PyAny> = discover_scope_snapshot(project_dir)?; let discovered: Bound<'py, PyAny> = snapshot.getattr("discovered_inputs")?; let inventory: Bound<'py, PyAny> = call_with( - &imported(py, MACRO_INVENTORY_MODULE, "inventory_project_macros")?, + &host::entry(py, UserCodeHost::InventoryProjectMacros)?, &[("macro_files", discovered.getattr("macro_files")?)], )?; let native: Bound<'py, PyAny> = Bound::new( @@ -192,8 +183,7 @@ fn load_or_build_scope_index<'py>( return Ok(cached.into_bound(py)); } } - let mut syntax: Bound<'py, PyAny> = - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")?.call0()?; + let mut syntax: Bound<'py, PyAny> = registry::class(py, RecordId::SqlLexicalSyntax)?.call0()?; let compiled: PyResult> = match project_adapter(&resolved) { Ok((discovered, adapter)) => { syntax = adapter.getattr("sql_lexical_syntax")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs index 2c489b1889..1f7cd1852f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs @@ -17,6 +17,8 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::placement::{QualifiedRow, qualified_identity}; use crate::bindings::_helpers::scopes::scope_objects::{ ScopeClasses, read_index, report_records, resource_identity_object, tuple_attribute, @@ -24,7 +26,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ use crate::bindings::types::CompilerDetach; const TYPES_MODULE: &str = "types"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; /// `(declarations, canonical positions)` matched, else identity and path error messages. pub(crate) type TargetRows = (bool, Vec, Option, Option); @@ -250,7 +251,7 @@ fn visibility_index<'py>( PyTuple::new(py, positions)?, )?; } - classes.model("DeclarationVisibilityIndex")?.call1(( + classes.model(RecordId::DeclarationVisibilityIndex)?.call1(( PyTuple::new(py, identities)?, PyTuple::new(py, &visibility.global)?, mapping_proxy(&private)?, @@ -261,9 +262,7 @@ fn visibility_index<'py>( pub(crate) fn scope_path_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr("InvalidScopePathError")? - .call1((message,))?, + registry::error(py, ErrorId::InvalidScopePath)?.call1((message,))?, )) } @@ -285,7 +284,7 @@ pub(crate) fn build_scope_lookup(py: Python<'_>, index: &Bound<'_, PyAny>) -> Py let usages: Bound<'_, PyTuple> = tuple_attribute(index, "usages")?; let grants: Bound<'_, PyTuple> = tuple_attribute(index, "grants")?; let canonical_declarations: Bound<'_, PyTuple> = picked(&declarations, &lookup.declarations)?; - let canonical_index: Bound<'_, PyAny> = classes.model("ScopeIndex")?.call1(( + let canonical_index: Bound<'_, PyAny> = classes.model(RecordId::ScopeIndex)?.call1(( picked(&tuple_attribute(index, "ownership_roots")?, &lookup.roots)?, picked(&resources, &lookup.resources)?, &canonical_declarations, @@ -333,7 +332,7 @@ pub(crate) fn build_scope_lookup(py: Python<'_>, index: &Bound<'_, PyAny>) -> Py by_resource, ] = mappings; Ok(classes - .model("ScopeLookup")? + .model(RecordId::ScopeLookup)? .call1(( canonical_index, by_identity, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs index ffeafab18c..a2ebbd44cf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs @@ -1,6 +1,6 @@ //! Python `ScopeIndex` models read into the native full index, and built back from it. -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyResult, Python}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; use sqlbuild_scopes::placement::models::{PlacementIndex, PlacementUsage}; use sqlbuild_scopes::scope_cache::models::{ @@ -17,44 +17,40 @@ use sqlbuild_scopes::scope_report::models::{ ProjectionGrant, ProjectionIndex, ProjectionStanding, ProjectionUsage, ReportValues, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::placement::{ IdentityRow, QualifiedRow, RootRow, declaration_identity, declaration_record, ownership_root, qualified_identity, resource_identity, }; use crate::bindings::_helpers::scopes::scope_lookup::grant_kind; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; const ENUM_MODULE: &str = "enum"; -/// The scope model and enum modules every built object comes from. +/// The native scope model and enum classes every built object comes from. pub(crate) struct ScopeClasses<'py> { - models: Bound<'py, PyModule>, - types: Bound<'py, PyModule>, + py: Python<'py>, } impl<'py> ScopeClasses<'py> { - /// Import the scope models and enums. + /// The scope classes of `py`. pub(crate) fn new(py: Python<'py>) -> PyResult { - Ok(Self { - models: py.import(MODELS_MODULE)?, - types: py.import(TYPES_MODULE)?, - }) + Ok(Self { py }) } /// One scope model class. - pub(crate) fn model(&self, name: &str) -> PyResult> { - self.models.getattr(name) + pub(crate) fn model(&self, name: RecordId) -> PyResult> { + registry::class(self.py, name) } /// The member of scope enum `name` with `value`; an unknown value raises `ValueError`. - pub(crate) fn member(&self, name: &str, value: &str) -> PyResult> { - self.types.getattr(name)?.call1((value,)) + pub(crate) fn member(&self, name: RecordId, value: &str) -> PyResult> { + registry::class(self.py, name)?.call1((value,)) } /// The interpreter the classes belong to. pub(crate) fn py(&self) -> Python<'py> { - self.models.py() + self.py } } @@ -464,8 +460,8 @@ pub(crate) fn resource_identity_object<'py>( classes: &ScopeClasses<'py>, identity: &ResourceIdentity, ) -> PyResult> { - classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", identity.kind.as_str())?, + classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, identity.kind.as_str())?, identity.name.as_str(), )) } @@ -480,8 +476,8 @@ pub(crate) fn declaration_identity_object<'py>( .as_ref() .map(|owner| resource_identity_object(classes, owner)) .transpose()?; - classes.model("DeclarationIdentity")?.call1(( - classes.member("DeclarationKind", identity.kind.as_str())?, + classes.model(RecordId::DeclarationIdentity)?.call1(( + classes.member(RecordId::DeclarationKind, identity.kind.as_str())?, identity.name.as_str(), owner, )) @@ -514,11 +510,11 @@ pub(crate) fn root_object<'py>( ) -> PyResult> { let resource_kind: Option> = root .resource_kind - .map(|kind| classes.member("ResourceKind", ResourceKind::as_str(kind))) + .map(|kind| classes.member(RecordId::ResourceKind, ResourceKind::as_str(kind))) .transpose()?; - classes.model("OwnershipRoot")?.call1(( + classes.model(RecordId::OwnershipRoot)?.call1(( root.path.as_str(), - classes.member("OwnershipRootKind", root.kind.as_str())?, + classes.member(RecordId::OwnershipRootKind, root.kind.as_str())?, resource_kind, )) } @@ -527,7 +523,7 @@ fn resource_object<'py>( classes: &ScopeClasses<'py>, record: &ResourceRecord, ) -> PyResult> { - classes.model("ResourceRecord")?.call1(( + classes.model(RecordId::ResourceRecord)?.call1(( resource_identity_object(classes, &record.identity)?, record.path.as_str(), root_object(classes, &record.ownership_root)?, @@ -548,7 +544,7 @@ fn macro_object<'py>( facts: &MacroFacts, ) -> PyResult> { let py: Python<'py> = classes.py(); - classes.model("MacroMetadata")?.call1(( + classes.model(RecordId::MacroMetadata)?.call1(( PyTuple::new(py, &facts.parameters)?, tuple_of(py, &facts.dependencies, |identity| { declaration_identity_object(classes, identity) @@ -558,8 +554,8 @@ fn macro_object<'py>( } fn enum_object<'py>(classes: &ScopeClasses<'py>, facts: &EnumFacts) -> PyResult> { - let member_class: Bound<'py, PyAny> = classes.model("EnumMemberMetadata")?; - classes.model("EnumMetadata")?.call1(( + let member_class: Bound<'py, PyAny> = classes.model(RecordId::EnumMemberMetadata)?; + classes.model(RecordId::EnumMetadata)?.call1(( tuple_of(classes.py(), &facts.members, |name| { member_class.call1((name.as_str(),)) })?, @@ -571,7 +567,7 @@ fn constant_object<'py>( classes: &ScopeClasses<'py>, facts: &ConstantFacts, ) -> PyResult> { - classes.model("ConstantMetadata")?.call1(( + classes.model(RecordId::ConstantMetadata)?.call1(( facts.logical_type.as_str(), facts.collection_kind.as_deref(), facts.item_count, @@ -600,12 +596,12 @@ fn declaration_object<'py>( .transpose()?, ]; let [macro_metadata, enum_metadata, constant_metadata] = values; - classes.model("DeclarationRecord")?.call1(( + classes.model(RecordId::DeclarationRecord)?.call1(( declaration_identity_object(classes, &record.identity)?, record.path.as_str(), record.line, record.column, - classes.member("ScopeKind", record.scope.as_str())?, + classes.member(RecordId::ScopeKind, record.scope.as_str())?, root_object(classes, &record.ownership_root)?, record.owning_path.as_deref(), macro_metadata, @@ -618,10 +614,10 @@ fn usage_object<'py>( classes: &ScopeClasses<'py>, item: &CachedUsage, ) -> PyResult> { - classes.model("UsageRecord")?.call1(( + classes.model(RecordId::UsageRecord)?.call1(( qualified_object(classes, &item.consumer)?, declaration_identity_object(classes, &item.declaration)?, - classes.member("UsageKind", &item.kind)?, + classes.member(RecordId::UsageKind, &item.kind)?, optional_qualified_object(classes, item.through.as_ref())?, item.enum_member.as_deref(), )) @@ -631,11 +627,11 @@ fn grant_object<'py>( classes: &ScopeClasses<'py>, item: &ReportGrant, ) -> PyResult> { - classes.model("GrantRecord")?.call1(( + classes.model(RecordId::GrantRecord)?.call1(( resource_identity_object(classes, &item.resource)?, declaration_identity_object(classes, &item.declaration)?, qualified_object(classes, &item.through)?, - classes.member("GrantKind", item.kind.as_str())?, + classes.member(RecordId::GrantKind, item.kind.as_str())?, )) } @@ -643,10 +639,10 @@ fn visibility_object<'py>( classes: &ScopeClasses<'py>, item: &CachedVisibility, ) -> PyResult> { - classes.model("VisibilityRecord")?.call1(( + classes.model(RecordId::VisibilityRecord)?.call1(( resource_identity_object(classes, &item.resource)?, declaration_identity_object(classes, &item.declaration)?, - classes.member("VisibilityReason", &item.reason)?, + classes.member(RecordId::VisibilityReason, &item.reason)?, optional_qualified_object(classes, item.through.as_ref())?, )) } @@ -655,10 +651,10 @@ fn inaccessible_object<'py>( classes: &ScopeClasses<'py>, item: &CachedInaccessible, ) -> PyResult> { - classes.model("InaccessibleRecord")?.call1(( + classes.model(RecordId::InaccessibleRecord)?.call1(( resource_identity_object(classes, &item.resource)?, declaration_identity_object(classes, &item.declaration)?, - classes.member("InaccessibleReason", &item.reason)?, + classes.member(RecordId::InaccessibleReason, &item.reason)?, )) } @@ -667,10 +663,10 @@ pub(crate) fn diagnostic_object<'py>( classes: &ScopeClasses<'py>, item: &CachedDiagnostic, ) -> PyResult> { - classes.model("ScopeDiagnostic")?.call1(( - classes.member("ScopeDiagnosticCode", &item.code)?, + classes.model(RecordId::ScopeDiagnostic)?.call1(( + classes.member(RecordId::ScopeDiagnosticCode, &item.code)?, item.message.as_str(), - classes.member("ScopeDiagnosticSeverity", &item.severity)?, + classes.member(RecordId::ScopeDiagnosticSeverity, &item.severity)?, item.path.as_deref(), item.line, item.column, @@ -691,7 +687,7 @@ pub(crate) fn completeness_object<'py>( done: [bool; 6], ) -> PyResult> { classes - .model("ScopeCompleteness")? + .model(RecordId::ScopeCompleteness)? .call1((done[0], done[1], done[2], done[3], done[4], done[5])) } @@ -701,7 +697,7 @@ pub(crate) fn index_object<'py>( index: &CachedIndex, ) -> PyResult> { let py: Python<'py> = classes.py(); - classes.model("ScopeIndex")?.call1(( + classes.model(RecordId::ScopeIndex)?.call1(( tuple_of(py, &index.roots, |root| root_object(classes, root))?, tuple_of(py, &index.resources, |record| { resource_object(classes, record) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs index bbfbf1599f..01b529f61c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs @@ -14,6 +14,7 @@ use sqlbuild_scopes::scope_report::models::{ ScopeFolder, ScopeReport, ScopeResult, ScopeSection, SourceLocation, }; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::scopes::scope_objects::{ ScopeClasses, diagnostic_facts, field, optional_item, read_declaration_identity, read_diagnostic, read_qualified, @@ -35,8 +36,8 @@ pub(crate) fn read_target( classes: &ScopeClasses<'_>, target: &Bound<'_, PyAny>, ) -> PyResult { - if target.is_instance(&classes.model("ResourceIdentity")?)? - || target.is_instance(&classes.model("DeclarationIdentity")?)? + if target.is_instance(&classes.model(RecordId::ResourceIdentity)?)? + || target.is_instance(&classes.model(RecordId::DeclarationIdentity)?)? { return Ok(QueryTarget::Identity(read_qualified(target)?)); } @@ -65,7 +66,7 @@ pub(crate) fn read_declaration_target( classes: &ScopeClasses<'_>, declaration: &Bound<'_, PyAny>, ) -> PyResult { - if declaration.is_instance(&classes.model("DeclarationIdentity")?)? { + if declaration.is_instance(&classes.model(RecordId::DeclarationIdentity)?)? { return Ok(DeclarationTarget::Identity(read_declaration_identity( declaration, )?)); @@ -215,7 +216,7 @@ fn declaration_object<'py>( .visibility .map(|provenance| { classes - .model("VisibilityProvenance")? + .model(RecordId::VisibilityProvenance)? .call1((provenance.reason, provenance.through)) }) .transpose()?; @@ -224,7 +225,7 @@ fn declaration_object<'py>( report.kind.into_bound_py_any(py)?, report.name.into_bound_py_any(py)?, report.owner.into_bound_py_any(py)?, - classes.model("ScopeSourceLocation")?.call1(( + classes.model(RecordId::ScopeSourceLocation)?.call1(( report.definition.path, report.definition.line, report.definition.column, @@ -247,7 +248,7 @@ fn declaration_object<'py>( ]; let arguments: Vec> = head.into_iter().chain(standing).chain(links).collect(); classes - .model("DeclarationReport")? + .model(RecordId::DeclarationReport)? .call1(PyTuple::new(py, arguments)?) } @@ -264,7 +265,7 @@ fn section_object<'py>( classes: &ScopeClasses<'py>, section: ScopeSection, ) -> PyResult> { - classes.model("ScopeSection")?.call1(( + classes.model(RecordId::ScopeSection)?.call1(( section.name, section.total, section.returned, @@ -290,9 +291,9 @@ pub(crate) fn diagnostics_object<'py>( ReportDiagnostic::Raised(facts) => { let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("path", facts.path)?; - classes.model("ScopeDiagnostic")?.call( + classes.model(RecordId::ScopeDiagnostic)?.call( ( - classes.member("ScopeDiagnosticCode", &facts.code)?, + classes.member(RecordId::ScopeDiagnosticCode, &facts.code)?, facts.message, ), Some(&keywords), @@ -311,7 +312,7 @@ pub(crate) fn explanation_object<'py>( .map(|report| declaration_object(classes, report)) .transpose()?; classes - .model("DeclarationExplanation")? + .model(RecordId::DeclarationExplanation)? .call1((declaration, explanation.complete)) } @@ -320,7 +321,7 @@ pub(crate) fn move_object<'py>( classes: &ScopeClasses<'py>, preview: MovePreview, ) -> PyResult> { - classes.model("MovePreview")?.call1(( + classes.model(RecordId::MovePreview)?.call1(( preview.resource, preview.destination, preview.new_ownership_root, @@ -343,7 +344,7 @@ pub(crate) fn report_object<'py>( let (filters, index_diagnostics) = context; let py: Python<'py> = classes.py(); let resource: ResourceReport = report.resource; - let chain_class: Bound<'py, PyAny> = classes.model("ScopeChainEntry")?; + let chain_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeChainEntry)?; let explanation: Option> = report .explanation .map(|explanation| explanation_object(classes, explanation)) @@ -352,8 +353,8 @@ pub(crate) fn report_object<'py>( .move_preview .map(|preview| move_object(classes, preview)) .transpose()?; - classes.model("ScopeReport")?.call1(( - classes.model("ScopeResourceReport")?.call1(( + classes.model(RecordId::ScopeReport)?.call1(( + classes.model(RecordId::ScopeResourceReport)?.call1(( resource.target, resource.identity, resource.path, @@ -386,8 +387,8 @@ pub(crate) fn browse_object<'py>( index_diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = classes.py(); - let folder_class: Bound<'py, PyAny> = classes.model("ScopeFolder")?; - classes.model("ScopeBrowseResult")?.call1(( + let folder_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeFolder)?; + classes.model(RecordId::ScopeBrowseResult)?.call1(( result.folder, tuple_of(py, result.folders, |folder| { folder_class.call1(( @@ -410,7 +411,7 @@ pub(crate) fn list_object<'py>( result: ListResult, index_diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { - classes.model("ScopeListResult")?.call1(( + classes.model(RecordId::ScopeListResult)?.call1(( result.folder, declarations_object(classes, result.declarations)?, section_object(classes, result.section)?, @@ -588,10 +589,10 @@ pub(crate) fn read_result( classes: &ScopeClasses<'_>, result: &Bound<'_, PyAny>, ) -> PyResult { - if result.is_instance(&classes.model("ScopeReport")?)? { + if result.is_instance(&classes.model(RecordId::ScopeReport)?)? { return Ok(ScopeResult::Report(Box::new(read_report(result)?))); } - if result.is_instance(&classes.model("ScopeBrowseResult")?)? { + if result.is_instance(&classes.model(RecordId::ScopeBrowseResult)?)? { return Ok(ScopeResult::Browse(BrowseResult { folder: field(result, "folder")?, folders: result diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs index 75b24cbe18..1bc4610d7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs @@ -21,6 +21,8 @@ use sqlbuild_scopes::scope_report::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::{ NativeScopeLookup, identity_message, path_message, }; @@ -58,7 +60,6 @@ pub(crate) type ListArguments<'py> = ( bool, ); -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const CURRENT_FOLDER: &str = "."; /// One lookup's native lookup and every declaration's values in input order. @@ -105,11 +106,9 @@ fn lookup_reports(lookup: &Bound<'_, PyAny>) -> PyResult { }) } -fn scope_error(py: Python<'_>, class: &str, message: String) -> PyResult { +fn scope_error(py: Python<'_>, class: ErrorId, message: String) -> PyResult { Ok(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr(class)? - .call1((message,))?, + registry::error(py, class)?.call1((message,))?, )) } @@ -117,11 +116,11 @@ fn scope_error(py: Python<'_>, class: &str, message: String) -> PyResult fn failure_error(py: Python<'_>, failure: &ReportFailure) -> PyResult { match failure { ReportFailure::Path(rejected) => { - scope_error(py, "InvalidScopePathError", path_message(rejected)) + scope_error(py, ErrorId::InvalidScopePath, path_message(rejected)) } ReportFailure::Identity(failure, text) => scope_error( py, - "InvalidQualifiedIdentityError", + ErrorId::InvalidQualifiedIdentity, identity_message(failure, text), ), ReportFailure::Serialization(message) => Ok(PyValueError::new_err(message.clone())), @@ -153,7 +152,7 @@ pub(crate) fn scope_report( let classes: ScopeClasses<'_> = ScopeClasses::new(py)?; let selected: Bound<'_, PyAny> = match filters.filter(|filters| !filters.is_none()) { Some(filters) => filters, - None => classes.model("ScopeReportFilters")?.call0()?, + None => classes.model(RecordId::ScopeReportFilters)?.call0()?, }; let reports: LookupReports = lookup_reports(lookup)?; let request: ReportRequest = ReportRequest { @@ -304,7 +303,7 @@ pub(crate) fn serialize_scope_report( match serialized(&classes, report) { Err(error) if error.is_instance_of::(py) => { let message: String = error.value(py).str()?.extract()?; - let raised: PyErr = scope_error(py, "ScopeError", message)?; + let raised: PyErr = scope_error(py, ErrorId::Scope, message)?; raised.set_cause(py, Some(error)); Err(raised) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs index e0aeede773..09e9c5c0d7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs @@ -8,6 +8,7 @@ use sqlbuild_scopes::scope_index::models::{ OwnershipRoot, OwnershipRootKind, ResourceIdentity, ResourceKind, ResourceRecord, }; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::scopes::scope_objects::{ ScopeClasses, enum_text, field, optional_item, read_resource, root_object, tuple_attribute, }; @@ -47,9 +48,9 @@ impl<'py> Diagnostics<'py> { let keywords: Bound<'py, PyDict> = PyDict::new(self.classes.py()); keywords.set_item("path", path.as_deref())?; self.objects - .push(self.classes.model("ScopeDiagnostic")?.call( + .push(self.classes.model(RecordId::ScopeDiagnostic)?.call( ( - self.classes.member("ScopeDiagnosticCode", code)?, + self.classes.member(RecordId::ScopeDiagnosticCode, code)?, message.as_str(), ), Some(&keywords), @@ -112,9 +113,9 @@ fn resource_object<'py>( classes: &ScopeClasses<'py>, record: &ResourceRecord, ) -> PyResult> { - classes.model("ResourceRecord")?.call1(( - classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", record.identity.kind.as_str())?, + classes.model(RecordId::ResourceRecord)?.call1(( + classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, record.identity.kind.as_str())?, record.identity.name.as_str(), ))?, record.path.as_str(), @@ -212,7 +213,7 @@ pub(crate) fn scope_tolerant_index( declaration_faults.is_empty() && config_faults.is_empty() && macro_faults.is_empty(); let relationships: bool = snapshot_relationship_faults.is_empty() && relationship_faults.is_empty(); - let completeness: Bound<'_, PyAny> = classes.model("ScopeCompleteness")?.call1(( + let completeness: Bound<'_, PyAny> = classes.model(RecordId::ScopeCompleteness)?.call1(( false, static_visibility, false, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs index 292be2f619..905be51f19 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs @@ -14,6 +14,8 @@ use sqlbuild_scopes::placement::models::{ use sqlbuild_scopes::scope_index::models::ResourceIdentity; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::compile_usages::{ MergeRows, SourceRow, UsageRow, merged_usage_rows, model_schema_scope_usages, }; @@ -25,8 +27,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ use crate::bindings::types::CompilerDetach; const DATACLASSES_MODULE: &str = "dataclasses"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; const RUNTIME_USAGE: usize = 2; /// Compile input groups whose items carry declaration usages, after the model inputs. const COMPILE_USAGE_GROUPS: [&str; 5] = [ @@ -219,7 +219,7 @@ fn placement_validated_index<'py>( } let classes: ScopeClasses<'py> = ScopeClasses::new(py)?; let severity: Bound<'py, PyAny> = classes.member( - "ScopeDiagnosticSeverity", + RecordId::ScopeDiagnosticSeverity, if enforce_placement { "error" } else { @@ -231,7 +231,7 @@ fn placement_validated_index<'py>( .compiler_detach(|| Ok(placement_diagnostics(&placement))) .map_err(compiler_error)?; let records: Bound<'py, PyTuple> = tuple_attribute(index, "declarations")?; - let diagnostic_class: Bound<'py, PyAny> = classes.model("ScopeDiagnostic")?; + let diagnostic_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeDiagnostic)?; for item in found { let record: Bound<'py, PyAny> = records.get_item(item.declaration)?; let native = &placement.declarations[item.declaration]; @@ -243,7 +243,7 @@ fn placement_validated_index<'py>( message: item.message.clone(), }); kept.push(diagnostic_class.call1(( - classes.member("ScopeDiagnosticCode", item.code.as_str())?, + classes.member(RecordId::ScopeDiagnosticCode, item.code.as_str())?, item.message, &severity, record.getattr("path")?, @@ -315,7 +315,10 @@ pub(crate) fn scope_relocated_declarations( &records.get_item(item.declaration)?, &[ ("path", item.path.into_bound_py_any(py)?), - ("scope", classes.member("ScopeKind", item.scope.as_str())?), + ( + "scope", + classes.member(RecordId::ScopeKind, item.scope.as_str())?, + ), ( "ownership_root", root_object(&classes, &item.ownership_root)?, @@ -377,10 +380,8 @@ pub(crate) fn scope_index_with_compile_usages<'py>( #[pyo3(signature = (*, index))] pub(crate) fn validate_scope_index(index: &Bound<'_, PyAny>) -> Result<(), PyErr> { let py: Python<'_> = index.py(); - let error: Bound<'_, PyAny> = py - .import(SCOPE_TYPES_MODULE)? - .getattr("ScopeDiagnosticSeverity")? - .getattr("ERROR")?; + let error: Bound<'_, PyAny> = + registry::class(py, RecordId::ScopeDiagnosticSeverity)?.getattr("ERROR")?; let mut errors: Vec> = Vec::new(); for item in index.getattr("diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; @@ -400,9 +401,7 @@ pub(crate) fn validate_scope_index(index: &Bound<'_, PyAny>) -> Result<(), PyErr let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("diagnostics", PyTuple::new(py, errors)?)?; Err(PyErr::from_value( - py.import(SCOPE_ERRORS_MODULE)? - .getattr("ScopeValidationError")? - .call((message,), Some(&options))?, + registry::error(py, ErrorId::ScopeValidation)?.call((message,), Some(&options))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs index 3d77e5e4dc..6ef152b40c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs @@ -10,6 +10,8 @@ use sqlbuild_scopes::scope_index::models::{ OwnershipRoot, OwnershipRootKind, RejectedPath, ResourceIdentity, ResourceKind, ResourceRecord, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::{ NativeScopeLookup, ResolvedRows, TargetRows, path_message, scope_path_error, }; @@ -19,7 +21,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ type PyObject = Py; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const CURRENT_PATH: &str = "."; fn native_lookup(lookup: &Bound<'_, PyAny>) -> PyResult> { @@ -33,8 +34,8 @@ fn target_query<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = classes.py(); - let identity: bool = target.is_instance(&classes.model("ResourceIdentity")?)? - || target.is_instance(&classes.model("DeclarationIdentity")?)?; + let identity: bool = target.is_instance(&classes.model(RecordId::ResourceIdentity)?)? + || target.is_instance(&classes.model(RecordId::DeclarationIdentity)?)?; let raw: String = if identity { String::new() } else { @@ -49,9 +50,7 @@ fn target_query<'py>( native_lookup(lookup)?.get().target(row, &raw)?; if let Some(message) = identity_error { return Err(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr("InvalidQualifiedIdentityError")? - .call1((message,))?, + registry::error(py, ErrorId::InvalidQualifiedIdentity)?.call1((message,))?, )); } if let Some(message) = path_error { @@ -86,7 +85,7 @@ fn target_query<'py>( matched, )?; classes - .model("ScopeTargetQuery")? + .model(RecordId::ScopeTargetQuery)? .call((value,), Some(&keywords)) } @@ -149,8 +148,8 @@ fn resolved_visibility( tuple_attribute(&lookup.getattr("visibility_index")?, "identities")?; let declarations: Bound<'_, PyTuple> = tuple_attribute(&index, "declarations")?; let grants: Bound<'_, PyTuple> = tuple_attribute(&index, "grants")?; - let visible_class: Bound<'_, PyAny> = classes.model("VisibilityRecord")?; - let inaccessible_class: Bound<'_, PyAny> = classes.model("InaccessibleRecord")?; + let visible_class: Bound<'_, PyAny> = classes.model(RecordId::VisibilityRecord)?; + let inaccessible_class: Bound<'_, PyAny> = classes.model(RecordId::InaccessibleRecord)?; let mut visible: Vec> = Vec::new(); let mut inaccessible: Vec> = Vec::new(); for resource in tuple_attribute(&query, "matches")?.iter() { @@ -164,7 +163,7 @@ fn resolved_visibility( visible.push(visible_class.call1(( &owner, identities.get_item(position)?, - classes.member("VisibilityReason", reason)?, + classes.member(RecordId::VisibilityReason, reason)?, through, ))?); } @@ -175,17 +174,17 @@ fn resolved_visibility( inaccessible_class.call1(( &owner, identity, - classes.member("InaccessibleReason", reason)?, + classes.member(RecordId::InaccessibleReason, reason)?, ))? } else { identity }); } } - let result: &str = if with_reasons { - "VisibilityResolution" + let result: RecordId = if with_reasons { + RecordId::VisibilityResolution } else { - "DeclarationVisibility" + RecordId::DeclarationVisibility }; Ok(classes .model(result)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs index 2e907c9cca..4b5ecca39c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs @@ -16,17 +16,12 @@ use sqlbuild_core::seed_command::models::SeedRow; use crate::bindings::_helpers::command_runtime::invocation::resolve_command_invocation; use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hooks; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const SEED_PLAN_MODULE: &str = "sqlbuild._native"; -const SEED_STATE_MODULE: &str = "sqlbuild._native"; -const RUNTIME_CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; const SUCCESS_STATUS: &str = "success"; const FAILED_STATUS: &str = "failed"; const EXECUTION_COMMAND: &str = "sqb seed"; @@ -181,7 +176,7 @@ fn compile_seeds<'py>( let connection_config: Bound<'py, PyAny> = invocation.getattr("connection_config")?; let discovered: Bound<'py, PyAny> = invocation.getattr("discovered_inputs")?; let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream.clone()), @@ -191,18 +186,14 @@ fn compile_seeds<'py>( stream.call_method1("write", ("\n",))?; stream.call_method0("flush")?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("select", request.getattr("select")?), @@ -213,7 +204,7 @@ fn compile_seeds<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -235,7 +226,7 @@ fn compile_seeds<'py>( } else { let relay: Bound<'py, SeedConnectionRelay> = connection_relay(&connection_progress)?; let hooks: Bound<'py, PyAny> = call_with( - &imported(py, RUNTIME_CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_connection_start", @@ -251,7 +242,7 @@ fn compile_seeds<'py>( let connection: Bound<'py, PyAny> = connection_with_hooks(&adapter, &connection_config, &hooks)?; let read: PyResult> = call_with( - &imported(py, SEED_STATE_MODULE, "read_selected_seed_fingerprints")?, + ®istry::native(py, NativeAttr::ReadSelectedSeedFingerprints)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -264,7 +255,7 @@ fn compile_seeds<'py>( read }; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, SEED_PLAN_MODULE, "build_seed_command_plan")?, + ®istry::native(py, NativeAttr::BuildSeedCommandPlan)?, &[ ("project", project), ("scope", scope), @@ -273,7 +264,7 @@ fn compile_seeds<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -306,7 +297,7 @@ fn write_header( header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", use_color)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&header))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; Ok(()) } @@ -328,9 +319,9 @@ fn load_seeds<'py>( .map(|(index, name)| (name.clone(), index + 1)) .collect(); let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; let projector: Option> = if projector.is_none() { None } else { @@ -360,7 +351,7 @@ fn load_seeds<'py>( }, )?; let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream), @@ -374,7 +365,7 @@ fn load_seeds<'py>( let relay: Bound<'py, SeedConnectionRelay> = connection_relay(&connection_progress)?; let loaded: PyResult>> = (|| { call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_seed_pipeline")?, + &host::entry(py, ExecutorHost::RunSeedPipeline)?, &[ ("plan", plan), ( @@ -416,7 +407,7 @@ fn load_seeds<'py>( #[pyo3(signature = (*, request))] fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = - resolve_command_invocation(request, &imported(py, NATIVE_MODULE, "SeedInvocation")?)?; + resolve_command_invocation(request, ®istry::class(py, RecordId::SeedInvocation)?)?; let pipeline: Bound<'_, PyAny> = compile_seeds(request, &invocation)?; let requested: Bound<'_, PyAny> = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { @@ -456,14 +447,14 @@ fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult )?; stream.call_method0("flush")?; let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_seed_execution_json")?, + ®istry::native(py, NativeAttr::FormatSeedExecutionJson)?, &[ ("results", PyTuple::new(py, &results)?.into_any()), ("plan", pipeline.getattr("plan_output")?), ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs index 0c06652ffe..762df3d046 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs @@ -7,13 +7,12 @@ use crate::bindings::_helpers::analysis::binding_catalog::ignores_quoted_case; use crate::bindings::_helpers::analysis::schema_request::{ SchemaRequestParts, schema_validation_request, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_checks::resource_sql::resource_sql_diagnostics_of; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; use crate::bindings::models::ProjectCatalog; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; - /// The column names a metadata config value lists: a name, or the names of a list or tuple. pub(crate) fn column_names(value: &Bound<'_, PyAny>) -> PyResult> { if value.is_instance_of::() { @@ -57,9 +56,7 @@ pub(crate) fn sql_test_columns<'py>( if bodies.is_empty() { return Ok(columns); } - let query_class: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeColumnQuery")?; + let query_class: Bound<'py, PyAny> = registry::class(py, RecordId::NativeColumnQuery)?; let mut queries: Vec> = Vec::with_capacity(bodies.len()); for sql in bodies.keys() { let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -71,10 +68,8 @@ pub(crate) fn sql_test_columns<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("queries", PyTuple::new(py, queries)?)?; arguments.set_item("profile", profile)?; - let analyses: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("infer_native_query_columns")? - .call((), Some(&arguments))?; + let analyses: Bound<'py, PyAny> = + registry::native(py, NativeAttr::InferNativeQueryColumns)?.call((), Some(&arguments))?; for (sql, analysis) in bodies.keys().iter().zip(analyses.try_iter()?) { let analysis: Bound<'py, PyAny> = analysis?; let mut names: Vec> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs index 974a648c5e..2b743d310b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs @@ -14,12 +14,11 @@ use sqlbuild_analysis::semantic_checks::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; use crate::bindings::types::CompilerDetach; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const SQL_ANALYSIS_CONFIG_KEY: &str = "sql_analysis"; const MODEL_HOOK_KEYS: [&str; 2] = ["pre_hooks", "post_hooks"]; const BUILT_IN_AUDIT_ROOT: &str = ""; @@ -53,12 +52,8 @@ pub(crate) fn resource_sql_diagnostics_of<'py>( Err(failure) => return Err(failure_error(py, failure)?), }; let classes = DiagnosticClasses { - compiler_diagnostic: py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?, - source_location: py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?, + compiler_diagnostic: registry::class(py, RecordId::CompilerDiagnostic)?, + source_location: registry::class(py, RecordId::SourceLocation)?, path: py.import("pathlib")?.getattr("Path")?, }; let built: Vec> = diagnostics @@ -74,9 +69,7 @@ fn request( profile: &Bound<'_, PyAny>, ) -> PyResult { let py: Python<'_> = project.py(); - let reference_class: Bound<'_, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompileSqlReference")?; + let reference_class: Bound<'_, PyAny> = registry::class(py, RecordId::CompileSqlReference)?; let mut audits: Vec = Vec::new(); for audit in project.getattr("audits")?.try_iter()? { audits.push(audit_input(&audit?, &reference_class)?); @@ -331,11 +324,9 @@ fn python_diagnostic<'py>( fn failure_error(py: Python<'_>, failure: ResourceSqlFailure) -> PyResult { Ok(match failure { - ResourceSqlFailure::CursorIntrinsic(message) => PyErr::from_value( - py.import(COMPILE_ERRORS_MODULE)? - .getattr("CompileInputError")? - .call1((message,))?, - ), + ResourceSqlFailure::CursorIntrinsic(message) => { + PyErr::from_value(registry::error(py, ErrorId::CompileInput)?.call1((message,))?) + } ResourceSqlFailure::Normalization(message) | ResourceSqlFailure::Internal(message) => { compiler_error(message) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs index 7c2a2137b4..183d6014c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs @@ -1,22 +1,17 @@ //! The Python models, enums and compile helpers the semantic completion stage reads and builds. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const UNNEEDED_SQL_ANALYSIS_OPT_OUT_CODE: &str = "P009"; const SQL_ANALYSIS_CONFIG_KEY: &str = "sql_analysis"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const PROJECT_WITH_MODULE: &str = "sqlbuild._native"; -const TYPE_RECOVERY_MODULE: &str = "sqlbuild._native"; -const BINDING_DIAGNOSTICS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const NORMALIZE_TYPE_MODULE: &str = "sqlbuild.adapter.type_system.main.normalize_type"; const DATACLASSES_MODULE: &str = "dataclasses"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; /// Imported once per stage run. pub(crate) struct StageClasses<'py> { @@ -43,20 +38,16 @@ pub(crate) struct StageClasses<'py> { impl<'py> StageClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let models = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; - let severity = shape_class(types.as_any(), "DiagnosticSeverity")?; + let severity = registry::class(types.py(), RecordId::DiagnosticSeverity)?; let resource = types.getattr("CompiledResourceType")?; - let kinds = py - .import(REFERENCE_TYPES_MODULE)? - .getattr("SqlReferenceKind")?; + let kinds = registry::class(py, RecordId::SqlReferenceKind)?; Ok(Self { - diagnostic: models.getattr("CompilerDiagnostic")?, - location: py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?, - model_test_payload: shape_class(models.as_any(), "CompiledModelSqlTestPayload")?, - compile_phase: shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?, + diagnostic: registry::class(py, RecordId::CompilerDiagnostic)?, + location: registry::class(py, RecordId::SourceLocation)?, + model_test_payload: registry::class(py, RecordId::CompiledModelSqlTestPayload)?, + compile_phase: registry::class(types.py(), RecordId::DiagnosticPhase)? + .getattr("COMPILE")?, error: severity.getattr("ERROR")?, warning: severity.getattr("WARNING")?, model_resource: resource.getattr("MODEL")?, @@ -64,17 +55,11 @@ impl<'py> StageClasses<'py> { sql_test_resource: resource.getattr("SQL_TEST")?, opt_out_code: PyString::new(py, UNNEEDED_SQL_ANALYSIS_OPT_OUT_CODE).into_any(), replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, - project_with: py.import(PROJECT_WITH_MODULE)?.getattr("project_with")?, - with_type_recovery: py - .import(TYPE_RECOVERY_MODULE)? - .getattr("with_type_recovery")?, - with_binding_diagnostics: py - .import(BINDING_DIAGNOSTICS_MODULE)? - .getattr("with_binding_diagnostics")?, - schema_validations: py.import(NATIVE_MODULE)?.getattr("schema_validations")?, - normalize_type: py - .import(NORMALIZE_TYPE_MODULE)? - .getattr("normalize_type")?, + project_with: registry::native(py, NativeAttr::ProjectWith)?, + with_type_recovery: registry::native(py, NativeAttr::WithTypeRecovery)?, + with_binding_diagnostics: registry::native(py, NativeAttr::WithBindingDiagnostics)?, + schema_validations: registry::native(py, NativeAttr::SchemaValidations)?, + normalize_type: host::entry(py, AdapterHost::NormalizeType)?, sql_analysis_key: PyString::new(py, SQL_ANALYSIS_CONFIG_KEY).into_any(), udf_kind: kinds.getattr("UDF")?, table_function_kind: kinds.getattr("TABLE_FUNCTION")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs index 468a2995fe..469b5c98eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs @@ -8,10 +8,11 @@ use crate::bindings::_helpers::analysis::binding_catalog::{ BindingCatalog, CASE_SENSITIVE_BINDING_DIALECTS, }; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const UNKNOWN: &str = "UNKNOWN"; const GENERIC_DIALECT: &str = "generic"; @@ -72,10 +73,8 @@ pub(crate) fn published_shape<'py>( let py: Python<'py> = model.py(); let shape: Bound<'py, PyDict> = binding_shape(model, columns, inputs, profile)?; let values: Bound<'py, PyAny> = model.getattr("config")?.getattr("values")?; - let snapshot: Bound<'py, PyAny> = py - .import(PLANNER_TYPES_MODULE)? - .getattr("MaterializationType")? - .getattr("SNAPSHOT")?; + let snapshot: Bound<'py, PyAny> = + registry::class(py, RecordId::MaterializationType)?.getattr("SNAPSHOT")?; if values .call_method1("get", ("materialized",))? .eq(snapshot)? @@ -133,10 +132,8 @@ fn binding_shape<'py>( request.set_item("dialect", or_generic(&dialect)?)?; request.set_item("stubs", PyDict::new(py))?; request.set_item("placeholders", PyDict::new(py))?; - let sql: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("normalize_analysis_sql")? - .call1((request,))?; + let sql: Bound<'py, PyAny> = + registry::native(py, NativeAttr::NormalizeAnalysisSql)?.call1((request,))?; let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("sql", sql)?; arguments.set_item("columns", columns)?; @@ -201,9 +198,7 @@ pub(crate) fn project_profile<'py>( "sql_analysis_dialect", project.getattr("sql_analysis_dialect")?, )?; - py.import(ADAPTER_MODELS_MODULE)? - .getattr("ExpressionInferenceProfile")? - .call((), Some(&arguments))? + host::entry(py, AdapterHost::ExpressionInferenceProfile)?.call((), Some(&arguments))? } }; let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -241,10 +236,8 @@ pub(crate) fn project_shapes<'py>( shapes.set_item(format!("{TABLE_FUNCTION_PREFIX}{name}"), shape)?; } } - let enforced: Bound<'py, PyAny> = py - .import(PLANNER_TYPES_MODULE)? - .getattr("ContractPolicy")? - .getattr("ENFORCED")?; + let enforced: Bound<'py, PyAny> = + registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; let mut expression_names: Vec> = Vec::new(); let mut expressions: Vec> = Vec::new(); for source in project.getattr("sources")?.try_iter()? { @@ -266,10 +259,9 @@ pub(crate) fn project_shapes<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("expressions", PyTuple::new(py, expressions)?)?; arguments.set_item("profile", &profile)?; - let source_shapes: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("infer_native_expression_source_shapes")? - .call((), Some(&arguments))?; + let source_shapes: Bound<'py, PyAny> = + registry::native(py, NativeAttr::InferNativeExpressionSourceShapes)? + .call((), Some(&arguments))?; for (name, shape) in expression_names.iter().zip(source_shapes.try_iter()?) { let shape: Bound<'py, PyAny> = shape?; if !shape.is_none() { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs index f05b78d6f4..976de6687d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -15,6 +14,8 @@ use crate::bindings::_helpers::analysis::schema_request::{ }; use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_sql; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; use crate::bindings::_helpers::semantic_shapes::shapes::{ model_inputs, project_shapes, published_shape, @@ -22,9 +23,6 @@ use crate::bindings::_helpers::semantic_shapes::shapes::{ const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const BINDING_UNKNOWN_TABLE_INTERNAL_CODE: &str = "B000"; const DATACLASSES_MODULE: &str = "dataclasses"; @@ -157,16 +155,12 @@ fn rebound_models<'py>( .getattr("TopologicalSorter")? .call1((graph,))? .call_method0("static_order")?; - let query_class: Object<'py> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeColumnQuery")?; + let query_class: Object<'py> = registry::class(py, RecordId::NativeColumnQuery)?; let unknown: Object<'py> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")? .getattr("UNKNOWN")?; - let infer: Object<'py> = py - .import(NATIVE_MODULE)? - .getattr("infer_native_query_columns")?; + let infer: Object<'py> = registry::native(py, NativeAttr::InferNativeQueryColumns)?; let mut affected: HashSet = HashSet::new(); let mut models: Vec> = Vec::new(); for name in order.try_iter()? { @@ -307,11 +301,11 @@ fn validated_diagnostics<'py>( expansions.set_item(path.getattr("stem")?, expansion)?; } let skipped: Object<'py> = PyString::new(py, BINDING_UNKNOWN_TABLE_INTERNAL_CODE).into_any(); - let models_module = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; - let diagnostic_class: Object<'py> = models_module.getattr("CompilerDiagnostic")?; - let severity_class: Object<'py> = shape_class(types.as_any(), "DiagnosticSeverity")?; - let phase: Object<'py> = shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?; + let diagnostic_class: Object<'py> = registry::class(py, RecordId::CompilerDiagnostic)?; + let severity_class: Object<'py> = registry::class(types.py(), RecordId::DiagnosticSeverity)?; + let phase: Object<'py> = + registry::class(types.py(), RecordId::DiagnosticPhase)?.getattr("COMPILE")?; let model_type: Object<'py> = types.getattr("CompiledResourceType")?.getattr("MODEL")?; let mut diagnostics: Vec> = Vec::new(); for ((model, request), result) in models.iter().zip(&requests).zip(results.try_iter()?) { @@ -395,9 +389,7 @@ fn positions<'py>( request.set_item("expanded", expanded)?; request.set_item("cleaned", cleaned)?; request.set_item("passes", passes)?; - py.import(NATIVE_MODULE)? - .getattr("BindingPositions")? - .call1((request,)) + registry::native(py, NativeAttr::BindingPositions)?.call1((request,)) } /// Whether inspected columns differ from the known shape; untyped names match any type. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs index f9711d0da0..4019dbc047 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs @@ -25,22 +25,15 @@ use sqlbuild_core::test_command::models::{ use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_command_invocation; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::compile_outputs::runtime_target::write_test_runtime_target; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::integration_values::json_value; use crate::bindings::_helpers::result_output::resource_ids::sql_test_identity; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const TEST_PLAN_MODULE: &str = "sqlbuild._native"; -const PLANNER_ERRORS: &str = "sqlbuild._native"; -const EXECUTOR_PIPELINE_MODELS: &str = "sqlbuild.executor.pipeline.models"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; const DECIMAL_KIND: &str = "decimal"; const PASS_OUTCOME: &str = "pass"; const EXPECT_LABEL: &str = "expect"; @@ -153,9 +146,9 @@ fn outcome_status(outcome: &Bound<'_, PyAny>) -> PyResult<&'static str> { /// Nested `expect` rows of a completed test, one per step. fn expectation_rows<'py>(result: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = result.py(); - let row_class: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NestedProgressChildRow")?; - let name: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "format_expectation_name")?; - let detail: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "format_expectation_detail")?; + let row_class: Bound<'py, PyAny> = registry::class(py, RecordId::NestedProgressChildRow)?; + let name: Bound<'py, PyAny> = registry::native(py, NativeAttr::FormatExpectationName)?; + let detail: Bound<'py, PyAny> = registry::native(py, NativeAttr::FormatExpectationDetail)?; let mut rows: Vec> = Vec::new(); for step in result.getattr("step_results")?.try_iter()? { let step: Bound<'py, PyAny> = step?; @@ -296,7 +289,7 @@ fn unknown_case(py: Python<'_>, message: String) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", UNKNOWN_CASE_CODE)?; - imported(py, PLANNER_ERRORS, "PlannerInputError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::PlannerInput)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -313,18 +306,14 @@ fn compile_tests<'py>( let discovered: Bound<'py, PyAny> = invocation.getattr("discovered_inputs")?; let adapter: Bound<'py, PyAny> = invocation.getattr("adapter")?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -347,7 +336,7 @@ fn compile_tests<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -362,7 +351,7 @@ fn compile_tests<'py>( )?; let case_name: Bound<'py, PyAny> = request.getattr("case_name")?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, TEST_PLAN_MODULE, "build_test_command_plan")?, + ®istry::native(py, NativeAttr::BuildTestCommandPlan)?, &[ ("project", context.getattr("project")?), ("adapter", adapter), @@ -387,7 +376,7 @@ fn compile_tests<'py>( )); } call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -475,7 +464,7 @@ fn run_tests<'py>( } let worker_count: i64 = concurrency.min(i64::try_from(test_count).unwrap_or(i64::MAX)); let progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "NestedCommandProgressCallbacks")?, + ®istry::native(py, NativeAttr::NestedCommandProgressCallbacks)?, &[ ("total", PyInt::new(py, test_count).into_any()), ("label", text(py, "test")), @@ -489,7 +478,7 @@ fn run_tests<'py>( ], )?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { for entry in entries.try_iter()? { call_with( @@ -504,7 +493,7 @@ fn run_tests<'py>( )?; stream.call_method0("flush")?; let connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream.clone()), @@ -513,7 +502,7 @@ fn run_tests<'py>( )?; let preflight: Bound<'py, PyAny> = status_reporter(&stream, use_color.is_truthy()?)?; let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let relay: Bound<'py, TestCallbackRelay> = Bound::new( py, TestCallbackRelay { @@ -525,7 +514,7 @@ fn run_tests<'py>( )?; let executed: PyResult>> = (|| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_PIPELINE_MODELS, "TestPipelineCallbacks")?, + &host::entry(py, ExecutorHost::TestPipelineCallbacks)?, &[ ( "on_connection_start", @@ -545,7 +534,7 @@ fn run_tests<'py>( ], )?; call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_test_pipeline")?, + &host::entry(py, ExecutorHost::RunTestPipeline)?, &[ ("plan", plan_output), ( @@ -607,11 +596,11 @@ fn complete( stream.call_method0("flush")?; let tuple: Bound<'_, PyAny> = PyTuple::new(py, results)?.into_any(); let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_test_execution_json")?, + ®istry::native(py, NativeAttr::FormatTestExecutionJson)?, &[("results", tuple)], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -628,7 +617,7 @@ fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult let inspect_only: bool = request.getattr("inspect")?.is_truthy()?; let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - &imported(py, NATIVE_MODULE, "TestInvocation")?, + ®istry::class(py, RecordId::TestInvocation)?, !inspect_only, )?; let pipeline: Bound<'_, PyAny> = compile_tests(request, &invocation)?; @@ -653,7 +642,7 @@ fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", invocation.getattr("use_color")?)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&header))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; let results: Vec> = run_tests(&invocation, &pipeline, concurrency)?; write_test_runtime_target( ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs index f1b7ed40f4..618ed0562d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs @@ -12,10 +12,10 @@ use sqlbuild_analysis::compiler::models::{SqlTestExtractionFailure, SqlTestReque use sqlbuild_analysis::sql_test_attachment::main::authored_test_offset::authored_test_offset; use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; const MODEL_MODE: &str = "model"; /// Extract fixture bodies from authored test or scenario CTEs in authored order. @@ -81,10 +81,8 @@ fn located_error(py: Python<'_>, failure: &SqlTestExtractionFailure, body: &Auth let raised = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", failure.help.as_deref())?; - let error: Bound<'_, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs index fd84264b42..71b4cf56b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs @@ -1,19 +1,17 @@ //! Compiled SQL tests, their keyed P013 diagnostics and assembly failures, built natively. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::assembly::{ AssemblyRequest, AssemblyRow, DiagnosticRow, FactsRow, assemble_compiled_sql_tests, }; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const GLUE_MODELS_MODULE: &str = "sqlbuild._native"; -const SPEC_MODELS_MODULE: &str = "sqlbuild._native"; /// Fields a direct-logic compiled payload copies from its input payload, in order. const DIRECT_PAYLOAD_FIELDS: [&str; 5] = [ "mode", @@ -50,7 +48,6 @@ type Fields<'py> = Vec<(&'static str, Bound<'py, PyAny>)>; /// The Python classes a compiled test is built from. struct Classes<'py> { - models: Bound<'py, PyModule>, resource_type: Bound<'py, PyAny>, direct_input: Bound<'py, PyAny>, assembly: Bound<'py, PyAny>, @@ -63,33 +60,30 @@ struct Classes<'py> { impl<'py> Classes<'py> { fn new(py: Python<'py>) -> PyResult { - let models: Bound<'py, PyModule> = py.import(COMPILE_MODELS_MODULE)?; let types: Bound<'py, PyModule> = py.import(COMPILE_TYPES_MODULE)?; - let glue: Bound<'py, PyModule> = py.import(GLUE_MODELS_MODULE)?; Ok(Self { - direct_input: shape_class(&models, "CompileDirectLogicSqlTestInputPayload")?, + direct_input: registry::class(py, RecordId::CompileDirectLogicSqlTestInputPayload)?, resource_type: types.getattr("CompiledResourceType")?, - phase: shape_class(&types, "DiagnosticPhase")?.getattr("COMPILE")?, - severity: shape_class(&types, "DiagnosticSeverity")?.getattr("ERROR")?, - assembly: glue.getattr("NativeSqlTestAssembly")?, - failure: glue.getattr("NativeSqlTestFailure")?, - location: py.import(SPEC_MODELS_MODULE)?.getattr("SourceLocation")?, + phase: registry::class(py, RecordId::DiagnosticPhase)?.getattr("COMPILE")?, + severity: registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?, + assembly: registry::class(py, RecordId::NativeSqlTestAssembly)?, + failure: registry::class(py, RecordId::NativeSqlTestFailure)?, + location: registry::class(py, RecordId::SourceLocation)?, code: PyString::new(py, super::constants::SQL_TEST_HELPER_REFERENCE_CODE).into_any(), - models, }) } fn py(&self) -> Python<'py> { - self.models.py() + self.resource_type.py() } - fn model(&self, name: &str) -> PyResult> { - shape_class(&self.models, name) + fn model(&self, name: RecordId) -> PyResult> { + registry::class(self.py(), name) } fn object_key(&self, resource_type: &str, name: &str) -> PyResult> { keyword_call( - &self.model("CompiledObjectKey")?, + &self.model(RecordId::CompiledObjectKey)?, vec![ ("resource_type", self.resource_type.call1((resource_type,))?), ("name", text(self.py(), name)), @@ -131,10 +125,10 @@ fn compiled_payload<'py>( let py: Python<'py> = classes.py(); if payload.is_instance(&classes.direct_input)? { let compiled: Bound<'py, PyAny> = keyword_call( - &classes.model("CompiledDirectLogicSqlTestPayload")?, + &classes.model(RecordId::CompiledDirectLogicSqlTestPayload)?, copied(payload, &DIRECT_PAYLOAD_FIELDS)?, )?; - let resource_type: Bound<'py, PyAny> = classes.model("CompiledSqlTestResource")?; + let resource_type: Bound<'py, PyAny> = classes.model(RecordId::CompiledSqlTestResource)?; let mode: Bound<'py, PyAny> = payload.getattr("mode")?; let mut resources: Vec> = Vec::new(); for name in payload.getattr("tested_resource_names")?.try_iter()? { @@ -154,7 +148,7 @@ fn compiled_payload<'py>( copied(payload, &MODEL_PAYLOAD_FIELDS)?; payload_fields.push(("model_query_overrides", PyDict::new(py).into_any())); let compiled: Bound<'py, PyAny> = keyword_call( - &classes.model("CompiledModelSqlTestPayload")?, + &classes.model(RecordId::CompiledModelSqlTestPayload)?, payload_fields, )?; let mut fields: Vec<(&'static str, Bound<'py, PyAny>)> = copied(payload, &MODEL_TEST_FIELDS)?; @@ -190,7 +184,7 @@ fn keyed_diagnostic<'py>( ], )?; let diagnostic: Bound<'py, PyAny> = keyword_call( - &classes.model("CompilerDiagnostic")?, + &classes.model(RecordId::CompilerDiagnostic)?, vec![ ("phase", classes.phase.clone()), ("severity", classes.severity.clone()), @@ -249,7 +243,7 @@ fn compiled_test<'py>( ), ]; fields.extend(payload_fields); - let test: Bound<'py, PyAny> = keyword_call(&classes.model("CompiledSqlTest")?, fields)?; + let test: Bound<'py, PyAny> = keyword_call(&classes.model(RecordId::CompiledSqlTest)?, fields)?; let keyed: Vec> = diagnostics .into_iter() .map(|row| keyed_diagnostic(classes, (test_input, &resource_name), row)) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs index 463bd380c9..f23145f868 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs @@ -4,8 +4,6 @@ pub(super) const UDF: &str = "udf"; pub(super) const TABLE_FN: &str = "table_fn"; pub(super) const ERROR_SEVERITY: &str = "error"; pub(super) const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -pub(super) const PLAN_MODELS: &str = "sqlbuild._native"; -pub(super) const NATIVE_PLANNING: &str = "sqlbuild._native"; pub(super) const FIXTURE_PREFIXES: [(&str, &str); 4] = [ ("model", "__ref__"), ("source", "__source__"), @@ -13,7 +11,6 @@ pub(super) const FIXTURE_PREFIXES: [(&str, &str); 4] = [ ("sql_test", "__expected__"), ]; pub(super) const CURSOR_ALGEBRA_EXCEPTIONS: &str = "sqlbuild.cursor_algebra.exceptions"; -pub(super) const PLANNER_TYPES: &str = "sqlbuild._native"; pub(super) const INTEGER_CURSOR_TYPE: &str = "integer"; pub(super) const TIMESTAMP_CURSOR_TYPE: &str = "timestamp"; pub(super) const DEFAULT_TIMESTAMP_START: &str = "1900-01-01 00:00:00"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs index 6c1ae72432..872e7c9fc0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs @@ -15,10 +15,12 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::{parse_value, render_value}; use crate::bindings::_helpers::model_config::config_values::config_str; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::{ CURSOR_ALGEBRA_EXCEPTIONS, CURSOR_END_KEY, CURSOR_START_KEY, DEFAULT_INTEGER_END, DEFAULT_INTEGER_START, DEFAULT_TIMESTAMP_END, DEFAULT_TIMESTAMP_START, INTEGER_CURSOR_TYPE, - PLANNER_TYPES, TIMESTAMP_CURSOR_TYPE, + TIMESTAMP_CURSOR_TYPE, }; /// One model's cursor name, type and timestamp grain. @@ -237,13 +239,13 @@ fn model_cursor<'py>(model: &Bound<'py, PyAny>) -> PyResult> { .extract::() .is_ok_and(|value| !value.is_empty()) }); - let cursor_type = imported(py, PLANNER_TYPES, "CursorType")?.call1(( + let cursor_type = registry::class(py, RecordId::CursorType)?.call1(( declared.unwrap_or_else(|| PyString::new(py, TIMESTAMP_CURSOR_TYPE).into_any()), ))?; let type_name: String = cursor_type.getattr("value")?.extract()?; let grain = match config_str(&values, "cursor_grain")? { Some(grain) if type_name == TIMESTAMP_CURSOR_TYPE => { - let grain = imported(py, PLANNER_TYPES, "CursorGrain")?.call1((grain,))?; + let grain = registry::class(py, RecordId::CursorGrain)?.call1((grain,))?; let session = CursorSession::new(py)?; session .grain(&grain)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs index 0075483932..f88373bfe9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs @@ -3,10 +3,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; -use crate::bindings::_helpers::sql_test_glue::constants::NATIVE_PLANNING; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; #[pyfunction(name = "plan_sql_tests")] #[pyo3(signature = (**kwargs))] @@ -36,7 +35,7 @@ pub(crate) fn assemble_sql_test_plans<'py>( context = args.value("fixture_planning_context")?; if !context.is_truthy()? { context = call_with( - &imported(py, "sqlbuild._native", "build_relation_fixture_context")?, + ®istry::native(py, NativeAttr::BuildRelationFixtureContext)?, &[("project", project.clone())], )?; } @@ -50,7 +49,7 @@ pub(crate) fn assemble_sql_test_plans<'py>( .filter_map(|(index, test)| (!diagnostics.contains_key(&index)).then_some(test)) .collect(); let plans = call_with( - &imported(py, NATIVE_PLANNING, "plan_sql_tests_natively")?, + ®istry::native(py, NativeAttr::PlanSqlTestsNatively)?, &[ ("project", project.clone()), ("tests", PyTuple::new(py, plannable)?.into_any()), @@ -77,7 +76,7 @@ pub(crate) fn assemble_sql_test_plans<'py>( let model = model?; models.set_item(model.getattr("name")?, model)?; } - let result_class = imported(py, "sqlbuild._native", "SqlTestPlanResult")?; + let result_class = registry::class(py, RecordId::SqlTestPlanResult)?; let mut results: Vec> = Vec::new(); for (index, test) in tests.iter().enumerate() { if let Some(diagnostic) = diagnostics.get(&index) { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs index 4652f20271..a9095ed179 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs @@ -4,6 +4,8 @@ use crate::bindings::_helpers::compile_driver::compile_timings::add_compile_timi use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::plan_values::tuple; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python, pyfunction}; use pyo3::types::{PyBool, PyDict, PyTuple}; @@ -17,7 +19,7 @@ pub(super) fn chains<'py>( return Ok(PyTuple::empty(py).into_any()); } let request = call_with( - &imported(py, "sqlbuild._native", "NativeSqlTestChainRequest")?, + ®istry::class(py, RecordId::NativeSqlTestChainRequest)?, &[ ("models", project.getattr("models")?), ("tests", tests.clone()), @@ -29,8 +31,7 @@ pub(super) fn chains<'py>( ), ], )?; - let native = - imported(py, "sqlbuild._native", "resolve_compiled_sql_test_chains")?.call1((request,)); + let native = registry::native(py, NativeAttr::ResolveCompiledSqlTestChains)?.call1((request,)); let native = match native { Ok(value) => value, Err(error) if error.is_instance_of::(py) => { @@ -75,7 +76,7 @@ fn planned<'py>(args: &KeywordArguments<'py>, artifact: bool) -> PyResult(args: &KeywordArguments<'py>, artifact: bool) -> PyResult value, Err(error) if error.is_instance_of::(py) => { @@ -151,12 +152,12 @@ fn planned<'py>(args: &KeywordArguments<'py>, artifact: bool) -> PyResult(test: &Bound<'py, PyAny>, prefix: &str) -> PyResult> { @@ -124,7 +126,7 @@ pub(super) fn entry<'py>( fields.set_item("chain", plan.getattr("chain")?)?; fields.set_item("assertions", plan.getattr("assertions")?)?; fields.set_item("sql_analysis_enabled", analysis)?; - imported(py, PLAN_MODELS, "SqlTestPlanEntry")?.call((), Some(&fields)) + registry::class(py, RecordId::SqlTestPlanEntry)?.call((), Some(&fields)) } pub(super) fn expected_diagnostics( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs index 043e11832f..86881e4082 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs @@ -1,14 +1,14 @@ //! Project native planning failures into existing located diagnostic exceptions. use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::plan_values::string; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_analysis::sql_test_attachment::main::sql_test_cte_location::sql_test_cte_location; use sqlbuild_analysis::sql_test_attachment::models::CteLocation; -const SOURCE_LOCATION_MODULE: &str = "sqlbuild._native"; - /// The location of `call` inside one CTE of a SQL test block, else the CTE header, else the block. fn cte_call_location<'py>( py: Python<'py>, @@ -32,7 +32,7 @@ fn cte_call_location<'py>( arguments.set_item("column", located.column)?; arguments.set_item("end_line", located.end_line)?; arguments.set_item("end_column", located.end_column)?; - imported(py, SOURCE_LOCATION_MODULE, "SourceLocation")?.call((), Some(&arguments)) + registry::class(py, RecordId::SourceLocation)?.call((), Some(&arguments)) } pub(super) fn planning_error( @@ -73,27 +73,26 @@ pub(super) fn planning_error( kwargs.set_item("help", help)?; let class = if let Some(test) = found { kwargs.set_item("location", cte_call_location(py, &test, (&cte, &call))?)?; - "SqlTestReferenceError" + ErrorId::SqlTestReference } else { kwargs.set_item("code", super::constants::SQL_TEST_HELPER_REFERENCE_CODE)?; - "CompileInputError" + ErrorId::CompileInput }; return Ok(PyErr::from_value( - imported(py, "sqlbuild._native", class)?.call((message,), Some(&kwargs))?, + registry::error(py, class)?.call((message,), Some(&kwargs))?, )); } - let (module, class, message) = if let Some(message) = message.strip_prefix("compile_input:") { - ("sqlbuild._native", "CompileInputError", message.to_owned()) + let (class, message) = if let Some(message) = message.strip_prefix("compile_input:") { + (ErrorId::CompileInput, message.to_owned()) } else if let Some(message) = message.strip_prefix("planner_input:") { - ("sqlbuild._native", "PlannerInputError", message.to_owned()) + (ErrorId::PlannerInput, message.to_owned()) } else { ( - "sqlbuild._native", - "NativeSqlTestPlanningError", + ErrorId::NativeSqlTestPlanning, format!("native SQL-test planning failed: {message}"), ) }; Ok(PyErr::from_value( - imported(py, module, class)?.call1((string(py, &message),))?, + registry::error(py, class)?.call1((string(py, &message),))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs index df8734947d..78a9c510a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs @@ -4,8 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::authored_ctes::prefixed_ctes; -use crate::bindings::_helpers::sql_test_glue::constants::NATIVE_PLANNING; use crate::bindings::_helpers::sql_test_glue::fixture_validation::validated_fixtures; use crate::bindings::_helpers::sql_test_glue::plan_values::model_payload; @@ -92,7 +93,7 @@ pub(super) fn validate<'py>( return Ok((tests.to_vec(), Default::default())); } let chains = call_with( - &imported(py, NATIVE_PLANNING, "resolve_sql_test_model_chains")?, + ®istry::native(py, NativeAttr::ResolveSqlTestModelChains)?, &[ ("project", inputs.0.clone()), ("tests", PyTuple::new(py, model_tests)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs index 1a4a6e7599..350760a6b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs @@ -1,10 +1,12 @@ //! Adapter rendering and declared cursor-window boundaries for native test planning. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::sql_test_glue::plan_values::{model_payload, string}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::SENTINEL; use crate::bindings::_helpers::sql_test_glue::cursor_window::{ declared_window_cursor_models, declares_cursor_window, render_test_cursor_intrinsics, @@ -160,7 +162,7 @@ pub(super) fn functions<'py>( .filter(|(_, suffix)| !suffix.contains(SENTINEL)) else { return Err(PyErr::from_value( - imported(py, "sqlbuild._native", "NativeSqlTestPlanningError")? + registry::error(py, ErrorId::NativeSqlTestPlanning)? .call1(("adapter SQL function call template is not deterministic",))?, )); }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs index a428435e0c..9976e742c1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs @@ -1,8 +1,8 @@ //! Materialize native plan rows as the existing executor-facing dataclasses. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; -use crate::bindings::_helpers::sql_test_glue::constants::PLAN_MODELS; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::plan_values::tuple; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -36,13 +36,12 @@ fn step<'py>(row: &Bound<'py, PyAny>, chain: bool) -> PyResult }, )?; } - shape_class_at( + registry::class( py, - PLAN_MODELS, if chain { - "ChainStep" + RecordId::ChainStep } else { - "SqlTestAssertionStep" + RecordId::SqlTestAssertionStep }, )? .call((), Some(&kwargs)) @@ -59,11 +58,11 @@ pub(super) fn plan<'py>(row: &Bound<'py, PyAny>) -> PyResult> assertions.push(step(&value?, false)?); } let mut warnings: Vec> = Vec::new(); - let severity = imported(py, "sqlbuild._native", "WarningSeverity")?; + let severity = registry::class(py, RecordId::WarningSeverity)?; for value in row.get_item(4)?.try_iter()? { let value = value?; warnings.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", value.get_item(0)?), ("severity", severity.call1((value.get_item(1)?,))?), @@ -72,7 +71,7 @@ pub(super) fn plan<'py>(row: &Bound<'py, PyAny>) -> PyResult> )?); } call_with( - &imported(py, "sqlbuild._native", "NativeSqlTestPlan")?, + ®istry::class(py, RecordId::NativeSqlTestPlan)?, &[ ("chain", PyTuple::new(py, chain)?.into_any()), ("assertions", PyTuple::new(py, assertions)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs index dcdcf61198..970a81e3e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs @@ -4,18 +4,17 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; pub(super) fn string<'py>(py: Python<'py>, value: &str) -> Bound<'py, PyAny> { PyString::new(py, value).into_any() } pub(super) fn model_payload(test: &Bound<'_, PyAny>) -> PyResult { - test.getattr("payload")?.is_instance(&imported( + test.getattr("payload")?.is_instance(®istry::class( test.py(), - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", + RecordId::CompiledModelSqlTestPayload, )?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs index 77659c1912..6a7f5ed88e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs @@ -1,14 +1,12 @@ //! Authored header values as the Python header objects: scalars, sets, constants and hooks. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{IntoPyObject, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; use sqlbuild_sqltext::compiler::main::header_word_value::header_word_value; use sqlbuild_sqltext::compiler::models::HeaderWord; -const SQL_VALUES_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; - fn word_to_python(py: Python<'_>, word: String) -> PyResult> { match header_word_value(&word) { HeaderWord::Bool(value) => Ok(value.into_pyobject(py)?.to_owned().unbind().into_any()), @@ -28,10 +26,10 @@ fn word_to_python(py: Python<'_>, word: String) -> PyResult> { fn instance( py: Python<'_>, - (module, class): (&str, &str), + class: RecordId, arguments: &pyo3::Bound<'_, PyDict>, ) -> PyResult> { - Ok(shape_class_at(py, module, class)? + Ok(registry::class(py, class)? .call((), Some(arguments))? .unbind()) } @@ -60,7 +58,7 @@ fn constant_call( .collect::>>()?; let keywords = PyDict::new(py); keywords.set_item("arguments", PyTuple::new(py, arguments)?)?; - instance(py, (SQL_VALUES_MODULE, "AuthoredSqlValueCall"), &keywords) + instance(py, RecordId::AuthoredSqlValueCall, &keywords) } fn authored_value_to_python( @@ -87,17 +85,21 @@ fn authored_value_to_python( AuthoredValue::Set(values) => { let keywords = PyDict::new(py); keywords.set_item("values", values_tuple(py, values)?)?; - instance(py, (SQL_VALUES_MODULE, "AuthoredSqlSet"), &keywords) + instance(py, RecordId::AuthoredSqlSet, &keywords) } AuthoredValue::Tuple(values) => values_tuple(py, values), AuthoredValue::TypedConstant(values) => constant_call(py, values), AuthoredValue::InlineSqlHook(statement) => { let keywords = PyDict::new(py); keywords.set_item("statement", statement)?; - instance(py, (DISCOVERY_MODELS_MODULE, "SqlHookEntry"), &keywords) + instance(py, RecordId::SqlHookEntry, &keywords) + } + AuthoredValue::NamedSqlHook(name, kwargs) => { + hook(py, RecordId::NamedSqlHookEntry, name, kwargs) + } + AuthoredValue::PythonHook(name, kwargs) => { + hook(py, RecordId::PythonHookEntry, name, kwargs) } - AuthoredValue::NamedSqlHook(name, kwargs) => hook(py, "NamedSqlHookEntry", name, kwargs), - AuthoredValue::PythonHook(name, kwargs) => hook(py, "PythonHookEntry", name, kwargs), } } @@ -114,14 +116,14 @@ pub(crate) fn map_to_python( fn hook( py: Python<'_>, - class: &str, + class: RecordId, name: String, kwargs: Vec<(String, sqlbuild_sqltext::compiler::models::AuthoredValue)>, ) -> PyResult> { let keywords = PyDict::new(py); keywords.set_item("name", name)?; keywords.set_item("kwargs", map_to_python(py, kwargs)?)?; - instance(py, (DISCOVERY_MODELS_MODULE, class), &keywords) + instance(py, class, &keywords) } pub(crate) fn optional_authored_value_to_python( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs index 594654873d..bbcc55a893 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs @@ -10,11 +10,10 @@ use sqlbuild_sqltext::code_scanner::main::scanner_non_code_end::scanner_non_code use sqlbuild_sqltext::code_scanner::models::CodeScanner; use sqlbuild_sqltext::sql_scan::models::Unclosed; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; - /// One SQL text scanned under an adapter's lexical rules, or the generic compiler rules. #[pyclass(module = "sqlbuild._native", frozen)] pub(crate) struct SqlCodeScanner { @@ -54,10 +53,8 @@ impl SqlCodeScanner { }; let message: String = format!("{} contains an unclosed {construct}", self.context); let raised = || -> PyResult { - let error: Bound<'_, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call1((message,))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call1((message,))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs index 3b48467554..baa50eca9a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs @@ -9,12 +9,11 @@ use sqlbuild_sqltext::ddl_effects::main::relation_name_key::relation_name_key as use sqlbuild_sqltext::ddl_effects::main::statement_metadata_effect::statement_metadata_effect as native_statement_metadata_effect; use sqlbuild_sqltext::ddl_effects::models::StatementMetadataEffect; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; -const EFFECT_MODEL: (&str, &str) = ( - "sqlbuild.adapter.relations.models", - "StatementMetadataEffect", -); +const EFFECT_MODEL: AdapterHost = AdapterHost::StatementMetadataEffect; static EFFECT_CLASS: PyOnceLock> = PyOnceLock::new(); @@ -24,9 +23,7 @@ static EFFECT_CLASS: PyOnceLock> = PyOnceLock::new(); fn statement_metadata_effect<'py>(py: Python<'py>, sql: &str) -> PyResult> { let effect: StatementMetadataEffect = py.detach(|| native_statement_metadata_effect(sql)); let class: &Bound<'py, PyAny> = EFFECT_CLASS - .get_or_try_init(py, || { - imported(py, EFFECT_MODEL.0, EFFECT_MODEL.1).map(Bound::unbind) - })? + .get_or_try_init(py, || host::entry(py, EFFECT_MODEL).map(Bound::unbind))? .bind(py); call_with( class, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs index c74782f322..e800067b7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs @@ -1,6 +1,5 @@ //! Logical SQL references extracted natively under one adapter's lexical rules. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -13,16 +12,12 @@ use sqlbuild_sqltext::sql_references::models::{ use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; const REFERENCE_CALL_SYNTAX_CODE: &str = "P012"; /// `(kind, name, package, call_argument_count)` for one reference. @@ -110,16 +105,13 @@ fn scanned<'py>( ReferenceExtraction::Extracted(scan) => scan, ReferenceExtraction::Failed(failure) => { let error: Bound<'py, PyAny> = - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call1((failure.message,))?; + registry::error(py, ErrorId::CompileInput)?.call1((failure.message,))?; return Err(PyErr::from_value(error)); } }; - let kind_type: Bound<'py, PyAny> = imported(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?; - let reference_type: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompileSqlReference")?; - let invalid_type: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "InvalidSqlReferenceCall")?; + let kind_type: Bound<'py, PyAny> = registry::class(py, RecordId::SqlReferenceKind)?; + let reference_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompileSqlReference)?; + let invalid_type: Bound<'py, PyAny> = registry::class(py, RecordId::InvalidSqlReferenceCall)?; let references: Vec> = scan .references .into_iter() @@ -168,7 +160,7 @@ fn scan_sql_reference_calls<'py>( let py: Python<'py> = syntax.py(); let (references, invalid_calls) = scanned(py, sql, syntax)?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlReferenceScan")?, + ®istry::class(py, RecordId::SqlReferenceScan)?, &[ ("references", PyTuple::new(py, references)?.into_any()), ("invalid_calls", PyTuple::new(py, invalid_calls)?.into_any()), @@ -185,9 +177,8 @@ pub(crate) fn extract_references<'py>( ) -> PyResult> { let py: Python<'py> = syntax.py(); let (references, invalid_calls) = scanned(py, sql, syntax)?; - let report: Bound<'py, PyAny> = imported(py, COLLECTOR_MODULE, "report_compile_diagnostic")?; - let diagnostic_type: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?; + let report: Bound<'py, PyAny> = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; + let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let types = py.import(COMPILE_TYPES_MODULE)?; for call in invalid_calls { let diagnostic: Bound<'py, PyAny> = call_with( @@ -195,11 +186,11 @@ pub(crate) fn extract_references<'py>( &[ ( "phase", - shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?, + registry::class(types.py(), RecordId::DiagnosticPhase)?.getattr("COMPILE")?, ), ( "severity", - shape_class(types.as_any(), "DiagnosticSeverity")?.getattr("ERROR")?, + registry::class(types.py(), RecordId::DiagnosticSeverity)?.getattr("ERROR")?, ), ("code", text(py, REFERENCE_CALL_SYNTAX_CODE)?), ("message", call.getattr("message")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs index 2d5507f85e..adf8039338 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs @@ -1,9 +1,5 @@ //! Python modules, names and messages of migration state rows. -pub(crate) const MIGRATION_MODELS: &str = "sqlbuild._native"; -pub(crate) const MIGRATION_TYPES: &str = "sqlbuild._native"; -pub(crate) const MIGRATION_EXCEPTIONS: &str = "sqlbuild._native"; -pub(crate) const SQL_VALUE_TYPES: &str = "sqlbuild._native"; pub(crate) const OLD_NAME_VIEW_CONFIG_KEY: &str = "old_name_view"; pub(crate) const MODEL_KIND: &str = "model"; pub(crate) const COLUMN_KIND: &str = "column"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs index dded2008a1..e79ff7e3d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs @@ -16,11 +16,10 @@ use sqlbuild_state::events::main::old_name_view_event_id::old_name_view_event_id use sqlbuild_state::events::models::{EventRelation, StateCell, StateColumnType, StateValue}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::state_rows::timestamps::{date_time_typed, presumed_utc}; -const SQL_VALUES_EXCEPTIONS: &str = "sqlbuild.sql_values.exceptions"; - fn emit_error(error: JsonEmitError) -> PyErr { PyValueError::new_err(format!("{error:?}")) } @@ -110,7 +109,7 @@ pub(crate) fn typed_insert_sql( /// `SqlValueRenderingError(message)` for an ill-typed value. fn rendering_error(py: Python<'_>) -> impl Fn(StateSqlError) -> PyErr + '_ { move |error: StateSqlError| { - let raised = imported(py, SQL_VALUES_EXCEPTIONS, "SqlValueRenderingError") + let raised = host::entry(py, ExecutorHost::SqlValueRenderingError) .and_then(|class| class.call1((error.0,))); match raised { Ok(exception) => PyErr::from_value(exception), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs index 578d6b0c4e..df4e44520c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs @@ -19,18 +19,18 @@ use sqlbuild_state::freshness::types::TimestampParser; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::{DATETIME_MODULE, local_date_time}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::timestamps::{ date_time_typed, in_utc, offset_aware, presumed_utc, }; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const TIMESTAMP_KIND: &str = "timestamp"; const INTEGER_KIND: &str = "integer"; const STRING_KIND: &str = "string"; -fn raised(py: Python<'_>, class: &str, message: String) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, class).and_then(|class| class.call1((message,))) { +fn raised(py: Python<'_>, class: NativeAttr, message: String) -> PyErr { + match registry::native(py, class).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -39,9 +39,11 @@ fn raised(py: Python<'_>, class: &str, message: String) -> PyErr { fn python_error(py: Python<'_>, error: FreshnessError) -> PyErr { match error { FreshnessError::Observation(message) => { - raised(py, "SourceFreshnessObservationError", message) + raised(py, NativeAttr::SourceFreshnessObservationError, message) + } + FreshnessError::Input(message) => { + raised(py, NativeAttr::SourceFreshnessInputError, message) } - FreshnessError::Input(message) => raised(py, "SourceFreshnessInputError", message), FreshnessError::Overflow(message) => PyOverflowError::new_err(message), } } @@ -60,7 +62,11 @@ pub(crate) fn version_hash( } fn observation_error(py: Python<'_>, message: &str) -> PyErr { - raised(py, "SourceFreshnessObservationError", message.to_owned()) + raised( + py, + NativeAttr::SourceFreshnessObservationError, + message.to_owned(), + ) } /// A source freshness value normalized for stable state storage. @@ -107,7 +113,7 @@ pub(crate) fn normalize<'py>( } Err(raised( py, - "SourceFreshnessObservationError", + NativeAttr::SourceFreshnessObservationError, format!( "Unsupported source freshness value kind: {}", value_kind.str()? @@ -206,7 +212,7 @@ pub(crate) fn age_policy<'py>( if policy.is_none() { return Ok(None); } - let statuses: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS, "SourceFreshnessAgeStatus")?; + let statuses: Bound<'py, PyAny> = registry::class(py, RecordId::SourceFreshnessAgeStatus)?; if !date_time_typed(data_version)? { return statuses.getattr("UNKNOWN").map(Some); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs index 5487870783..c96a3c8618 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs @@ -11,10 +11,10 @@ use sqlbuild_state::events::constants::{ }; use sqlbuild_state::events::models::EventOrder; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::state_rows::constants::{ - COLUMN_KIND, MIGRATION_EXCEPTIONS, MODEL_KIND, OLD_NAME_KIND, -}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::state_rows::constants::{COLUMN_KIND, MODEL_KIND, OLD_NAME_KIND}; use crate::bindings::_helpers::state_rows::models::{EventTable, EventTableLayout}; use crate::bindings::_helpers::state_rows::timestamps::presumed_utc; @@ -56,9 +56,7 @@ pub(crate) fn layout(kind: &str) -> PyResult { /// `MigrationStateError(message)`. pub(crate) fn state_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, MIGRATION_EXCEPTIONS, "MigrationStateError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::MigrationState).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs index cb87db8e4c..0da6a9176b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs @@ -11,10 +11,10 @@ use sqlbuild_state::events::models::{ ColumnEventKey, EventOrder, EventRelation, MoveEventKey, OldNameFactKey, OldNameStep, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::state_rows::constants::{ - MIGRATION_MODELS, OLD_NAME_VIEW_CONFIG_KEY, -}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::state_rows::constants::OLD_NAME_VIEW_CONFIG_KEY; use crate::bindings::_helpers::state_rows::events::relation as event_relation; use crate::bindings::_helpers::state_rows::migration_objects::event_order; @@ -108,7 +108,7 @@ pub(crate) fn old_name_views<'py>( }) }) .collect::>()?; - let history = imported(py, MIGRATION_MODELS, "OldNameViewHistory")?; + let history = registry::class(py, RecordId::OldNameViewHistory)?; let fact_at = |index: Option| chosen(py, &facts, index); let histories: Vec> = old_name_histories(&move_orders, &fact_keys) .into_iter() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs index 737dc4d356..c0af2ecbc0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs @@ -1,6 +1,5 @@ //! Create, read and append SQL of migration state tables, and stored rows decoded to events. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::exceptions::{PyKeyError, PyValueError}; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, @@ -19,9 +18,8 @@ use sqlbuild_state::events::main::readable_columns::readable_columns; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; -use crate::bindings::_helpers::state_rows::constants::{ - MIGRATION_MODELS, MIGRATION_TYPES, SQL_VALUE_TYPES, -}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::events::typed_insert_sql; use crate::bindings::_helpers::state_rows::migration_objects::{ layout, qualified_table, state_error, @@ -42,7 +40,7 @@ fn create_sql<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = imported(py, SQL_VALUE_TYPES, "StateSqlValueType")?; + let value_types: Bound<'py, PyAny> = registry::class(py, RecordId::StateSqlValueType)?; let column_types: Bound<'py, PyDict> = PyDict::new(py); for column in layout.columns { let member: &str = if TIMESTAMP_COLUMNS.contains(column) { @@ -224,7 +222,7 @@ impl<'py> StoredRow<'_, 'py> { fn relation(&self, py: Python<'py>, prefix: &str) -> PyResult> { call_with( - &imported(py, MIGRATION_MODELS, "MigrationRelation")?, + ®istry::class(py, RecordId::MigrationRelation)?, &[ ( "database", @@ -255,15 +253,15 @@ fn timestamp<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { fn enumerated<'py>( py: Python<'py>, - class: &str, + class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult> { - shape_class_at(py, MIGRATION_TYPES, class)?.call1((value,)) + registry::class(py, class)?.call1((value,)) } fn known_member<'py>( py: Python<'py>, - class: &str, + class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult>> { match enumerated(py, class, value) { @@ -282,7 +280,7 @@ fn model_event<'py>(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult> { call_with( - &imported(py, MIGRATION_MODELS, "ColumnMigrationEvent")?, + ®istry::class(py, RecordId::ColumnMigrationEvent)?, &[ ("event_id", row.text("event_id")?), ("target_name", row.nullable(py, "target_name")?), @@ -317,11 +315,11 @@ fn column_event<'py>(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult( Some(value) => value.str()?.into_any(), None => PyString::new(py, "None").into_any(), }; - let Some(event_type) = known_member(py, "OldNameViewEventType", raw_type)? else { + let Some(event_type) = known_member(py, RecordId::OldNameViewEventType, raw_type)? else { return Ok(None); }; let drop_reason: Option> = match row.optional_text("drop_reason")? { - Some(raw) => known_member(py, "OldNameViewDropReason", raw)?, + Some(raw) => known_member(py, RecordId::OldNameViewDropReason, raw)?, None => None, }; let none = || py.None().into_bound(py); @@ -380,11 +378,7 @@ fn old_name_event<'py>( ("grants_copied", grants), ("view_sql", row.nullable(py, "view_sql")?), ]; - call_with( - &imported(py, MIGRATION_MODELS, "OldNameViewEvent")?, - &fields, - ) - .map(Some) + call_with(®istry::class(py, RecordId::OldNameViewEvent)?, &fields).map(Some) } /// Stored rows of the selected `columns` as events; rows this version cannot read are skipped. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs index 3cc4994e3a..90a55d550b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs @@ -15,11 +15,11 @@ use sqlbuild_state::state_tables::main::values_insert_sql::values_insert_sql; use sqlbuild_state::state_tables::models::TableColumn; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::state_literals::render_state_sql_literal; -use crate::bindings::_helpers::freshness::support::ADAPTER_EXCEPTIONS; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; -const ADAPTER_TYPES: &str = "sqlbuild.adapter.contract.types"; const MISSING_SCHEMA_MESSAGE: &str = "source freshness table requires a target schema"; /// Each state value type a state table may declare and the framework type that renders it. const DECLARED_TYPES: [(&str, &str); 3] = [ @@ -39,7 +39,7 @@ pub(crate) fn state_table_create_sql<'py>( render_framework_type: &Bound<'py, PyAny>, ) -> PyResult { let py: Python<'py> = render_framework_type.py(); - let framework_types: Bound<'py, PyAny> = imported(py, ADAPTER_TYPES, "FrameworkType")?; + let framework_types: Bound<'py, PyAny> = host::entry(py, AdapterHost::FrameworkType)?; let mut rendered: Vec<(&str, String)> = Vec::with_capacity(DECLARED_TYPES.len()); for (declared, framework) in DECLARED_TYPES { let text: String = render_framework_type @@ -82,7 +82,7 @@ fn render_create_sql(request: Option<&Bound<'_, PyDict>>) -> PyResult { } fn adapter_user_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, ADAPTER_EXCEPTIONS, "AdapterUserError").and_then(|class| { + match host::entry(py, AdapterHost::AdapterUserError).and_then(|class| { call_with( &class, &[("message", PyString::new(py, message).into_any())], diff --git a/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs b/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs index 3af3c77ea6..cc2553b41a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs @@ -9,10 +9,11 @@ use sqlbuild_analysis::type_system::main::normalize_type::normalize_type; use sqlbuild_analysis::type_system::models::{NormalizedType, TypeNormalization}; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::type_system::normalization::normalization_error; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const TYPE_NORMALIZATION_LOGGER: &str = "sqlbuild.adapter"; const TYPE_PARSE_FALLBACK_MESSAGE: &str = "type normalization polyglot parse failed; falling back"; const DEFAULT_DIALECT: &str = "generic"; @@ -69,7 +70,7 @@ fn log_parse_fallback( ) -> PyResult<()> { let py: Python<'_> = dialect.py(); call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, + &host::entry(py, ExecutorHost::LogDebugEvent)?, &[ ( "logger", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs index bae5bed561..c40910c9e4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs @@ -20,12 +20,14 @@ use sqlbuild_config::workspace::models::{PlaygroundRequest, SkillInstallRequest} use sqlbuild_core::cli_text::models::CliStyle; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const SKILLS_DIRECTORY: &str = ".agents/skills/sqlbuild"; const TEMPLATES_DIRECTORY: &str = "playground_templates"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const OUTPUT_MODELS: &str = "sqlbuild._native"; const SKILLS_OPTIONS: (&[&str], &[&str]) = (&["project_dir"], &["global_install", "targets", "force"]); const UPDATE_OPTIONS: (&[&str], &[&str]) = ( @@ -45,8 +47,8 @@ fn raised(py: Python<'_>, error: WorkspaceError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; keywords.set_item("help", help)?; - let instance: Bound<'_, PyAny> = imported(py, EXCEPTIONS_MODULE, "CliUserError")? - .call((message,), Some(&keywords))?; + let instance: Bound<'_, PyAny> = + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(instance)) }; created().unwrap_or_else(|failure| failure) @@ -63,7 +65,7 @@ fn raised(py: Python<'_>, error: WorkspaceError) -> PyErr { /// The installed `sqlbuild` package directory. fn package_dir(py: Python<'_>) -> PyResult { - let file: PathBuf = py.import("sqlbuild")?.getattr("__file__")?.extract()?; + let file: PathBuf = host::entry(py, FacadeHost::PackageFile)?.extract()?; Ok(file.parent().map(Path::to_path_buf).unwrap_or_default()) } @@ -117,7 +119,7 @@ fn update_sqlbuild_skills<'py>( let written: Vec = install(py, &request)?; let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("written_paths", PyTuple::new(py, written)?)?; - imported(py, OUTPUT_MODELS, "SkillUpdateResult")?.call((), Some(&keywords)) + registry::class(py, RecordId::SkillUpdateResult)?.call((), Some(&keywords)) } /// Check or refresh generated project skills without changing command success. @@ -128,7 +130,7 @@ fn maintain_sqlbuild_skills(py: Python<'_>, project_dir: PathBuf) -> PyResult = PyDict::new(py); keywords.set_item("message", message)?; - imported(py, OUTPUT_MODELS, "SkillMaintenanceResult")?.call((), Some(&keywords)) + registry::class(py, RecordId::SkillMaintenanceResult)?.call((), Some(&keywords)) } /// Return every packaged skill file keyed by skill-relative path, with generated markers. diff --git a/crates/sqlbuild-python/src/bindings/main/register.rs b/crates/sqlbuild-python/src/bindings/main/register.rs index d47d397c40..1643c96c6d 100644 --- a/crates/sqlbuild-python/src/bindings/main/register.rs +++ b/crates/sqlbuild-python/src/bindings/main/register.rs @@ -8,15 +8,17 @@ use crate::bindings::_helpers::{ compile_attachment, compile_command, compile_driver, compile_outputs, compiled_project, contracts, cursor, dag_artifact, diagnostics, discovery, exported_constants, fingerprints, frontier, graph, lifecycle_events, lineage, macro_bridge, manifest, microbatches, model_config, - plan_output, planner_registration, project_assembly, records, refactor, references, render, - rules, scopes, semantic_checks, semantic_shapes, sql_test_glue, sqltext, state_rows, - type_system, + native_host, plan_output, planner_registration, project_assembly, records, refactor, + references, render, rules, scopes, semantic_checks, semantic_shapes, sql_test_glue, sqltext, + state_rows, type_system, }; use crate::bindings::models; #[pymodule] pub fn _native(module: &Bound<'_, PyModule>) -> PyResult<()> { records::registration::register(module)?; + records::registry::verify_tables()?; + native_host::registration::register(module)?; rules::registration::register(module)?; cli::registration::register(module)?; analysis::normalization::register(module)?; @@ -33,7 +35,7 @@ pub fn _native(module: &Bound<'_, PyModule>) -> PyResult<()> { register_analysis_stages(module)?; module.add("API_VERSION", API_VERSION)?; module.add("BUILD_IDENTITY", NATIVE_BUILD_IDENTITY)?; - Ok(()) + records::registry::verify_attributes(module) } fn register_project_stages(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/fensu.toml b/fensu.toml index db6188ab05..b4a5217090 100644 --- a/fensu.toml +++ b/fensu.toml @@ -296,6 +296,11 @@ rule = "FFR402" path = "src/sqlbuild/rules/__init__.py" reason = "These imports provide the documented sqlbuild.rules package-root authoring API." +[[targets.python.rule_exceptions]] +rule = "FFR307" +path = "src/sqlbuild/_native_host.py" +reason = "The native host boundary: the one module native code imports by path, naming every staying-Python entry point native code may call." + [[targets.python.rule_exceptions]] rule = "FFR307" path = "src/sqlbuild/rules/testing.py" diff --git a/python-boundary.toml b/python-boundary.toml index 4b1df2ee41..1f018ef03d 100644 --- a/python-boundary.toml +++ b/python-boundary.toml @@ -552,6 +552,11 @@ path = "compiler/sql_analysis/_helpers/polyglot.py" rule = 4 note = "Wraps the Polyglot Python package with complexity guards." +[[stays]] +path = "_native_host.py" +rule = 1 +note = "The one module native code imports by path: a lazy name table of the executor, adapter, user-code, third-party and facade entry points the native host table (crates/sqlbuild-python native_host) reaches." + [[stays]] path = "assets.py" rule = 3 diff --git a/scripts/native_module_paths/_helpers/scan.py b/scripts/native_module_paths/_helpers/scan.py index e830a6fc39..75c0ab66f5 100644 --- a/scripts/native_module_paths/_helpers/scan.py +++ b/scripts/native_module_paths/_helpers/scan.py @@ -5,6 +5,8 @@ from scripts.native_module_paths.constants import ( DOTTED_PATH, DYNAMIC_MODULE_PATHS, + HOST_ENTRY, + HOST_MODULE, IMPORT_GETATTR_CHAIN, IMPORT_HELPER_CALL, LITERAL_MODULE_ATTRIBUTE_PAIR, @@ -16,7 +18,7 @@ def scan_native_source(*, source: str, label: str) -> NativeModulePathScan: - """Return the module paths, module attributes and dynamic module paths in one Rust file.""" + """Return the module paths, attributes, host entries and dynamic module paths in one file.""" constants: dict[str, str] = { match.group(1): match.group(2) for match in STRING_CONSTANT.finditer(source) } @@ -51,6 +53,14 @@ def scan_native_source(*, source: str, label: str) -> NativeModulePathScan: attribute=match.group(2), ) ) + references.extend( + ModulePathReference( + location=_location(source=source, label=label, offset=match.start()), + path=HOST_MODULE, + attribute=match.group(1), + ) + for match in HOST_ENTRY.finditer(source) + ) for pattern in DYNAMIC_MODULE_PATHS: violations.extend( f"{_location(source=source, label=label, offset=match.start())}: module path is " diff --git a/scripts/native_module_paths/constants.py b/scripts/native_module_paths/constants.py index 7cf304ae84..192e7b5ecf 100644 --- a/scripts/native_module_paths/constants.py +++ b/scripts/native_module_paths/constants.py @@ -32,3 +32,5 @@ ), re.compile(r"\b(?:format|concat)!\(\s*\"sqlbuild\."), ) +HOST_MODULE: str = "sqlbuild._native_host" +HOST_ENTRY: re.Pattern[str] = re.compile(r"\bHostEntry\s*\{\s*name:\s*" + ATTRIBUTE_NAME) diff --git a/src/sqlbuild/_native.pyi b/src/sqlbuild/_native.pyi index 5bfbae85ff..378b4a7620 100644 --- a/src/sqlbuild/_native.pyi +++ b/src/sqlbuild/_native.pyi @@ -168,6 +168,7 @@ from sqlbuild.runtime.output_capture.models import ( from sqlbuild.runtime.output_capture.types import CommandOutputStream BUILD_IDENTITY: str +HOST_ENTRIES: tuple[tuple[str, str, str], ...] MICROBATCH_START_SENTINEL: str MICROBATCH_END_SENTINEL: str MIGRATION_FINGERPRINT_METADATA_KEY: str diff --git a/src/sqlbuild/_native_host.py b/src/sqlbuild/_native_host.py new file mode 100644 index 0000000000..3e22a919a5 --- /dev/null +++ b/src/sqlbuild/_native_host.py @@ -0,0 +1,733 @@ +"""The only module native code imports by path: staying-Python entry points by name.""" + +from __future__ import annotations + +import importlib + +_TARGETS: dict[str, tuple[str, str]] = { + "BaseAdapter": ("sqlbuild.adapter.contract.classes.base_adapter", "BaseAdapter"), + "AdapterUserError": ("sqlbuild.adapter.contract.exceptions", "AdapterUserError"), + "ColumnInfo": ("sqlbuild.adapter.contract.models", "ColumnInfo"), + "ExpressionInferenceProfile": ( + "sqlbuild.adapter.contract.models", + "ExpressionInferenceProfile", + ), + "RelationInfo": ("sqlbuild.adapter.contract.models", "RelationInfo"), + "RelationLookup": ("sqlbuild.adapter.contract.models", "RelationLookup"), + "RetentionRequest": ("sqlbuild.adapter.contract.models", "RetentionRequest"), + "RowDiffSampling": ("sqlbuild.adapter.contract.models", "RowDiffSampling"), + "RowDiffTolerance": ("sqlbuild.adapter.contract.models", "RowDiffTolerance"), + "RowDiffTolerances": ("sqlbuild.adapter.contract.models", "RowDiffTolerances"), + "TableFreshnessMetadata": ("sqlbuild.adapter.contract.models", "TableFreshnessMetadata"), + "TableFreshnessRequest": ("sqlbuild.adapter.contract.models", "TableFreshnessRequest"), + "BuiltinAdapter": ("sqlbuild.adapter.contract.types", "BuiltinAdapter"), + "FrameworkType": ("sqlbuild.adapter.contract.types", "FrameworkType"), + "LifeCycleEventKind": ("sqlbuild.adapter.contract.types", "LifeCycleEventKind"), + "MigrationTransfer": ("sqlbuild.adapter.contract.types", "MigrationTransfer"), + "RetentionScope": ("sqlbuild.adapter.contract.types", "RetentionScope"), + "TableFreshnessStatus": ("sqlbuild.adapter.contract.types", "TableFreshnessStatus"), + "adapter_supports_session_warehouse": ( + "sqlbuild.adapter.discovery.main._session_warehouse_support", + "adapter_supports_session_warehouse", + ), + "builtin_adapter_classes": ( + "sqlbuild.adapter.discovery.main.builtins", + "builtin_adapter_classes", + ), + "discover_project_adapters": ( + "sqlbuild.adapter.discovery.main.project_adapters", + "discover_project_adapters", + ), + "resolve_adapter": ("sqlbuild.adapter.discovery.main.resolve_adapter", "resolve_adapter"), + "get_columns_for_inspection": ( + "sqlbuild.adapter.relations.main._get_columns_for_inspection", + "get_columns_for_inspection", + ), + "list_relations_for_inspection": ( + "sqlbuild.adapter.relations.main._list_relations_for_inspection", + "list_relations_for_inspection", + ), + "active_inspection_catalog": ( + "sqlbuild.adapter.relations.main.active_inspection_catalog", + "active_inspection_catalog", + ), + "inspection_query_sink": ( + "sqlbuild.adapter.relations.main.inspection_query_sink", + "inspection_query_sink", + ), + "open_inspection_catalog": ( + "sqlbuild.adapter.relations.main.open_inspection_catalog", + "open_inspection_catalog", + ), + "record_inspection_query": ( + "sqlbuild.adapter.relations.main.record_inspection_query", + "record_inspection_query", + ), + "build_relation_lookup": ( + "sqlbuild.adapter.relations.main.relation_lookup", + "build_relation_lookup", + ), + "resolve_relation_location_qualified_name": ( + "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name", + "resolve_relation_location_qualified_name", + ), + "run_bounded_inspections": ( + "sqlbuild.adapter.relations.main.run_bounded_inspections", + "run_bounded_inspections", + ), + "run_recorded_inspection_query": ( + "sqlbuild.adapter.relations.main.run_recorded_inspection_query", + "run_recorded_inspection_query", + ), + "InspectionQueryRecord": ("sqlbuild.adapter.relations.models", "InspectionQueryRecord"), + "StatementMetadataEffect": ("sqlbuild.adapter.relations.models", "StatementMetadataEffect"), + "conditional_result_nullability": ( + "sqlbuild.adapter.type_system.main.conditional_result_nullability", + "conditional_result_nullability", + ), + "first_arg_nullability": ( + "sqlbuild.adapter.type_system.main.first_arg_nullability", + "first_arg_nullability", + ), + "normalize_relation_type": ( + "sqlbuild.adapter.type_system.main.normalize_relation_type", + "normalize_relation_type", + ), + "normalize_type": ("sqlbuild.adapter.type_system.main.normalize_type", "normalize_type"), + "safe_cast_nullability": ( + "sqlbuild.adapter.type_system.main.safe_cast_nullability", + "safe_cast_nullability", + ), + "python_resource_rows": ( + "sqlbuild.compiler.compile._helpers.diagnostics.python_resources", + "python_resource_rows", + ), + "target_context_with_macro_call_store": ( + "sqlbuild.compiler.compile._helpers.macro_bridge.call_store", + "target_context_with_macro_call_store", + ), + "inventory_project_macros": ( + "sqlbuild.compiler.compile._helpers.render.user_macros", + "inventory_project_macros", + ), + "load_project_macros": ( + "sqlbuild.compiler.compile._helpers.render.user_macros", + "load_project_macros", + ), + "macro_invocation_context": ( + "sqlbuild.compiler.compile._helpers.render.user_macros", + "macro_invocation_context", + ), + "discover_command_output_sink_functions": ( + "sqlbuild.compiler.discovery._helpers.filesystem.command_output_sinks", + "discover_command_output_sink_functions", + ), + "import_python_root_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "import_python_root_module", + ), + "install_python_root_package": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "install_python_root_package", + ), + "load_materialize_function": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_materialize_function", + ), + "load_provider_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_provider_module", + ), + "load_python_node_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_python_node_module", + ), + "load_sink_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_sink_module", + ), + "module_functions": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "module_functions", + ), + "module_provider_classes": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "module_provider_classes", + ), + "parameter_annotations": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "parameter_annotations", + ), + "provider_usages": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "provider_usages", + ), + "requires_arguments": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "requires_arguments", + ), + "restore_python_root_modules": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "restore_python_root_modules", + ), + "source_line": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "source_line", + ), + "validate_event_exporter_signature": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "validate_event_exporter_signature", + ), + "validate_discovered_inputs": ( + "sqlbuild.compiler.discovery._helpers.validation.discovery", + "validate_discovered_inputs", + ), + "discover_runtime_extensions": ( + "sqlbuild.compiler.discovery.main.runtime_extensions", + "discover_runtime_extensions", + ), + "run_with_macro_bridge": ( + "sqlbuild.compiler.macro_bridge.classes.macro_bridge", + "run_with_macro_bridge", + ), + "macro_store_module_paths": ( + "sqlbuild.compiler.macro_bridge.main._macro_store_module_paths", + "macro_store_module_paths", + ), + "active_macro_bridge": ( + "sqlbuild.compiler.macro_bridge.main.active_macro_bridge", + "active_macro_bridge", + ), + "load_custom_materializations": ( + "sqlbuild.compiler.pipeline._helpers.materializations", + "load_custom_materializations", + ), + "python_node_dependency_key": ( + "sqlbuild.compiler.python_nodes._helpers.user_inventory", + "python_node_dependency_key", + ), + "python_node_identity": ( + "sqlbuild.compiler.python_nodes._helpers.user_inventory", + "python_node_identity", + ), + "build_hook_identities": ( + "sqlbuild.compiler.python_nodes.main._hook_identities", + "build_hook_identities", + ), + "PythonNodeKind": ("sqlbuild.compiler.python_nodes.types", "PythonNodeKind"), + "PythonNodeStatus": ("sqlbuild.compiler.python_nodes.types", "PythonNodeStatus"), + "ProviderInputError": ("sqlbuild.provider.exceptions", "ProviderInputError"), + "build_provider_session": ("sqlbuild.provider.main.session", "build_provider_session"), + "read_asset_definition": ( + "sqlbuild.python_nodes.main.read_asset_definition", + "read_asset_definition", + ), + "read_audit_factory_definition": ( + "sqlbuild.python_nodes.main.read_audit_factory_definition", + "read_audit_factory_definition", + ), + "read_check_definition": ( + "sqlbuild.python_nodes.main.read_check_definition", + "read_check_definition", + ), + "read_factory_definition": ( + "sqlbuild.python_nodes.main.read_factory_definition", + "read_factory_definition", + ), + "read_hook_definition": ( + "sqlbuild.python_nodes.main.read_hook_definition", + "read_hook_definition", + ), + "read_loader_definition": ( + "sqlbuild.python_nodes.main.read_loader_definition", + "read_loader_definition", + ), + "read_task_definition": ( + "sqlbuild.python_nodes.main.read_task_definition", + "read_task_definition", + ), + "AuditCase": ("sqlbuild.python_nodes.models", "AuditCase"), + "RELATION_RENDERER": ("sqlbuild.python_nodes.models", "RELATION_RENDERER"), + "SqlResourceRef": ("sqlbuild.python_nodes.models", "SqlResourceRef"), + "PythonCheckSeverity": ("sqlbuild.python_nodes.types", "PythonCheckSeverity"), + "SqlResourceRefKind": ("sqlbuild.python_nodes.types", "SqlResourceRefKind"), + "load_custom_rules": ( + "sqlbuild.rule_engine._helpers.engine._custom_catalogue", + "load_custom_rules", + ), + "custom_rule_payloads": ( + "sqlbuild.rule_engine._helpers.engine.custom_payloads", + "custom_rule_payloads", + ), + "fact_outcome_digest": ( + "sqlbuild.rule_engine._helpers.engine.fact_replay", + "fact_outcome_digest", + ), + "full_fact_keys": ("sqlbuild.rule_engine._helpers.engine.fact_replay", "full_fact_keys"), + "is_tree_fact": ("sqlbuild.rule_engine._helpers.engine.fact_replay", "is_tree_fact"), + "shared_fact_encoder": ( + "sqlbuild.rule_engine._helpers.engine.fact_replay", + "shared_fact_encoder", + ), + "build_rule_fact_views": ("sqlbuild.rule_engine.classes.rule_context", "build_rule_fact_views"), + "EventExporterInputError": ( + "sqlbuild.runtime.event_exporting.exceptions", + "EventExporterInputError", + ), + "EventExporterStateError": ( + "sqlbuild.runtime.event_exporting.exceptions", + "EventExporterStateError", + ), + "invoke_lifecycle_event_sink": ( + "sqlbuild.runtime.event_exporting.main._invoke_lifecycle_event_sink", + "invoke_lifecycle_event_sink", + ), + "run_command_output_sinks": ( + "sqlbuild.runtime.event_exporting.main._run_command_output_sinks", + "run_command_output_sinks", + ), + "get_lifecycle_event_sink_definition": ( + "sqlbuild.runtime.event_exporting.main.get_lifecycle_event_sink_definition", + "get_lifecycle_event_sink_definition", + ), + "CostResourceContext": ("sqlbuild.cost.models", "CostResourceContext"), + "RunCostSummary": ("sqlbuild.cost.models", "RunCostSummary"), + "CostCapability": ("sqlbuild.cost.types", "CostCapability"), + "CostStatus": ("sqlbuild.cost.types", "CostStatus"), + "BoundSentinel": ("sqlbuild.cursor_algebra.types", "BoundSentinel"), + "read_process_resources": ( + "sqlbuild.diagnostics._helpers.process_resources", + "read_process_resources", + ), + "SQL_PRIVATE_RECORD_MARKER": ("sqlbuild.diagnostics.constants", "SQL_PRIVATE_RECORD_MARKER"), + "diagnostics_context": ("sqlbuild.diagnostics.main.diagnostics_context", "diagnostics_context"), + "log_debug_event": ("sqlbuild.diagnostics.main.log_debug_event", "log_debug_event"), + "log_sql": ("sqlbuild.diagnostics.main.log_sql", "log_sql"), + "process_resource_reporting": ( + "sqlbuild.diagnostics.main.process_resource_reporting", + "process_resource_reporting", + ), + "ProcessResourceUsage": ("sqlbuild.diagnostics.models", "ProcessResourceUsage"), + "ExecutorInputError": ("sqlbuild.errors.contracts.exceptions", "ExecutorInputError"), + "current_audit_result_projection": ( + "sqlbuild.executor.auditing.main._current_audit_result_projection", + "current_audit_result_projection", + ), + "AuditExecutionResult": ("sqlbuild.executor.auditing.models", "AuditExecutionResult"), + "BuildCallbacks": ("sqlbuild.executor.build.models", "BuildCallbacks"), + "BuildCustomizations": ("sqlbuild.executor.build.models", "BuildCustomizations"), + "BuildExecutionResult": ("sqlbuild.executor.build.models", "BuildExecutionResult"), + "BuildInitialState": ("sqlbuild.executor.build.models", "BuildInitialState"), + "BuildRuntimeParams": ("sqlbuild.executor.build.models", "BuildRuntimeParams"), + "FunctionExecutionResult": ("sqlbuild.executor.build.models", "FunctionExecutionResult"), + "SeedExecutionResult": ("sqlbuild.executor.build.models", "SeedExecutionResult"), + "BuildStatus": ("sqlbuild.executor.build.types", "BuildStatus"), + "build_destination_transient_models": ( + "sqlbuild.executor.clone.main._build_destination_transient_models", + "build_destination_transient_models", + ), + "build_destination_retention_requests": ( + "sqlbuild.executor.clone.main._build_retention_requests", + "build_destination_retention_requests", + ), + "copy_clone_fingerprints": ( + "sqlbuild.executor.clone.main._fingerprinting", + "copy_clone_fingerprints", + ), + "run_prephase_clone_stream": ( + "sqlbuild.executor.clone.main._run_prephase_clone_stream", + "run_prephase_clone_stream", + ), + "execute_clone": ("sqlbuild.executor.clone.main.execute", "execute_clone"), + "CloneExecutionInput": ("sqlbuild.executor.clone.models", "CloneExecutionInput"), + "CloneItemResult": ("sqlbuild.executor.clone.models", "CloneItemResult"), + "CloneSourceEntries": ("sqlbuild.executor.clone.models", "CloneSourceEntries"), + "CloneStatus": ("sqlbuild.executor.clone.types", "CloneStatus"), + "QueryDiffArtifactLifecycle": ( + "sqlbuild.executor.diff.classes.query_artifact_lifecycle", + "QueryDiffArtifactLifecycle", + ), + "FullDiffSizeGuardError": ("sqlbuild.executor.diff.exceptions", "FullDiffSizeGuardError"), + "execute_query_diff": ("sqlbuild.executor.diff.main._execute_query", "execute_query_diff"), + "execute_diff": ("sqlbuild.executor.diff.main.execute", "execute_diff"), + "DiffExecutionOptions": ("sqlbuild.executor.diff.models", "DiffExecutionOptions"), + "DiffExecutionResult": ("sqlbuild.executor.diff.models", "DiffExecutionResult"), + "FullDiffSizeLimits": ("sqlbuild.executor.diff.models", "FullDiffSizeLimits"), + "RowDiffSamplingOverride": ("sqlbuild.executor.diff.models", "RowDiffSamplingOverride"), + "execute_janitor_plan": ("sqlbuild.executor.janitor.main.execute", "execute_janitor_plan"), + "JanitorArchiveCandidate": ("sqlbuild.executor.janitor.models", "JanitorArchiveCandidate"), + "JanitorArchivedRelation": ("sqlbuild.executor.janitor.models", "JanitorArchivedRelation"), + "JanitorBlockedSchema": ("sqlbuild.executor.janitor.models", "JanitorBlockedSchema"), + "JanitorDeleteCandidate": ("sqlbuild.executor.janitor.models", "JanitorDeleteCandidate"), + "JanitorDirectStatePruneCandidate": ( + "sqlbuild.executor.janitor.models", + "JanitorDirectStatePruneCandidate", + ), + "JanitorOldNameView": ("sqlbuild.executor.janitor.models", "JanitorOldNameView"), + "JanitorOldNameViewPlanning": ( + "sqlbuild.executor.janitor.models", + "JanitorOldNameViewPlanning", + ), + "JanitorPlan": ("sqlbuild.executor.janitor.models", "JanitorPlan"), + "JanitorQueryDiffArtifactCandidate": ( + "sqlbuild.executor.janitor.models", + "JanitorQueryDiffArtifactCandidate", + ), + "JanitorRelationKey": ("sqlbuild.executor.janitor.models", "JanitorRelationKey"), + "JanitorSkippedRelation": ("sqlbuild.executor.janitor.models", "JanitorSkippedRelation"), + "JanitorSkippedSchema": ("sqlbuild.executor.janitor.models", "JanitorSkippedSchema"), + "run_load_pipeline": ("sqlbuild.executor.load.main.run", "run_load_pipeline"), + "LoadCallbacks": ("sqlbuild.executor.load.models", "LoadCallbacks"), + "LoadExecutionResult": ("sqlbuild.executor.load.models", "LoadExecutionResult"), + "LoadRuntimeParams": ("sqlbuild.executor.load.models", "LoadRuntimeParams"), + "migration_relation_for_location": ( + "sqlbuild.executor.migrations._helpers.relation_for_location", + "migration_relation_for_location", + ), + "is_compatibility_view": ( + "sqlbuild.executor.old_name_views._helpers.janitor", + "is_compatibility_view", + ), + "AuditExecutionError": ("sqlbuild.executor.pipeline.exceptions", "AuditExecutionError"), + "AuditPipelineCallbacks": ("sqlbuild.executor.pipeline.main.run", "AuditPipelineCallbacks"), + "run_audit_pipeline_with_callbacks": ( + "sqlbuild.executor.pipeline.main.run", + "run_audit_pipeline_with_callbacks", + ), + "run_build_pipeline": ("sqlbuild.executor.pipeline.main.run", "run_build_pipeline"), + "run_scenario_capture_pipeline": ( + "sqlbuild.executor.pipeline.main.run", + "run_scenario_capture_pipeline", + ), + "run_scenario_local_test_pipeline": ( + "sqlbuild.executor.pipeline.main.run", + "run_scenario_local_test_pipeline", + ), + "run_scenario_test_pipeline": ( + "sqlbuild.executor.pipeline.main.run", + "run_scenario_test_pipeline", + ), + "run_seed_pipeline": ("sqlbuild.executor.pipeline.main.run", "run_seed_pipeline"), + "run_test_pipeline": ("sqlbuild.executor.pipeline.main.run", "run_test_pipeline"), + "select_scenario_snapshot_capture_candidates": ( + "sqlbuild.executor.pipeline.main.run", + "select_scenario_snapshot_capture_candidates", + ), + "TestPipelineCallbacks": ("sqlbuild.executor.pipeline.models", "TestPipelineCallbacks"), + "execute_python_checks": ( + "sqlbuild.executor.python_nodes.main._checks", + "execute_python_checks", + ), + "execute_ingress_python_loader_nodes": ( + "sqlbuild.executor.python_nodes.main._ingress", + "execute_ingress_python_loader_nodes", + ), + "run_ingress_python_loader_nodes": ( + "sqlbuild.executor.python_nodes.main._ingress", + "run_ingress_python_loader_nodes", + ), + "create_read_side_python_execution_tracker": ( + "sqlbuild.executor.python_nodes.main.read_side", + "create_read_side_python_execution_tracker", + ), + "IngressCallbacks": ("sqlbuild.executor.python_nodes.models", "IngressCallbacks"), + "PythonCheckCallbacks": ("sqlbuild.executor.python_nodes.models", "PythonCheckCallbacks"), + "PythonCheckExecutionResult": ( + "sqlbuild.executor.python_nodes.models", + "PythonCheckExecutionResult", + ), + "PythonNodeExecutionResult": ( + "sqlbuild.executor.python_nodes.models", + "PythonNodeExecutionResult", + ), + "PythonNodeRunState": ("sqlbuild.executor.python_nodes.models", "PythonNodeRunState"), + "PythonNodeRuntime": ("sqlbuild.executor.python_nodes.models", "PythonNodeRuntime"), + "ModelExecutionResult": ("sqlbuild.executor.run.models", "ModelExecutionResult"), + "ScenarioCaptureSettings": ("sqlbuild.executor.scenario.models", "ScenarioCaptureSettings"), + "ScenarioLocalReplaySource": ("sqlbuild.executor.scenario.models", "ScenarioLocalReplaySource"), + "ScenarioSnapshotCaptureLimits": ( + "sqlbuild.executor.scenario.models", + "ScenarioSnapshotCaptureLimits", + ), + "ExecutionStatus": ("sqlbuild.executor.scheduling.types", "ExecutionStatus"), + "SqlTestExecutionResult": ("sqlbuild.executor.testing.models", "SqlTestExecutionResult"), + "MicrobatchEventCodec": ("sqlbuild.microbatches.classes.event_codec", "MicrobatchEventCodec"), + "MICROBATCH_COLUMNS": ("sqlbuild.microbatches.constants", "MICROBATCH_COLUMNS"), + "MICROBATCH_COLUMN_TYPES": ("sqlbuild.microbatches.constants", "MICROBATCH_COLUMN_TYPES"), + "MICROBATCH_DIRECT_INDEXES": ("sqlbuild.microbatches.constants", "MICROBATCH_DIRECT_INDEXES"), + "MICROBATCH_GENERATION_WILDCARD": ( + "sqlbuild.microbatches.constants", + "MICROBATCH_GENERATION_WILDCARD", + ), + "MICROBATCH_TABLE_NAME": ("sqlbuild.microbatches.constants", "MICROBATCH_TABLE_NAME"), + "MicrobatchStateError": ("sqlbuild.microbatches.exceptions", "MicrobatchStateError"), + "MicrobatchCoverageProjection": ( + "sqlbuild.microbatches.models", + "MicrobatchCoverageProjection", + ), + "MicrobatchInterval": ("sqlbuild.microbatches.models", "MicrobatchInterval"), + "ProjectedMicrobatchInterval": ("sqlbuild.microbatches.models", "ProjectedMicrobatchInterval"), + "ReplayRequirementProjection": ("sqlbuild.microbatches.models", "ReplayRequirementProjection"), + "ReplayRequirementState": ("sqlbuild.microbatches.types", "ReplayRequirementState"), + "TextComputeLogTee": ("sqlbuild.runtime.compute_logs.classes.text_tee", "TextComputeLogTee"), + "CaptureAlreadyExistsError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "CaptureAlreadyExistsError", + ), + "CaptureNotFoundError": ("sqlbuild.runtime.compute_logs.exceptions", "CaptureNotFoundError"), + "CaptureStateError": ("sqlbuild.runtime.compute_logs.exceptions", "CaptureStateError"), + "ComputeLogMetadataError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "ComputeLogMetadataError", + ), + "ComputeLogPathError": ("sqlbuild.runtime.compute_logs.exceptions", "ComputeLogPathError"), + "InvalidCaptureIdError": ("sqlbuild.runtime.compute_logs.exceptions", "InvalidCaptureIdError"), + "InvalidComputeLogCursorError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "InvalidComputeLogCursorError", + ), + "InvalidComputeLogLimitError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "InvalidComputeLogLimitError", + ), + "CaptureByteCounts": ("sqlbuild.runtime.compute_logs.models", "CaptureByteCounts"), + "CaptureInventory": ("sqlbuild.runtime.compute_logs.models", "CaptureInventory"), + "CaptureInventoryItem": ("sqlbuild.runtime.compute_logs.models", "CaptureInventoryItem"), + "CaptureMetadata": ("sqlbuild.runtime.compute_logs.models", "CaptureMetadata"), + "ComputeLogReadChunk": ("sqlbuild.runtime.compute_logs.models", "ComputeLogReadChunk"), + "FinalCaptureMetadata": ("sqlbuild.runtime.compute_logs.models", "FinalCaptureMetadata"), + "PruneResult": ("sqlbuild.runtime.compute_logs.models", "PruneResult"), + "ComputeLogStream": ("sqlbuild.runtime.compute_logs.types", "ComputeLogStream"), + "ConnectionHooks": ("sqlbuild.runtime.contracts.models", "ConnectionHooks"), + "ExecutionResourceKind": ("sqlbuild.runtime.contracts.types", "ExecutionResourceKind"), + "ExecutionHistoryStorageError": ( + "sqlbuild.runtime.execution_history.exceptions", + "ExecutionHistoryStorageError", + ), + "IntegrityConflictError": ( + "sqlbuild.runtime.execution_history.exceptions", + "IntegrityConflictError", + ), + "InvalidCursorError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidCursorError"), + "InvalidEventError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidEventError"), + "InvalidFilterError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidFilterError"), + "InvalidLimitError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidLimitError"), + "ProjectionConsistencyError": ( + "sqlbuild.runtime.execution_history.exceptions", + "ProjectionConsistencyError", + ), + "UnsupportedSchemaVersionError": ( + "sqlbuild.runtime.execution_history.exceptions", + "UnsupportedSchemaVersionError", + ), + "EventPage": ("sqlbuild.runtime.execution_history.models", "EventPage"), + "RunPage": ("sqlbuild.runtime.execution_history.models", "RunPage"), + "RunRecord": ("sqlbuild.runtime.execution_history.models", "RunRecord"), + "StoredEvent": ("sqlbuild.runtime.execution_history.models", "StoredEvent"), + "RunStatus": ("sqlbuild.runtime.execution_history.types", "RunStatus"), + "ExecutionDurationLimitError": ( + "sqlbuild.runtime.execution_limits.exceptions", + "ExecutionDurationLimitError", + ), + "execution_deadline_scope": ( + "sqlbuild.runtime.execution_limits.main.execution_deadline_scope", + "execution_deadline_scope", + ), + "EventDispatcher": ( + "sqlbuild.runtime.observability.classes.event_dispatcher", + "EventDispatcher", + ), + "OperationLifecycle": ( + "sqlbuild.runtime.observability.classes.operation_lifecycle", + "OperationLifecycle", + ), + "ObservabilityValidationError": ( + "sqlbuild.runtime.observability.exceptions", + "ObservabilityValidationError", + ), + "create_lifecycle_event": ( + "sqlbuild.runtime.observability.main.create_lifecycle_event", + "create_lifecycle_event", + ), + "current_execution_identity": ( + "sqlbuild.runtime.observability.main.current_execution_identity", + "current_execution_identity", + ), + "diagnostic_log_to_json": ( + "sqlbuild.runtime.observability.main.diagnostic_log_to_json", + "diagnostic_log_to_json", + ), + "dispatcher_scope": ( + "sqlbuild.runtime.observability.main.dispatcher_scope", + "dispatcher_scope", + ), + "invocation_external_context_scope": ( + "sqlbuild.runtime.observability.main.invocation_external_context_scope", + "invocation_external_context_scope", + ), + "invocation_scope": ( + "sqlbuild.runtime.observability.main.invocation_scope", + "invocation_scope", + ), + "lifecycle_event_from_json": ( + "sqlbuild.runtime.observability.main.lifecycle_event_from_json", + "lifecycle_event_from_json", + ), + "lifecycle_event_to_json": ( + "sqlbuild.runtime.observability.main.lifecycle_event_to_json", + "lifecycle_event_to_json", + ), + "run_scope": ("sqlbuild.runtime.observability.main.run_scope", "run_scope"), + "DiagnosticLog": ("sqlbuild.runtime.observability.models", "DiagnosticLog"), + "LifecycleEvent": ("sqlbuild.runtime.observability.models", "LifecycleEvent"), + "OperationAttributes": ("sqlbuild.runtime.observability.models", "OperationAttributes"), + "CommandOutputValidationError": ( + "sqlbuild.runtime.output_capture.exceptions", + "CommandOutputValidationError", + ), + "OutputCaptureInputError": ( + "sqlbuild.runtime.output_capture.exceptions", + "OutputCaptureInputError", + ), + "configured_output_capture_scope": ( + "sqlbuild.runtime.output_capture.main.configured_output_capture_scope", + "configured_output_capture_scope", + ), + "BoundCommandOutputSink": ("sqlbuild.runtime.output_capture.models", "BoundCommandOutputSink"), + "CommandOutputCaptureSummary": ( + "sqlbuild.runtime.output_capture.models", + "CommandOutputCaptureSummary", + ), + "CommandOutputRecord": ("sqlbuild.runtime.output_capture.models", "CommandOutputRecord"), + "OutputCaptureContext": ("sqlbuild.runtime.output_capture.models", "OutputCaptureContext"), + "CommandOutputStream": ("sqlbuild.runtime.output_capture.types", "CommandOutputStream"), + "OutputRecordPriority": ("sqlbuild.runtime.output_capture.types", "OutputRecordPriority"), + "SqlValueRenderingError": ("sqlbuild.sql_values.exceptions", "SqlValueRenderingError"), + "ASSET_SELECTION_COMMANDS": ( + "sqlbuild.integrations.dagster.constants", + "ASSET_SELECTION_COMMANDS", + ), + "CHECK_METADATA_EXCLUDED_KEYS": ( + "sqlbuild.integrations.dagster.constants", + "CHECK_METADATA_EXCLUDED_KEYS", + ), + "CLONE_COMMAND": ("sqlbuild.integrations.dagster.constants", "CLONE_COMMAND"), + "COMPLETED_EXECUTION_STATUSES": ( + "sqlbuild.integrations.dagster.constants", + "COMPLETED_EXECUTION_STATUSES", + ), + "DAGSTER_ASSET_ENABLED_FIELD": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_ASSET_ENABLED_FIELD", + ), + "DAGSTER_ASSET_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_ASSET_NODE_KINDS", + ), + "DAGSTER_CHECK_ENABLED_FIELD": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_CHECK_ENABLED_FIELD", + ), + "DAGSTER_CHECK_NAME_FIELD": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_CHECK_NAME_FIELD", + ), + "DAGSTER_DIRECT_KIND_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_DIRECT_KIND_NODE_KINDS", + ), + "DEFAULT_SELECTABLE_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "DEFAULT_SELECTABLE_NODE_KINDS", + ), + "EXPLICIT_SELECTION_FLAGS": ( + "sqlbuild.integrations.dagster.constants", + "EXPLICIT_SELECTION_FLAGS", + ), + "INSUFFICIENT_CHECK_STATUS": ( + "sqlbuild.integrations.dagster.constants", + "INSUFFICIENT_CHECK_STATUS", + ), + "LIVE_EVENT_COMMANDS": ("sqlbuild.integrations.dagster.constants", "LIVE_EVENT_COMMANDS"), + "LOADER_NODE_KIND": ("sqlbuild.integrations.dagster.constants", "LOADER_NODE_KIND"), + "LOAD_SELECTABLE_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "LOAD_SELECTABLE_NODE_KINDS", + ), + "MATERIALIZABLE_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "MATERIALIZABLE_NODE_KINDS", + ), + "MODEL_NODE_KIND": ("sqlbuild.integrations.dagster.constants", "MODEL_NODE_KIND"), + "SCENARIO_CHECK_KIND": ("sqlbuild.integrations.dagster.constants", "SCENARIO_CHECK_KIND"), + "SOURCE_NODE_KIND": ("sqlbuild.integrations.dagster.constants", "SOURCE_NODE_KIND"), + "STANDALONE_CHECK_COMMANDS": ( + "sqlbuild.integrations.dagster.constants", + "STANDALONE_CHECK_COMMANDS", + ), + "SUCCESS_EXECUTION_STATUS": ( + "sqlbuild.integrations.dagster.constants", + "SUCCESS_EXECUTION_STATUS", + ), + "VIEW_MATERIALIZATION_TYPE": ( + "sqlbuild.integrations.dagster.constants", + "VIEW_MATERIALIZATION_TYPE", + ), + "WARNING_CHECK_SEVERITY": ("sqlbuild.integrations.dagster.constants", "WARNING_CHECK_SEVERITY"), + "DagsterDagInputError": ("sqlbuild.integrations.dagster.exceptions", "DagsterDagInputError"), + "load_dbt_yaml": ("sqlbuild.integrations.dbt._helpers.profile.load", "load_dbt_yaml"), + "render_selected_dbt_profile_output": ( + "sqlbuild.integrations.dbt._helpers.profile.render", + "render_selected_dbt_profile_output", + ), + "DbtProfileError": ("sqlbuild.integrations.dbt.exceptions", "DbtProfileError"), + "DLT_INTEGRATION_KIND": ("sqlbuild.integrations.dlt.constants", "DLT_INTEGRATION_KIND"), + "DltIntegrationError": ("sqlbuild.integrations.dlt.exceptions", "DltIntegrationError"), + "dlt_loader_function": ("sqlbuild.integrations.dlt.main._loaders", "dlt_loader_function"), + "DltResourceConfig": ("sqlbuild.integrations.dlt.models", "DltResourceConfig"), + "DltSourceConfig": ("sqlbuild.integrations.dlt.models", "DltSourceConfig"), + "INGESTR_INTEGRATION_KIND": ( + "sqlbuild.integrations.ingestr.constants", + "INGESTR_INTEGRATION_KIND", + ), + "IngestrIntegrationError": ( + "sqlbuild.integrations.ingestr.exceptions", + "IngestrIntegrationError", + ), + "ingestr_loader_function": ( + "sqlbuild.integrations.ingestr.main._loaders", + "ingestr_loader_function", + ), + "IngestrSourceConfig": ("sqlbuild.integrations.ingestr.models", "IngestrSourceConfig"), + "RIVERS_ASSET_NODE_KINDS": ( + "sqlbuild.integrations.rivers.constants", + "RIVERS_ASSET_NODE_KINDS", + ), + "package_file": ("sqlbuild", "__file__"), + "dispatch_with_compute_logs": ( + "sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs", + "dispatch_with_compute_logs", + ), + "provider_settings_inputs": ( + "sqlbuild.cli.compile_reuse._helpers.provider_settings", + "provider_settings_inputs", + ), + "startup_inputs": ("sqlbuild.cli.compile_reuse._helpers.startup_inputs", "startup_inputs"), + "CompileReuseRequest": ("sqlbuild.cli.compile_reuse.models", "CompileReuseRequest"), +} + +__all__ = ("host_targets", *_TARGETS) + + +def host_targets() -> dict[str, tuple[str, str]]: + """Each host name with the module and attribute it resolves to.""" + return dict(_TARGETS) + + +def __getattr__(name: str) -> object: + """Resolve `name` from its target module on every read.""" + target: tuple[str, str] | None = _TARGETS.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + module, attribute = target + return getattr(importlib.import_module(module), attribute) diff --git a/src/sqlbuild/_native_host.pyi b/src/sqlbuild/_native_host.pyi new file mode 100644 index 0000000000..562d33924e --- /dev/null +++ b/src/sqlbuild/_native_host.pyi @@ -0,0 +1,576 @@ +"""The staying-Python entry points native code calls, resolved lazily by name.""" + +from sqlbuild.adapter.contract.classes.base_adapter import BaseAdapter as BaseAdapter +from sqlbuild.adapter.contract.exceptions import AdapterUserError as AdapterUserError +from sqlbuild.adapter.contract.models import ColumnInfo as ColumnInfo +from sqlbuild.adapter.contract.models import ( + ExpressionInferenceProfile as ExpressionInferenceProfile, +) +from sqlbuild.adapter.contract.models import RelationInfo as RelationInfo +from sqlbuild.adapter.contract.models import RelationLookup as RelationLookup +from sqlbuild.adapter.contract.models import RetentionRequest as RetentionRequest +from sqlbuild.adapter.contract.models import RowDiffSampling as RowDiffSampling +from sqlbuild.adapter.contract.models import RowDiffTolerance as RowDiffTolerance +from sqlbuild.adapter.contract.models import RowDiffTolerances as RowDiffTolerances +from sqlbuild.adapter.contract.models import TableFreshnessMetadata as TableFreshnessMetadata +from sqlbuild.adapter.contract.models import TableFreshnessRequest as TableFreshnessRequest +from sqlbuild.adapter.contract.types import BuiltinAdapter as BuiltinAdapter +from sqlbuild.adapter.contract.types import FrameworkType as FrameworkType +from sqlbuild.adapter.contract.types import LifeCycleEventKind as LifeCycleEventKind +from sqlbuild.adapter.contract.types import MigrationTransfer as MigrationTransfer +from sqlbuild.adapter.contract.types import RetentionScope as RetentionScope +from sqlbuild.adapter.contract.types import TableFreshnessStatus as TableFreshnessStatus +from sqlbuild.adapter.discovery.main._session_warehouse_support import ( + adapter_supports_session_warehouse as adapter_supports_session_warehouse, +) +from sqlbuild.adapter.discovery.main.builtins import ( + builtin_adapter_classes as builtin_adapter_classes, +) +from sqlbuild.adapter.discovery.main.project_adapters import ( + discover_project_adapters as discover_project_adapters, +) +from sqlbuild.adapter.discovery.main.resolve_adapter import resolve_adapter as resolve_adapter +from sqlbuild.adapter.relations.main._get_columns_for_inspection import ( + get_columns_for_inspection as get_columns_for_inspection, +) +from sqlbuild.adapter.relations.main._list_relations_for_inspection import ( + list_relations_for_inspection as list_relations_for_inspection, +) +from sqlbuild.adapter.relations.main.active_inspection_catalog import ( + active_inspection_catalog as active_inspection_catalog, +) +from sqlbuild.adapter.relations.main.inspection_query_sink import ( + inspection_query_sink as inspection_query_sink, +) +from sqlbuild.adapter.relations.main.open_inspection_catalog import ( + open_inspection_catalog as open_inspection_catalog, +) +from sqlbuild.adapter.relations.main.record_inspection_query import ( + record_inspection_query as record_inspection_query, +) +from sqlbuild.adapter.relations.main.relation_lookup import ( + build_relation_lookup as build_relation_lookup, +) +from sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name import ( + resolve_relation_location_qualified_name as resolve_relation_location_qualified_name, +) +from sqlbuild.adapter.relations.main.run_bounded_inspections import ( + run_bounded_inspections as run_bounded_inspections, +) +from sqlbuild.adapter.relations.main.run_recorded_inspection_query import ( + run_recorded_inspection_query as run_recorded_inspection_query, +) +from sqlbuild.adapter.relations.models import InspectionQueryRecord as InspectionQueryRecord +from sqlbuild.adapter.relations.models import StatementMetadataEffect as StatementMetadataEffect +from sqlbuild.adapter.type_system.main.conditional_result_nullability import ( + conditional_result_nullability as conditional_result_nullability, +) +from sqlbuild.adapter.type_system.main.first_arg_nullability import ( + first_arg_nullability as first_arg_nullability, +) +from sqlbuild.adapter.type_system.main.normalize_relation_type import ( + normalize_relation_type as normalize_relation_type, +) +from sqlbuild.adapter.type_system.main.normalize_type import normalize_type as normalize_type +from sqlbuild.adapter.type_system.main.safe_cast_nullability import ( + safe_cast_nullability as safe_cast_nullability, +) +from sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs import ( + dispatch_with_compute_logs as dispatch_with_compute_logs, +) +from sqlbuild.cli.compile_reuse._helpers.provider_settings import ( + provider_settings_inputs as provider_settings_inputs, +) +from sqlbuild.cli.compile_reuse._helpers.startup_inputs import startup_inputs as startup_inputs +from sqlbuild.cli.compile_reuse.models import CompileReuseRequest as CompileReuseRequest +from sqlbuild.compiler.compile._helpers.diagnostics.python_resources import ( + python_resource_rows as python_resource_rows, +) +from sqlbuild.compiler.compile._helpers.macro_bridge.call_store import ( + target_context_with_macro_call_store as target_context_with_macro_call_store, +) +from sqlbuild.compiler.compile._helpers.render.user_macros import ( + inventory_project_macros as inventory_project_macros, +) +from sqlbuild.compiler.compile._helpers.render.user_macros import ( + load_project_macros as load_project_macros, +) +from sqlbuild.compiler.compile._helpers.render.user_macros import ( + macro_invocation_context as macro_invocation_context, +) +from sqlbuild.compiler.discovery._helpers.filesystem.command_output_sinks import ( + discover_command_output_sink_functions as discover_command_output_sink_functions, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + import_python_root_module as import_python_root_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + install_python_root_package as install_python_root_package, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_materialize_function as load_materialize_function, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_provider_module as load_provider_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_python_node_module as load_python_node_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_sink_module as load_sink_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + module_functions as module_functions, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + module_provider_classes as module_provider_classes, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + parameter_annotations as parameter_annotations, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + provider_usages as provider_usages, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + requires_arguments as requires_arguments, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + restore_python_root_modules as restore_python_root_modules, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + source_line as source_line, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + validate_event_exporter_signature as validate_event_exporter_signature, +) +from sqlbuild.compiler.discovery._helpers.validation.discovery import ( + validate_discovered_inputs as validate_discovered_inputs, +) +from sqlbuild.compiler.discovery.main.runtime_extensions import ( + discover_runtime_extensions as discover_runtime_extensions, +) +from sqlbuild.compiler.macro_bridge.classes.macro_bridge import ( + run_with_macro_bridge as run_with_macro_bridge, +) +from sqlbuild.compiler.macro_bridge.main._macro_store_module_paths import ( + macro_store_module_paths as macro_store_module_paths, +) +from sqlbuild.compiler.macro_bridge.main.active_macro_bridge import ( + active_macro_bridge as active_macro_bridge, +) +from sqlbuild.compiler.pipeline._helpers.materializations import ( + load_custom_materializations as load_custom_materializations, +) +from sqlbuild.compiler.python_nodes._helpers.user_inventory import ( + python_node_dependency_key as python_node_dependency_key, +) +from sqlbuild.compiler.python_nodes._helpers.user_inventory import ( + python_node_identity as python_node_identity, +) +from sqlbuild.compiler.python_nodes.main._hook_identities import ( + build_hook_identities as build_hook_identities, +) +from sqlbuild.compiler.python_nodes.types import PythonNodeKind as PythonNodeKind +from sqlbuild.compiler.python_nodes.types import PythonNodeStatus as PythonNodeStatus +from sqlbuild.cost.models import CostResourceContext as CostResourceContext +from sqlbuild.cost.models import RunCostSummary as RunCostSummary +from sqlbuild.cost.types import CostCapability as CostCapability +from sqlbuild.cost.types import CostStatus as CostStatus +from sqlbuild.cursor_algebra.types import BoundSentinel as BoundSentinel +from sqlbuild.diagnostics._helpers.process_resources import ( + read_process_resources as read_process_resources, +) +from sqlbuild.diagnostics.constants import SQL_PRIVATE_RECORD_MARKER as SQL_PRIVATE_RECORD_MARKER +from sqlbuild.diagnostics.main.diagnostics_context import diagnostics_context as diagnostics_context +from sqlbuild.diagnostics.main.log_debug_event import log_debug_event as log_debug_event +from sqlbuild.diagnostics.main.log_sql import log_sql as log_sql +from sqlbuild.diagnostics.main.process_resource_reporting import ( + process_resource_reporting as process_resource_reporting, +) +from sqlbuild.diagnostics.models import ProcessResourceUsage as ProcessResourceUsage +from sqlbuild.errors.contracts.exceptions import ExecutorInputError as ExecutorInputError +from sqlbuild.executor.auditing.main._current_audit_result_projection import ( + current_audit_result_projection as current_audit_result_projection, +) +from sqlbuild.executor.auditing.models import AuditExecutionResult as AuditExecutionResult +from sqlbuild.executor.build.models import BuildCallbacks as BuildCallbacks +from sqlbuild.executor.build.models import BuildCustomizations as BuildCustomizations +from sqlbuild.executor.build.models import BuildExecutionResult as BuildExecutionResult +from sqlbuild.executor.build.models import BuildInitialState as BuildInitialState +from sqlbuild.executor.build.models import BuildRuntimeParams as BuildRuntimeParams +from sqlbuild.executor.build.models import FunctionExecutionResult as FunctionExecutionResult +from sqlbuild.executor.build.models import SeedExecutionResult as SeedExecutionResult +from sqlbuild.executor.build.types import BuildStatus as BuildStatus +from sqlbuild.executor.clone.main._build_destination_transient_models import ( + build_destination_transient_models as build_destination_transient_models, +) +from sqlbuild.executor.clone.main._build_retention_requests import ( + build_destination_retention_requests as build_destination_retention_requests, +) +from sqlbuild.executor.clone.main._fingerprinting import ( + copy_clone_fingerprints as copy_clone_fingerprints, +) +from sqlbuild.executor.clone.main._run_prephase_clone_stream import ( + run_prephase_clone_stream as run_prephase_clone_stream, +) +from sqlbuild.executor.clone.main.execute import execute_clone as execute_clone +from sqlbuild.executor.clone.models import CloneExecutionInput as CloneExecutionInput +from sqlbuild.executor.clone.models import CloneItemResult as CloneItemResult +from sqlbuild.executor.clone.models import CloneSourceEntries as CloneSourceEntries +from sqlbuild.executor.clone.types import CloneStatus as CloneStatus +from sqlbuild.executor.diff.classes.query_artifact_lifecycle import ( + QueryDiffArtifactLifecycle as QueryDiffArtifactLifecycle, +) +from sqlbuild.executor.diff.exceptions import FullDiffSizeGuardError as FullDiffSizeGuardError +from sqlbuild.executor.diff.main._execute_query import execute_query_diff as execute_query_diff +from sqlbuild.executor.diff.main.execute import execute_diff as execute_diff +from sqlbuild.executor.diff.models import DiffExecutionOptions as DiffExecutionOptions +from sqlbuild.executor.diff.models import DiffExecutionResult as DiffExecutionResult +from sqlbuild.executor.diff.models import FullDiffSizeLimits as FullDiffSizeLimits +from sqlbuild.executor.diff.models import RowDiffSamplingOverride as RowDiffSamplingOverride +from sqlbuild.executor.janitor.main.execute import execute_janitor_plan as execute_janitor_plan +from sqlbuild.executor.janitor.models import JanitorArchiveCandidate as JanitorArchiveCandidate +from sqlbuild.executor.janitor.models import JanitorArchivedRelation as JanitorArchivedRelation +from sqlbuild.executor.janitor.models import JanitorBlockedSchema as JanitorBlockedSchema +from sqlbuild.executor.janitor.models import JanitorDeleteCandidate as JanitorDeleteCandidate +from sqlbuild.executor.janitor.models import ( + JanitorDirectStatePruneCandidate as JanitorDirectStatePruneCandidate, +) +from sqlbuild.executor.janitor.models import JanitorOldNameView as JanitorOldNameView +from sqlbuild.executor.janitor.models import ( + JanitorOldNameViewPlanning as JanitorOldNameViewPlanning, +) +from sqlbuild.executor.janitor.models import JanitorPlan as JanitorPlan +from sqlbuild.executor.janitor.models import ( + JanitorQueryDiffArtifactCandidate as JanitorQueryDiffArtifactCandidate, +) +from sqlbuild.executor.janitor.models import JanitorRelationKey as JanitorRelationKey +from sqlbuild.executor.janitor.models import JanitorSkippedRelation as JanitorSkippedRelation +from sqlbuild.executor.janitor.models import JanitorSkippedSchema as JanitorSkippedSchema +from sqlbuild.executor.load.main.run import run_load_pipeline as run_load_pipeline +from sqlbuild.executor.load.models import LoadCallbacks as LoadCallbacks +from sqlbuild.executor.load.models import LoadExecutionResult as LoadExecutionResult +from sqlbuild.executor.load.models import LoadRuntimeParams as LoadRuntimeParams +from sqlbuild.executor.migrations._helpers.relation_for_location import ( + migration_relation_for_location as migration_relation_for_location, +) +from sqlbuild.executor.old_name_views._helpers.janitor import ( + is_compatibility_view as is_compatibility_view, +) +from sqlbuild.executor.pipeline.exceptions import AuditExecutionError as AuditExecutionError +from sqlbuild.executor.pipeline.main.run import AuditPipelineCallbacks as AuditPipelineCallbacks +from sqlbuild.executor.pipeline.main.run import ( + run_audit_pipeline_with_callbacks as run_audit_pipeline_with_callbacks, +) +from sqlbuild.executor.pipeline.main.run import run_build_pipeline as run_build_pipeline +from sqlbuild.executor.pipeline.main.run import ( + run_scenario_capture_pipeline as run_scenario_capture_pipeline, +) +from sqlbuild.executor.pipeline.main.run import ( + run_scenario_local_test_pipeline as run_scenario_local_test_pipeline, +) +from sqlbuild.executor.pipeline.main.run import ( + run_scenario_test_pipeline as run_scenario_test_pipeline, +) +from sqlbuild.executor.pipeline.main.run import run_seed_pipeline as run_seed_pipeline +from sqlbuild.executor.pipeline.main.run import run_test_pipeline as run_test_pipeline +from sqlbuild.executor.pipeline.main.run import ( + select_scenario_snapshot_capture_candidates as select_scenario_snapshot_capture_candidates, +) +from sqlbuild.executor.pipeline.models import TestPipelineCallbacks as TestPipelineCallbacks +from sqlbuild.executor.python_nodes.main._checks import ( + execute_python_checks as execute_python_checks, +) +from sqlbuild.executor.python_nodes.main._ingress import ( + execute_ingress_python_loader_nodes as execute_ingress_python_loader_nodes, +) +from sqlbuild.executor.python_nodes.main._ingress import ( + run_ingress_python_loader_nodes as run_ingress_python_loader_nodes, +) +from sqlbuild.executor.python_nodes.main.read_side import ( + create_read_side_python_execution_tracker as create_read_side_python_execution_tracker, +) +from sqlbuild.executor.python_nodes.models import IngressCallbacks as IngressCallbacks +from sqlbuild.executor.python_nodes.models import PythonCheckCallbacks as PythonCheckCallbacks +from sqlbuild.executor.python_nodes.models import ( + PythonCheckExecutionResult as PythonCheckExecutionResult, +) +from sqlbuild.executor.python_nodes.models import ( + PythonNodeExecutionResult as PythonNodeExecutionResult, +) +from sqlbuild.executor.python_nodes.models import PythonNodeRunState as PythonNodeRunState +from sqlbuild.executor.python_nodes.models import PythonNodeRuntime as PythonNodeRuntime +from sqlbuild.executor.run.models import ModelExecutionResult as ModelExecutionResult +from sqlbuild.executor.scenario.models import ScenarioCaptureSettings as ScenarioCaptureSettings +from sqlbuild.executor.scenario.models import ScenarioLocalReplaySource as ScenarioLocalReplaySource +from sqlbuild.executor.scenario.models import ( + ScenarioSnapshotCaptureLimits as ScenarioSnapshotCaptureLimits, +) +from sqlbuild.executor.scheduling.types import ExecutionStatus as ExecutionStatus +from sqlbuild.executor.testing.models import SqlTestExecutionResult as SqlTestExecutionResult +from sqlbuild.integrations.dagster.constants import ( + ASSET_SELECTION_COMMANDS as ASSET_SELECTION_COMMANDS, +) +from sqlbuild.integrations.dagster.constants import ( + CHECK_METADATA_EXCLUDED_KEYS as CHECK_METADATA_EXCLUDED_KEYS, +) +from sqlbuild.integrations.dagster.constants import CLONE_COMMAND as CLONE_COMMAND +from sqlbuild.integrations.dagster.constants import ( + COMPLETED_EXECUTION_STATUSES as COMPLETED_EXECUTION_STATUSES, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_ASSET_ENABLED_FIELD as DAGSTER_ASSET_ENABLED_FIELD, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_ASSET_NODE_KINDS as DAGSTER_ASSET_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_CHECK_ENABLED_FIELD as DAGSTER_CHECK_ENABLED_FIELD, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_CHECK_NAME_FIELD as DAGSTER_CHECK_NAME_FIELD, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_DIRECT_KIND_NODE_KINDS as DAGSTER_DIRECT_KIND_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import ( + DEFAULT_SELECTABLE_NODE_KINDS as DEFAULT_SELECTABLE_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import ( + EXPLICIT_SELECTION_FLAGS as EXPLICIT_SELECTION_FLAGS, +) +from sqlbuild.integrations.dagster.constants import ( + INSUFFICIENT_CHECK_STATUS as INSUFFICIENT_CHECK_STATUS, +) +from sqlbuild.integrations.dagster.constants import LIVE_EVENT_COMMANDS as LIVE_EVENT_COMMANDS +from sqlbuild.integrations.dagster.constants import ( + LOAD_SELECTABLE_NODE_KINDS as LOAD_SELECTABLE_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import LOADER_NODE_KIND as LOADER_NODE_KIND +from sqlbuild.integrations.dagster.constants import ( + MATERIALIZABLE_NODE_KINDS as MATERIALIZABLE_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import MODEL_NODE_KIND as MODEL_NODE_KIND +from sqlbuild.integrations.dagster.constants import SCENARIO_CHECK_KIND as SCENARIO_CHECK_KIND +from sqlbuild.integrations.dagster.constants import SOURCE_NODE_KIND as SOURCE_NODE_KIND +from sqlbuild.integrations.dagster.constants import ( + STANDALONE_CHECK_COMMANDS as STANDALONE_CHECK_COMMANDS, +) +from sqlbuild.integrations.dagster.constants import ( + SUCCESS_EXECUTION_STATUS as SUCCESS_EXECUTION_STATUS, +) +from sqlbuild.integrations.dagster.constants import ( + VIEW_MATERIALIZATION_TYPE as VIEW_MATERIALIZATION_TYPE, +) +from sqlbuild.integrations.dagster.constants import WARNING_CHECK_SEVERITY as WARNING_CHECK_SEVERITY +from sqlbuild.integrations.dagster.exceptions import DagsterDagInputError as DagsterDagInputError +from sqlbuild.integrations.dbt._helpers.profile.load import load_dbt_yaml as load_dbt_yaml +from sqlbuild.integrations.dbt._helpers.profile.render import ( + render_selected_dbt_profile_output as render_selected_dbt_profile_output, +) +from sqlbuild.integrations.dbt.exceptions import DbtProfileError as DbtProfileError +from sqlbuild.integrations.dlt.constants import DLT_INTEGRATION_KIND as DLT_INTEGRATION_KIND +from sqlbuild.integrations.dlt.exceptions import DltIntegrationError as DltIntegrationError +from sqlbuild.integrations.dlt.main._loaders import dlt_loader_function as dlt_loader_function +from sqlbuild.integrations.dlt.models import DltResourceConfig as DltResourceConfig +from sqlbuild.integrations.dlt.models import DltSourceConfig as DltSourceConfig +from sqlbuild.integrations.ingestr.constants import ( + INGESTR_INTEGRATION_KIND as INGESTR_INTEGRATION_KIND, +) +from sqlbuild.integrations.ingestr.exceptions import ( + IngestrIntegrationError as IngestrIntegrationError, +) +from sqlbuild.integrations.ingestr.main._loaders import ( + ingestr_loader_function as ingestr_loader_function, +) +from sqlbuild.integrations.ingestr.models import IngestrSourceConfig as IngestrSourceConfig +from sqlbuild.integrations.rivers.constants import ( + RIVERS_ASSET_NODE_KINDS as RIVERS_ASSET_NODE_KINDS, +) +from sqlbuild.microbatches.classes.event_codec import MicrobatchEventCodec as MicrobatchEventCodec +from sqlbuild.microbatches.constants import MICROBATCH_COLUMN_TYPES as MICROBATCH_COLUMN_TYPES +from sqlbuild.microbatches.constants import MICROBATCH_COLUMNS as MICROBATCH_COLUMNS +from sqlbuild.microbatches.constants import MICROBATCH_DIRECT_INDEXES as MICROBATCH_DIRECT_INDEXES +from sqlbuild.microbatches.constants import ( + MICROBATCH_GENERATION_WILDCARD as MICROBATCH_GENERATION_WILDCARD, +) +from sqlbuild.microbatches.constants import MICROBATCH_TABLE_NAME as MICROBATCH_TABLE_NAME +from sqlbuild.microbatches.exceptions import MicrobatchStateError as MicrobatchStateError +from sqlbuild.microbatches.models import ( + MicrobatchCoverageProjection as MicrobatchCoverageProjection, +) +from sqlbuild.microbatches.models import MicrobatchInterval as MicrobatchInterval +from sqlbuild.microbatches.models import ProjectedMicrobatchInterval as ProjectedMicrobatchInterval +from sqlbuild.microbatches.models import ReplayRequirementProjection as ReplayRequirementProjection +from sqlbuild.microbatches.types import ReplayRequirementState as ReplayRequirementState +from sqlbuild.provider.exceptions import ProviderInputError as ProviderInputError +from sqlbuild.provider.main.session import build_provider_session as build_provider_session +from sqlbuild.python_nodes.main.read_asset_definition import ( + read_asset_definition as read_asset_definition, +) +from sqlbuild.python_nodes.main.read_audit_factory_definition import ( + read_audit_factory_definition as read_audit_factory_definition, +) +from sqlbuild.python_nodes.main.read_check_definition import ( + read_check_definition as read_check_definition, +) +from sqlbuild.python_nodes.main.read_factory_definition import ( + read_factory_definition as read_factory_definition, +) +from sqlbuild.python_nodes.main.read_hook_definition import ( + read_hook_definition as read_hook_definition, +) +from sqlbuild.python_nodes.main.read_loader_definition import ( + read_loader_definition as read_loader_definition, +) +from sqlbuild.python_nodes.main.read_task_definition import ( + read_task_definition as read_task_definition, +) +from sqlbuild.python_nodes.models import RELATION_RENDERER as RELATION_RENDERER +from sqlbuild.python_nodes.models import AuditCase as AuditCase +from sqlbuild.python_nodes.models import SqlResourceRef as SqlResourceRef +from sqlbuild.python_nodes.types import PythonCheckSeverity as PythonCheckSeverity +from sqlbuild.python_nodes.types import SqlResourceRefKind as SqlResourceRefKind +from sqlbuild.rule_engine._helpers.engine._custom_catalogue import ( + load_custom_rules as load_custom_rules, +) +from sqlbuild.rule_engine._helpers.engine.custom_payloads import ( + custom_rule_payloads as custom_rule_payloads, +) +from sqlbuild.rule_engine._helpers.engine.fact_replay import ( + fact_outcome_digest as fact_outcome_digest, +) +from sqlbuild.rule_engine._helpers.engine.fact_replay import full_fact_keys as full_fact_keys +from sqlbuild.rule_engine._helpers.engine.fact_replay import is_tree_fact as is_tree_fact +from sqlbuild.rule_engine._helpers.engine.fact_replay import ( + shared_fact_encoder as shared_fact_encoder, +) +from sqlbuild.rule_engine.classes.rule_context import build_rule_fact_views as build_rule_fact_views +from sqlbuild.runtime.compute_logs.classes.text_tee import TextComputeLogTee as TextComputeLogTee +from sqlbuild.runtime.compute_logs.exceptions import ( + CaptureAlreadyExistsError as CaptureAlreadyExistsError, +) +from sqlbuild.runtime.compute_logs.exceptions import CaptureNotFoundError as CaptureNotFoundError +from sqlbuild.runtime.compute_logs.exceptions import CaptureStateError as CaptureStateError +from sqlbuild.runtime.compute_logs.exceptions import ( + ComputeLogMetadataError as ComputeLogMetadataError, +) +from sqlbuild.runtime.compute_logs.exceptions import ComputeLogPathError as ComputeLogPathError +from sqlbuild.runtime.compute_logs.exceptions import InvalidCaptureIdError as InvalidCaptureIdError +from sqlbuild.runtime.compute_logs.exceptions import ( + InvalidComputeLogCursorError as InvalidComputeLogCursorError, +) +from sqlbuild.runtime.compute_logs.exceptions import ( + InvalidComputeLogLimitError as InvalidComputeLogLimitError, +) +from sqlbuild.runtime.compute_logs.models import CaptureByteCounts as CaptureByteCounts +from sqlbuild.runtime.compute_logs.models import CaptureInventory as CaptureInventory +from sqlbuild.runtime.compute_logs.models import CaptureInventoryItem as CaptureInventoryItem +from sqlbuild.runtime.compute_logs.models import CaptureMetadata as CaptureMetadata +from sqlbuild.runtime.compute_logs.models import ComputeLogReadChunk as ComputeLogReadChunk +from sqlbuild.runtime.compute_logs.models import FinalCaptureMetadata as FinalCaptureMetadata +from sqlbuild.runtime.compute_logs.models import PruneResult as PruneResult +from sqlbuild.runtime.compute_logs.types import ComputeLogStream as ComputeLogStream +from sqlbuild.runtime.contracts.models import ConnectionHooks as ConnectionHooks +from sqlbuild.runtime.contracts.types import ExecutionResourceKind as ExecutionResourceKind +from sqlbuild.runtime.event_exporting.exceptions import ( + EventExporterInputError as EventExporterInputError, +) +from sqlbuild.runtime.event_exporting.exceptions import ( + EventExporterStateError as EventExporterStateError, +) +from sqlbuild.runtime.event_exporting.main._invoke_lifecycle_event_sink import ( + invoke_lifecycle_event_sink as invoke_lifecycle_event_sink, +) +from sqlbuild.runtime.event_exporting.main._run_command_output_sinks import ( + run_command_output_sinks as run_command_output_sinks, +) +from sqlbuild.runtime.event_exporting.main.get_lifecycle_event_sink_definition import ( + get_lifecycle_event_sink_definition as get_lifecycle_event_sink_definition, +) +from sqlbuild.runtime.execution_history.exceptions import ( + ExecutionHistoryStorageError as ExecutionHistoryStorageError, +) +from sqlbuild.runtime.execution_history.exceptions import ( + IntegrityConflictError as IntegrityConflictError, +) +from sqlbuild.runtime.execution_history.exceptions import InvalidCursorError as InvalidCursorError +from sqlbuild.runtime.execution_history.exceptions import InvalidEventError as InvalidEventError +from sqlbuild.runtime.execution_history.exceptions import InvalidFilterError as InvalidFilterError +from sqlbuild.runtime.execution_history.exceptions import InvalidLimitError as InvalidLimitError +from sqlbuild.runtime.execution_history.exceptions import ( + ProjectionConsistencyError as ProjectionConsistencyError, +) +from sqlbuild.runtime.execution_history.exceptions import ( + UnsupportedSchemaVersionError as UnsupportedSchemaVersionError, +) +from sqlbuild.runtime.execution_history.models import EventPage as EventPage +from sqlbuild.runtime.execution_history.models import RunPage as RunPage +from sqlbuild.runtime.execution_history.models import RunRecord as RunRecord +from sqlbuild.runtime.execution_history.models import StoredEvent as StoredEvent +from sqlbuild.runtime.execution_history.types import RunStatus as RunStatus +from sqlbuild.runtime.execution_limits.exceptions import ( + ExecutionDurationLimitError as ExecutionDurationLimitError, +) +from sqlbuild.runtime.execution_limits.main.execution_deadline_scope import ( + execution_deadline_scope as execution_deadline_scope, +) +from sqlbuild.runtime.observability.classes.event_dispatcher import ( + EventDispatcher as EventDispatcher, +) +from sqlbuild.runtime.observability.classes.operation_lifecycle import ( + OperationLifecycle as OperationLifecycle, +) +from sqlbuild.runtime.observability.exceptions import ( + ObservabilityValidationError as ObservabilityValidationError, +) +from sqlbuild.runtime.observability.main.create_lifecycle_event import ( + create_lifecycle_event as create_lifecycle_event, +) +from sqlbuild.runtime.observability.main.current_execution_identity import ( + current_execution_identity as current_execution_identity, +) +from sqlbuild.runtime.observability.main.diagnostic_log_to_json import ( + diagnostic_log_to_json as diagnostic_log_to_json, +) +from sqlbuild.runtime.observability.main.dispatcher_scope import ( + dispatcher_scope as dispatcher_scope, +) +from sqlbuild.runtime.observability.main.invocation_external_context_scope import ( + invocation_external_context_scope as invocation_external_context_scope, +) +from sqlbuild.runtime.observability.main.invocation_scope import ( + invocation_scope as invocation_scope, +) +from sqlbuild.runtime.observability.main.lifecycle_event_from_json import ( + lifecycle_event_from_json as lifecycle_event_from_json, +) +from sqlbuild.runtime.observability.main.lifecycle_event_to_json import ( + lifecycle_event_to_json as lifecycle_event_to_json, +) +from sqlbuild.runtime.observability.main.run_scope import run_scope as run_scope +from sqlbuild.runtime.observability.models import DiagnosticLog as DiagnosticLog +from sqlbuild.runtime.observability.models import LifecycleEvent as LifecycleEvent +from sqlbuild.runtime.observability.models import OperationAttributes as OperationAttributes +from sqlbuild.runtime.output_capture.exceptions import ( + CommandOutputValidationError as CommandOutputValidationError, +) +from sqlbuild.runtime.output_capture.exceptions import ( + OutputCaptureInputError as OutputCaptureInputError, +) +from sqlbuild.runtime.output_capture.main.configured_output_capture_scope import ( + configured_output_capture_scope as configured_output_capture_scope, +) +from sqlbuild.runtime.output_capture.models import BoundCommandOutputSink as BoundCommandOutputSink +from sqlbuild.runtime.output_capture.models import ( + CommandOutputCaptureSummary as CommandOutputCaptureSummary, +) +from sqlbuild.runtime.output_capture.models import CommandOutputRecord as CommandOutputRecord +from sqlbuild.runtime.output_capture.models import OutputCaptureContext as OutputCaptureContext +from sqlbuild.runtime.output_capture.types import CommandOutputStream as CommandOutputStream +from sqlbuild.runtime.output_capture.types import OutputRecordPriority as OutputRecordPriority +from sqlbuild.sql_values.exceptions import SqlValueRenderingError as SqlValueRenderingError + +package_file: str + +def host_targets() -> dict[str, tuple[str, str]]: ... diff --git a/tests/unit/scripts/native_module_paths/_helpers/_test_types.py b/tests/unit/scripts/native_module_paths/_helpers/_test_types.py index db04ab1cdc..7040d2fcc8 100644 --- a/tests/unit/scripts/native_module_paths/_helpers/_test_types.py +++ b/tests/unit/scripts/native_module_paths/_helpers/_test_types.py @@ -13,3 +13,30 @@ class RepositoryModulePathTestCase: description: str expected_errors: tuple[str, ...] expected_logger_names: tuple[str, ...] + + +@dataclass(frozen=True) +class HostTableCase: + description: str + expected_duplicate_count: int + expected_missing_names: tuple[str, ...] + expected_extra_names: tuple[str, ...] + + +@dataclass(frozen=True) +class HostKindCase: + description: str + expected_mismatched_names: tuple[str, ...] + + +@dataclass(frozen=True) +class HostImportCase: + description: str + expected_loaded_modules: tuple[str, ...] + + +@dataclass(frozen=True) +class HostUnknownNameCase: + description: str + name: str + expected_error: str diff --git a/tests/unit/scripts/native_module_paths/_helpers/helpers.py b/tests/unit/scripts/native_module_paths/_helpers/helpers.py index f8c3865cc3..facfc3700e 100644 --- a/tests/unit/scripts/native_module_paths/_helpers/helpers.py +++ b/tests/unit/scripts/native_module_paths/_helpers/helpers.py @@ -7,3 +7,18 @@ def native_source_errors(source: str) -> tuple[str, ...]: """Scan one synthetic Rust file and resolve what it imports.""" return native_module_path_errors(scan=scan_native_source(source=source, label=SOURCE_LABEL)) + + +KIND_BY_TRAITS: dict[tuple[bool, bool, bool], str] = { + (True, True, True): "exception", + (True, False, True): "class", + (False, False, True): "function", + (False, False, False): "value", +} + + +def resolved_kind(value: object) -> str: + """The host kind of a resolved entry: class, exception, function or value.""" + is_class: bool = isinstance(value, type) + is_exception: bool = BaseException in getattr(value, "__mro__", ()) + return KIND_BY_TRAITS[(is_class, is_exception, callable(value))] diff --git a/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py b/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py index 936a3d35a1..f8e98b8787 100644 --- a/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py +++ b/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py @@ -46,6 +46,18 @@ ), expected_errors=(), ), + NativeSourceModulePathTestCase( + description="host table entry resolves through the host module", + source='const ENTRIES: &[HostEntry] = &[\n HostEntry {\n name: "BaseAdapter",\n },\n];\n', + expected_errors=(), + ), + NativeSourceModulePathTestCase( + description="host table entry the host module does not define", + source='const ENTRIES: &[HostEntry] = &[\n HostEntry {\n name: "no_such_entry",\n },\n];\n', + expected_errors=( + "lib.rs:2: 'sqlbuild._native_host' has no attribute 'no_such_entry'.", + ), + ), NativeSourceModulePathTestCase( description="unknown module literal", source='const MISSING_MODULE: &str = "sqlbuild.cli.no_such_module";\n', diff --git a/tests/unit/scripts/native_module_paths/_helpers/test_native_host.py b/tests/unit/scripts/native_module_paths/_helpers/test_native_host.py new file mode 100644 index 0000000000..5ff77bc5ee --- /dev/null +++ b/tests/unit/scripts/native_module_paths/_helpers/test_native_host.py @@ -0,0 +1,119 @@ +"""`sqlbuild._native_host` agrees with the native host table and imports its targets lazily.""" + +from __future__ import annotations + +import json +import subprocess +import sys + +import pytest + +import sqlbuild._native as native +import sqlbuild._native_host as native_host +from tests.unit.scripts.native_module_paths._helpers._test_types import ( + HostImportCase, + HostKindCase, + HostTableCase, + HostUnknownNameCase, +) +from tests.unit.scripts.native_module_paths._helpers.helpers import resolved_kind + + +@pytest.mark.parametrize( + "test_case", + [ + HostTableCase( + description="host names equal the native table", + expected_duplicate_count=0, + expected_missing_names=(), + expected_extra_names=(), + ) + ], + ids=lambda case: case.description, +) +def test_given_native_host_table_when_comparing_names_then_python_and_rust_agree( + test_case: HostTableCase, +) -> None: + listed: list[str] = [name for name, _, _ in native.HOST_ENTRIES] + native_names: set[str] = set(listed) + host_names: set[str] = set(native_host.host_targets()) + + assert len(listed) - len(native_names) == test_case.expected_duplicate_count + assert tuple(sorted(native_names - host_names)) == test_case.expected_missing_names + assert tuple(sorted(host_names - native_names)) == test_case.expected_extra_names + + +@pytest.mark.parametrize( + "test_case", + [ + HostKindCase( + description="every entry resolves to its declared kind", expected_mismatched_names=() + ) + ], + ids=lambda case: case.description, +) +def test_given_native_host_table_when_resolving_entries_then_each_has_its_declared_kind( + test_case: HostKindCase, +) -> None: + declared: dict[str, str] = {name: kind for name, kind, _ in native.HOST_ENTRIES} + resolved: dict[str, str] = { + name: resolved_kind(getattr(native_host, name)) for name in declared + } + + mismatched: set[tuple[str, str]] = set(resolved.items()) ^ set(declared.items()) + + assert tuple(sorted({name for name, _ in mismatched})) == test_case.expected_mismatched_names + + +@pytest.mark.parametrize( + "test_case", + [ + HostImportCase( + description="importing the host module loads none of its targets", + expected_loaded_modules=(), + ) + ], + ids=lambda case: case.description, +) +def test_given_clean_interpreter_when_importing_native_host_then_no_target_module_loads( + test_case: HostImportCase, +) -> None: + script: str = ( + "import json, sys\n" + "import sqlbuild\n" + "before = set(sys.modules)\n" + "import sqlbuild._native_host as host\n" + "targets = {module for module, _ in host.host_targets().values()}\n" + "loaded = sorted(targets & (set(sys.modules) - before))\n" + "print(json.dumps(loaded))\n" + ) + + result: subprocess.CompletedProcess[str] = subprocess.run( + [sys.executable, "-c", script], check=True, capture_output=True, text=True + ) + + assert tuple(json.loads(result.stdout)) == test_case.expected_loaded_modules + + +@pytest.mark.parametrize( + "test_case", + [ + HostUnknownNameCase( + description="a name outside the table is not an attribute", + name="no_such_entry", + expected_error="module 'sqlbuild._native_host' has no attribute 'no_such_entry'", + ) + ], + ids=lambda case: case.description, +) +def test_given_unknown_name_when_reading_native_host_then_raises_attribute_error( + test_case: HostUnknownNameCase, +) -> None: + with pytest.raises(AttributeError) as raised: + getattr(native_host, test_case.name) + + assert str(raised.value) == test_case.expected_error + + +if __name__ == "__main__": + pytest.main([__file__, "-vv"]) From 73ce77626e07914f77dcea597e6eb15f7a82c92a Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Fri, 16 Oct 2026 06:12:29 +0100 Subject: [PATCH 2/4] refactor: share generic graph walks from sqlbuild-core Add `sqlbuild_core::graph_walk` with walks generic over the key type and the visited set: breadth-first reach (optional depth), directed path nodes, shortest path to a goal, Kahn order with a caller tie-break queue, depth-first post-order and depth-first cycle search. Replace the per-crate copies: closures and path nodes over DAG artifacts, the project graph, lineage commands, dbt graphs, Python selection, diff selection, Python-keyed graph edges, loader graphs, SQL-test CTE reads and helper scopes, macro export dependencies, rule import closures and type recovery; Kahn orders for planner keys, Dagster events and macro module loads; post-orders for loaders, SQL-test model chains and migration discovery; and cycle searches for Python nodes and macros. Each caller keeps its traversal order, tie-breaks and messages. --- .../_helpers/sql_tests/check_independence.rs | 28 +- .../_helpers/sql_tests/helper_scope.rs | 46 +-- .../compiler/_helpers/sql_tests/mock_reads.rs | 38 +-- .../compiler/_helpers/sql_tests/planning.rs | 96 ++----- .../src/dag_artifact/_helpers/selection.rs | 60 ++-- .../src/diff_selection/_helpers/tokens.rs | 16 +- .../src/explicit_references/_helpers/graph.rs | 26 +- .../src/graph/_helpers/closure.rs | 55 ++-- .../src/lineage_command/_helpers/graph.rs | 29 +- .../main/lineage_path_nodes.rs | 45 ++- .../main/topological_order.rs | 29 +- .../main/discovery_order.rs | 35 +-- .../src/planner_scope/models.rs | 26 +- .../src/python_plan/main/upstream_closure.rs | 22 +- .../src/python_selection/_helpers/nodes.rs | 25 +- .../src/python_selection/_helpers/unified.rs | 19 +- .../_helpers/recovery/type_recovery.rs | 20 +- .../_helpers/attachment_checks.rs | 53 ++-- .../src/dagster_invocation/_helpers/graph.rs | 14 +- .../main/topological_order.rs | 31 +- .../src/dbt_graph/_helpers/closure.rs | 50 ---- .../src/dbt_graph/_helpers/mod.rs | 1 - .../src/dbt_graph/_helpers/selection.rs | 51 ++-- .../main/dbt_downstream_sqlbuild_models.rs | 14 +- .../src/graph_walk/_helpers/mod.rs | 2 + .../src/graph_walk/_helpers/node_sets.rs | 31 ++ .../src/graph_walk/_helpers/ready_queues.rs | 26 ++ .../src/graph_walk/main/breadth_first_path.rs | 34 +++ .../graph_walk/main/breadth_first_reach.rs | 31 ++ .../graph_walk/main/depth_first_postorder.rs | 33 +++ .../graph_walk/main/directed_path_nodes.rs | 37 +++ .../src/graph_walk/main/find_cycle.rs | 49 ++++ .../src/graph_walk/main/kahn_order.rs | 40 +++ .../sqlbuild-core/src/graph_walk/main/mod.rs | 6 + crates/sqlbuild-core/src/graph_walk/mod.rs | 9 + crates/sqlbuild-core/src/graph_walk/models.rs | 40 +++ crates/sqlbuild-core/src/graph_walk/tests.rs | 6 + .../src/graph_walk/tests/helpers.rs | 65 +++++ .../src/graph_walk/tests/test_graph_walk.rs | 267 ++++++++++++++++++ .../src/graph_walk/tests/test_types.rs | 47 +++ crates/sqlbuild-core/src/graph_walk/types.rs | 19 ++ crates/sqlbuild-core/src/lib.rs | 1 + .../src/load_command/_helpers/graph.rs | 66 ++--- .../load_command/_helpers/selection_sets.rs | 20 +- .../_helpers/python_nodes.rs | 68 +++-- .../src/macro_modules/_helpers/load_plan.rs | 28 +- .../macro_modules/_helpers/module_analysis.rs | 80 +++--- .../_helpers/project_analysis.rs | 24 +- .../src/rule_evidence/main/import_closure.rs | 23 +- .../src/bindings/_helpers/graph/algorithms.rs | 88 +++--- .../src/bindings/_helpers/graph/mod.rs | 1 + .../src/bindings/_helpers/graph/node_set.rs | 25 ++ .../planner_migrations/discovery_models.rs | 23 +- .../record_behaviours/python_nodes.rs | 30 +- 54 files changed, 1288 insertions(+), 760 deletions(-) delete mode 100644 crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/main/mod.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/mod.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/models.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/tests.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/tests/helpers.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/tests/test_types.rs create mode 100644 crates/sqlbuild-core/src/graph_walk/types.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs index 32e895995e..ae53d08a11 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs @@ -1,6 +1,8 @@ //! Expected results and assertions must not define or read each other, even through helpers. -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashMap}; + +use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path; use polyglot_sql::{Dialect, DialectType, Expression, ExpressionWalk}; use serde_json::Value; @@ -246,20 +248,12 @@ fn dependency_path( prohibited: &BTreeSet, dependencies: &HashMap>, ) -> Option> { - let mut pending: VecDeque> = VecDeque::from([vec![origin.to_owned()]]); - let mut visited: HashSet = HashSet::from([origin.to_owned()]); - while let Some(path) = pending.pop_front() { - let last: &String = path.last()?; - for dependency in dependencies.get(last).into_iter().flatten() { - let mut candidate: Vec = path.clone(); - candidate.push(dependency.clone()); - if prohibited.contains(dependency) { - return Some(candidate); - } - if visited.insert(dependency.clone()) { - pending.push_back(candidate); - } - } - } - None + breadth_first_path( + origin.to_owned(), + ( + |last: &String| dependencies.get(last).into_iter().flatten().cloned(), + |dependency: &String| prohibited.contains(dependency), + |_: &String| true, + ), + ) } diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs index 21c5ee7c27..dc7af49e4f 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs @@ -2,6 +2,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; +use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder; + use polyglot_sql::Dialect; use crate::compiler::_helpers::sql_tests::cte_rename::defined_cte_keys; @@ -103,36 +105,6 @@ pub(crate) struct ScopeGraph { rewritten: HashMap, } -/// Graph nodes in dependency-first order, built by depth-first traversal. -struct ScopeOrder<'a> { - graph: &'a ScopeGraph, - visited: HashSet, - ordered: Vec, - through_mocks: bool, -} - -impl ScopeOrder<'_> { - fn visit_tokens(&mut self, tokens: &[String]) { - for token in tokens { - if let Some(node) = self.graph.node(token) { - if !self.through_mocks && matches!(node, ScopeNode::Mock(_)) { - continue; - } - self.visit(node); - } - } - } - - fn visit(&mut self, node: ScopeNode) { - if !self.visited.insert(node) { - return; - } - let graph = self.graph; - self.visit_tokens(graph.tokens(node)); - self.ordered.push(node); - } -} - impl ScopeGraph { /// Tokenize one test's helpers and mocks and decide how its helpers are named and placed. pub(crate) fn new(request: &ScopeRequest<'_>) -> Result { @@ -294,14 +266,14 @@ impl ScopeGraph { } fn closure(&self, tokens: &[String], through_mocks: bool) -> Vec { - let mut order = ScopeOrder { - graph: self, - visited: HashSet::new(), - ordered: Vec::new(), - through_mocks, + let entered = |tokens: &[String]| -> Vec { + tokens + .iter() + .filter_map(|token| self.node(token)) + .filter(|node| through_mocks || !matches!(node, ScopeNode::Mock(_))) + .collect() }; - order.visit_tokens(tokens); - order.ordered + depth_first_postorder(entered(tokens), |node| entered(self.tokens(*node))) } /// Indexes of the helpers the compiler found the test's expected rows and assertions read. diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs index 941617bf68..97e790075e 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs @@ -1,11 +1,15 @@ //! Python's `report_mocks_reading_referencing_helpers`: P013s for mocks reading referencing helpers. use std::collections::HashSet; +use std::convert::Infallible; use polyglot_sql::expressions::With; use polyglot_sql::{ ComplexityGuardOptions, Dialect, DialectType, Expression, ExpressionWalk, ParseOptions, }; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; use sqlbuild_core::text::main::ignorecase_key::ignorecase_key; use sqlbuild_core::text::main::is_word_char::is_word_char; use sqlbuild_sqltext::sql_references::main::extract_sql_references::extract_sql_references; @@ -111,30 +115,18 @@ impl CteGraph<'_> { /// Python's `reachable_cte_keys`: breadth first, roots included, in visit order. fn reachable(&self, roots: &[String]) -> Vec { - let mut pending: Vec = Vec::new(); - for root in roots { - if self.cte(root).is_some() { - pending.push(root.clone()); - } - } - let mut visited: Vec = Vec::new(); - for key in &pending { - if !visited.contains(key) { - visited.push(key.clone()); - } - } - let mut cursor = 0; - while cursor < pending.len() { - let key = pending[cursor].clone(); - cursor += 1; - for dependency in self.reads(&key) { - if !visited.contains(dependency) { - visited.push(dependency.clone()); - pending.push(dependency.clone()); - } - } + let known: Vec<&String> = roots + .iter() + .filter(|root| self.cte(root).is_some()) + .collect(); + let mut visited: VisitOrder<&String> = VisitOrder::default(); + for root in &known { + let Ok(_) = visited.insert_new(root); } - visited + let Ok(()) = breadth_first_reach(known, None, &mut visited, |key| { + Ok::<_, Infallible>(self.reads(key)) + }); + visited.order.into_iter().cloned().collect() } } diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs index 272ae72a03..12a44c5c3e 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs @@ -1,6 +1,7 @@ //! Coarse project-level planning for SQL-native test artifacts. use std::collections::{BTreeMap, HashMap, HashSet}; +use std::convert::Infallible; use std::sync::{Arc, Mutex, OnceLock, PoisonError}; use std::time::Instant; @@ -9,6 +10,8 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use regex::Regex; #[cfg(test)] use serde::Serialize; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder; use crate::compiler::_helpers::sql_tests::cte_rename::defined_cte_keys; use crate::compiler::_helpers::sql_tests::cte_slices::{SliceDialect, strip_statement_terminators}; @@ -256,22 +259,12 @@ impl TextualChain { /// Return the generated CTEs needed by `roots`, dependencies first, each exactly once. fn closure_ctes(&self, roots: &[String], include_roots: bool) -> Vec<(String, String)> { let mut needed: HashSet = HashSet::new(); - let mut stack: Vec = Vec::new(); - for root in roots { - if include_roots { - stack.push(root.clone()); - } else if let Some(dependencies) = self.dependencies.get(root) { - stack.extend(dependencies.iter().cloned()); - } - } - while let Some(name) = stack.pop() { - if !needed.insert(name.clone()) { - continue; - } - if let Some(dependencies) = self.dependencies.get(&name) { - stack.extend(dependencies.iter().cloned()); - } + if include_roots { + needed.extend(roots.iter().cloned()); } + let Ok(()) = breadth_first_reach(roots.iter().cloned(), None, &mut needed, |name| { + Ok::<_, Infallible>(self.dependencies.get(name).into_iter().flatten().cloned()) + }); let mut ctes: Vec<(String, String)> = with_unique_ctes( Vec::new(), self.order @@ -1310,60 +1303,27 @@ fn classify_fixtures( } fn topo_sort_model_chain(request: TopoSortRequest<'_>) -> Vec { - fn visit( - name: &str, - models: &HashMap, - overrides: &BTreeMap, - mock_refs: &BTreeMap, - patterns: &SqlTestPatterns, - visited: &mut HashSet, - ordered: &mut Vec, - ) { - if !visited.insert(name.to_string()) { - return; - } - if let Some(model) = models.get(name) { - let mut dependencies = if let Some(override_sql) = overrides.get(name) { - marker_names(&patterns.reference, &patterns.lexical, override_sql) - } else { - model.model_dependencies.clone() - }; - dependencies.retain(|dependency| { - !mock_refs.contains_key(dependency) && models.contains_key(dependency) - }); - dependencies.sort(); - dependencies.dedup(); - for dependency in dependencies { - visit( - &dependency, - models, - overrides, - mock_refs, - patterns, - visited, - ordered, - ); - } - } - ordered.push(name.to_string()); - } - - let mut roots = request.expected_names.to_vec(); + let mut roots: Vec = request.expected_names.to_vec(); roots.sort(); - let mut visited: HashSet = HashSet::new(); - let mut ordered: Vec = Vec::new(); - for root in roots { - visit( - &root, - request.models, - request.overrides, - request.mock_refs, - request.patterns, - &mut visited, - &mut ordered, - ); - } - ordered + depth_first_postorder(roots, |name| { + let Some(model) = request.models.get(name) else { + return Vec::new(); + }; + let mut dependencies: Vec = match request.overrides.get(name) { + Some(override_sql) => marker_names( + &request.patterns.reference, + &request.patterns.lexical, + override_sql, + ), + None => model.model_dependencies.clone(), + }; + dependencies.retain(|dependency| { + !request.mock_refs.contains_key(dependency) && request.models.contains_key(dependency) + }); + dependencies.sort(); + dependencies.dedup(); + dependencies + }) } fn analyze_and_resolve_sql( diff --git a/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs b/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs index 8148ad5255..dd307490d5 100644 --- a/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs +++ b/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs @@ -1,7 +1,10 @@ -//! Breadth-first closures and directed path nodes over string-keyed DAG edges. +//! Closures, directed path nodes and selector resolution over string-keyed DAG edges. -use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::{BTreeMap, BTreeSet}; +use std::convert::Infallible; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; use sqlbuild_core::text::main::python_isspace::python_isspace; use sqlbuild_core::text::main::python_str_repr::python_str_repr; @@ -23,20 +26,14 @@ pub(crate) fn closure( edges: &Adjacency, include_starts: bool, ) -> BTreeSet { - let mut visited: BTreeSet = if include_starts { - starts.clone() - } else { - BTreeSet::new() - }; - let mut frontier: VecDeque<&String> = starts.iter().collect(); - while let Some(current) = frontier.pop_front() { - for neighbor in edges.get(current).into_iter().flatten() { - if visited.insert(neighbor.clone()) { - frontier.push_back(neighbor); - } - } + let mut visited: BTreeSet<&String> = BTreeSet::new(); + if include_starts { + visited.extend(starts); } - visited + let Ok(()) = breadth_first_reach(starts.iter(), None, &mut visited, |current| { + Ok::<_, Infallible>(edges.get(*current).into_iter().flatten()) + }); + visited.into_iter().cloned().collect() } /// Ids on directed paths from `start` to `end`, or `None` when `end` is not downstream. @@ -44,27 +41,18 @@ pub(crate) fn path_nodes( (start, end): (&str, &str), (upstream, downstream): (&Adjacency, &Adjacency), ) -> Option> { - let reachable: BTreeSet = - closure(&BTreeSet::from([start.to_owned()]), downstream, false); - if !reachable.contains(end) { - return None; - } - let mut upstream_of_end: BTreeSet = BTreeSet::new(); - let mut stack: Vec = vec![end.to_owned()]; - while let Some(current) = stack.pop() { - if !upstream_of_end.insert(current.clone()) { - continue; - } - for parent in upstream.get(¤t).into_iter().flatten() { - if reachable.contains(parent) || parent == start { - stack.push(parent.clone()); - } - } - } - let mut nodes: BTreeSet = reachable.intersection(&upstream_of_end).cloned().collect(); - nodes.insert(start.to_owned()); - nodes.insert(end.to_owned()); - Some(nodes) + let (start, end): (String, String) = (start.to_owned(), end.to_owned()); + let (mut reachable, mut path): (BTreeSet<&String>, BTreeSet<&String>) = + (BTreeSet::new(), BTreeSet::new()); + let Ok(found) = directed_path_nodes( + (&start, &end), + ( + |current: &&String| Ok(downstream.get(*current).into_iter().flatten()), + |current: &&String| Ok(upstream.get(*current).into_iter().flatten()), + ), + (&mut reachable, &mut path), + ); + found.then(|| path.into_iter().cloned().collect()) } pub(crate) fn resolve_expression( diff --git a/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs b/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs index 6c810daa23..b23c303398 100644 --- a/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs +++ b/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs @@ -1,6 +1,9 @@ //! One diff selector token resolved to the resources it names. -use std::collections::{HashSet, VecDeque}; +use std::collections::HashSet; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::diff_selection::constants::{ EMPTY_SELECTOR_CODE, GRAPH_OPERATOR, INTERSECTION_SEPARATOR, MODEL_PATH_PREFIX, @@ -13,14 +16,9 @@ use crate::diff_selection::models::DiffGraph; /// Resources reachable from `starts` along `edges`, without the starts unless reached. fn reachable(starts: &HashSet, edges: &[Vec]) -> HashSet { let mut visited: HashSet = HashSet::new(); - let mut frontier: VecDeque = starts.iter().copied().collect(); - while let Some(current) = frontier.pop_front() { - for neighbor in edges.get(current).into_iter().flatten() { - if visited.insert(*neighbor) { - frontier.push_back(*neighbor); - } - } - } + let Ok(()) = breadth_first_reach(starts.iter().copied(), None, &mut visited, |current| { + Ok::<_, Infallible>(edges.get(*current).into_iter().flatten().copied()) + }); visited } diff --git a/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs b/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs index 337a3f0ade..0852dec795 100644 --- a/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs +++ b/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs @@ -1,6 +1,8 @@ //! Python's lineage upstream edges and transitive closure over compiled resource keys. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::HashMap; + +use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path; use crate::explicit_references::constants::SQL_TEST_TYPE; use crate::explicit_references::models::GraphResource; @@ -31,17 +33,13 @@ pub(crate) fn lineage_upstream(resources: &[GraphResource]) -> Upstream { /// Whether `target` is reachable from `start` over at least one upstream edge. pub(crate) fn reaches(upstream: &Upstream, start: &ResourceKey, target: &ResourceKey) -> bool { - let mut visited: HashSet<&ResourceKey> = HashSet::new(); - let mut frontier: VecDeque<&ResourceKey> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in upstream.get(current).into_iter().flatten() { - if visited.insert(neighbor) { - if neighbor == target { - return true; - } - frontier.push_back(neighbor); - } - } - } - false + breadth_first_path( + start, + ( + |current: &&ResourceKey| upstream.get(*current).into_iter().flatten(), + |neighbour: &&ResourceKey| *neighbour == target, + |_: &&ResourceKey| true, + ), + ) + .is_some() } diff --git a/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs b/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs index 1c9360819b..8466a44518 100644 --- a/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs +++ b/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs @@ -1,6 +1,10 @@ -//! Python's breadth-first transitive closure and directed path nodes over graph edges. +//! Transitive closure and directed path nodes over project graph edges. -use std::collections::{BTreeSet, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; use crate::assembly::project::types::ObjectKey; use crate::graph::models::{EdgeDirection as Direction, ProjectGraph}; @@ -19,14 +23,9 @@ impl ProjectGraph { /// Every key reachable from `start`; `start` itself only through a cycle. pub(crate) fn closure(&self, start: &ObjectKey, direction: Direction) -> BTreeSet { let mut visited: HashSet<&ObjectKey> = HashSet::new(); - let mut frontier: VecDeque<&ObjectKey> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in self.edges(current, direction) { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } + let Ok(()) = breadth_first_reach([start], None, &mut visited, |current| { + Ok::<_, Infallible>(self.edges(current, direction)) + }); visited.into_iter().cloned().collect() } @@ -36,35 +35,25 @@ impl ProjectGraph { start: &ObjectKey, end: &ObjectKey, ) -> Option> { - let reachable: BTreeSet = self.closure(start, Direction::Downstream); - if !reachable.contains(end) { - return None; - } - let mut upstream_of_end: BTreeSet = BTreeSet::new(); - let mut stack: Vec = vec![end.clone()]; - while let Some(current) = stack.pop() { - if !upstream_of_end.insert(current.clone()) { - continue; - } - for parent in self.downstream_parents(¤t) { - if reachable.contains(&parent) || &parent == start { - stack.push(parent); - } - } - } - let mut nodes: BTreeSet = - reachable.intersection(&upstream_of_end).cloned().collect(); - nodes.insert(start.clone()); - nodes.insert(end.clone()); - Some(nodes) + let (mut reachable, mut path): (HashSet<&ObjectKey>, HashSet<&ObjectKey>) = + (HashSet::new(), HashSet::new()); + let Ok(found) = directed_path_nodes( + (start, end), + ( + |current: &&ObjectKey| Ok(self.edges(current, Direction::Downstream)), + |current: &&ObjectKey| Ok(self.downstream_parents(current)), + ), + (&mut reachable, &mut path), + ); + found.then(|| path.into_iter().cloned().collect()) } /// Keys whose downstream list holds `key`: the inverse of the downstream edges. - fn downstream_parents(&self, key: &ObjectKey) -> Vec { + fn downstream_parents<'graph>(&'graph self, key: &ObjectKey) -> Vec<&'graph ObjectKey> { self.downstream .iter() .filter(|(_, children)| children.contains(key)) - .map(|(parent, _)| parent.clone()) + .map(|(parent, _)| parent) .collect() } } diff --git a/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs b/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs index 7f5dd861fe..a2f500be5f 100644 --- a/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs +++ b/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs @@ -1,6 +1,9 @@ -//! Breadth-first closures and selected-graph assembly over a lineage index. +//! Closures and selected-graph assembly over a lineage index. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::lineage_command::constants::{ BOTH_DIRECTIONS, DOWNSTREAM_DIRECTION, UPSTREAM_DIRECTION, @@ -24,20 +27,14 @@ pub(crate) fn closure( starts: &[LineageKey], max_depth: Option, ) -> HashSet { - let mut visited: HashSet = HashSet::new(); - let mut frontier: VecDeque<(&LineageKey, i64)> = - starts.iter().map(|start| (start, 0)).collect(); - while let Some((current, depth)) = frontier.pop_front() { - if max_depth.is_some_and(|maximum| depth >= maximum) { - continue; - } - for neighbor in edges.get(current).copied().unwrap_or_default() { - if visited.insert(neighbor.clone()) { - frontier.push_back((neighbor, depth + 1)); - } - } - } - visited + let mut visited: HashSet<&LineageKey> = HashSet::new(); + let Ok(()) = breadth_first_reach( + starts, + max_depth.map(|maximum| u64::try_from(maximum).unwrap_or(0)), + &mut visited, + |current| Ok::<_, Infallible>(edges.get(*current).copied().unwrap_or_default()), + ); + visited.into_iter().cloned().collect() } /// The keys `direction` reaches from `keys` within `depth`. diff --git a/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs b/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs index 6ce7bb0db0..4a21bf0e22 100644 --- a/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs +++ b/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs @@ -2,7 +2,9 @@ use std::collections::HashSet; -use crate::lineage_command::_helpers::graph::{EdgeMap, closure, edge_map}; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; + +use crate::lineage_command::_helpers::graph::{EdgeMap, edge_map}; use crate::lineage_command::models::{LineageIndex, LineageKey}; /// Keys on downstream paths from `start` to `end`, or none when `end` is not downstream. @@ -13,28 +15,25 @@ pub fn lineage_path_nodes( end: &LineageKey, ) -> Vec { let downstream: EdgeMap<'_> = edge_map(&index.downstream); - let reachable: HashSet = closure(&downstream, std::slice::from_ref(start), None); - if !reachable.contains(end) { + let (mut reachable, mut path): (HashSet<&LineageKey>, HashSet<&LineageKey>) = + (HashSet::new(), HashSet::new()); + let Ok(found) = directed_path_nodes( + (start, end), + ( + |current: &&LineageKey| Ok(downstream.get(*current).copied().unwrap_or_default()), + |current: &&LineageKey| { + Ok(index + .downstream + .iter() + .filter(|(_, children)| children.contains(*current)) + .map(|(parent, _)| parent) + .collect::>()) + }, + ), + (&mut reachable, &mut path), + ); + if !found { return Vec::new(); } - let mut upstream_of_end: HashSet<&LineageKey> = HashSet::new(); - let mut stack: Vec<&LineageKey> = vec![end]; - while let Some(current) = stack.pop() { - if !upstream_of_end.insert(current) { - continue; - } - for (parent, children) in &index.downstream { - if children.contains(current) && (reachable.contains(parent) || parent == start) { - stack.push(parent); - } - } - } - let mut nodes: Vec = reachable - .iter() - .filter(|key| upstream_of_end.contains(key)) - .cloned() - .collect(); - nodes.push(start.clone()); - nodes.push(end.clone()); - nodes + path.into_iter().cloned().collect() } diff --git a/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs b/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs index 6a183eb1d7..7c84421336 100644 --- a/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs +++ b/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs @@ -3,6 +3,8 @@ use std::cmp::Reverse; use std::collections::{BinaryHeap, HashMap}; +use sqlbuild_core::graph_walk::main::kahn_order::kahn_order; + use crate::planner_identity::_helpers::cycle_message::cycle_message; use crate::planner_identity::_helpers::dependency_counts::{DependencyCounts, dependency_counts}; use crate::planner_identity::models::GraphKey; @@ -14,26 +16,17 @@ pub fn topological_order( ) -> Result, String> { let DependencyCounts { node_keys, - mut indegree, + indegree, downstream, } = dependency_counts(upstream); - let mut ready: BinaryHeap> = indegree - .iter() - .filter(|(_, count)| **count == 0) - .map(|(key, _)| Reverse(*key)) - .collect(); - let mut ordered: Vec = Vec::with_capacity(node_keys.len()); - while let Some(Reverse(current)) = ready.pop() { - ordered.push(current.clone()); - for downstream_key in downstream.get(current).into_iter().flatten() { - if let Some(count) = indegree.get_mut(downstream_key) { - *count = count.saturating_sub(1); - if *count == 0 { - ready.push(Reverse(downstream_key)); - } - } - } - } + let ordered: Vec = kahn_order( + (node_keys.iter().copied(), indegree), + BinaryHeap::>::new(), + |current| downstream.get(*current).cloned().unwrap_or_default(), + ) + .into_iter() + .cloned() + .collect(); if ordered.len() == node_keys.len() { return Ok(ordered); } diff --git a/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs b/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs index 04c8d5fa44..e889e29765 100644 --- a/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs +++ b/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs @@ -1,31 +1,18 @@ //! Stable depth-first ordering for migration identity propagation. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; + +use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder; /// Order models after their model dependencies using lexical traversal ties. pub fn discovery_order(dependencies: &BTreeMap>) -> Vec { - let mut pending: Vec<(String, bool)> = dependencies - .keys() - .rev() - .map(|name| (name.clone(), false)) - .collect(); - let mut visited: BTreeSet = BTreeSet::new(); - let mut ordered: Vec = Vec::new(); - while let Some((name, expanded)) = pending.pop() { - if expanded { - ordered.push(name); - continue; - } - if !visited.insert(name.clone()) { - continue; - } - pending.push((name.clone(), true)); - let mut upstream = dependencies[&name].clone(); + depth_first_postorder(dependencies.keys().cloned(), |name| { + let mut upstream: Vec = dependencies[name] + .iter() + .filter(|dependency| dependencies.contains_key(*dependency)) + .cloned() + .collect(); upstream.sort(); - pending.extend(upstream.into_iter().rev().filter_map(|dependency| { - (dependencies.contains_key(&dependency) && !visited.contains(&dependency)) - .then_some((dependency, false)) - })); - } - ordered + upstream + }) } diff --git a/crates/sqlbuild-analysis/src/planner_scope/models.rs b/crates/sqlbuild-analysis/src/planner_scope/models.rs index 38a1602733..1e892d9e74 100644 --- a/crates/sqlbuild-analysis/src/planner_scope/models.rs +++ b/crates/sqlbuild-analysis/src/planner_scope/models.rs @@ -1,6 +1,11 @@ //! Ordered loader facts used by planner scope resolution. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::HashMap; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; use crate::assembly::project::types::ObjectKey; use crate::planner_scope::constants::SOURCE; @@ -15,19 +20,14 @@ pub struct LoaderGraph { impl LoaderGraph { /// Upstream loader names in breadth-first, first-seen order. pub fn upstream(&self, dependencies: &[String]) -> Vec { - let mut pending: VecDeque = dependencies.iter().cloned().collect(); - let mut seen: HashSet = HashSet::new(); - let mut names: Vec = Vec::new(); - while let Some(name) = pending.pop_front() { - if !seen.insert(name.clone()) { - continue; - } - if let Some(dependencies) = self.loaders.get(&name) { - pending.extend(dependencies.iter().cloned()); - } - names.push(name); + let mut names: VisitOrder<&String> = VisitOrder::default(); + for name in dependencies { + let Ok(_) = names.insert_new(&name); } - names + let Ok(()) = breadth_first_reach(dependencies, None, &mut names, |name| { + Ok::<_, Infallible>(self.loaders.get(*name).into_iter().flatten()) + }); + names.order.into_iter().cloned().collect() } /// Source key for a loader, using the last source declaration for that loader. diff --git a/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs b/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs index e216ba75c9..2c0d9d8509 100644 --- a/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs +++ b/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs @@ -1,18 +1,22 @@ //! Every node upstream of one Python node, sorted. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; /// `sorted(transitive_closure(start=start, edges=upstream))`: `start` only when on a cycle. pub fn upstream_closure(start: &str, upstream: &HashMap>) -> Vec { let mut visited: HashSet<&str> = HashSet::new(); - let mut frontier: VecDeque<&str> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in upstream.get(current).into_iter().flatten() { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } + let Ok(()) = breadth_first_reach([start], None, &mut visited, |current| { + Ok::<_, Infallible>( + upstream + .get(*current) + .into_iter() + .flatten() + .map(String::as_str), + ) + }); let mut names: Vec = visited.into_iter().map(str::to_owned).collect(); names.sort(); names diff --git a/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs b/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs index e3d4e92112..34f8bf1c3a 100644 --- a/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs +++ b/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs @@ -1,7 +1,9 @@ //! Python's `resolve_python_node_selectors`: selector resolution over Python node names. -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::Infallible; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use sqlbuild_core::text::main::glob_match::glob_match; use crate::assembly::project::types::ObjectKey; @@ -89,19 +91,14 @@ impl<'graph> NodeIndex<'graph> { starts: impl IntoIterator, direction: EdgeDirection, ) -> Names { - let mut visited: HashSet<&str> = HashSet::new(); - let mut frontier: VecDeque<&str> = VecDeque::new(); - for start in starts { - frontier.push_back(start); - } - while let Some(current) = frontier.pop_front() { - for neighbor in self.edges(current, direction) { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } - visited.into_iter().map(str::to_owned).collect() + let mut visited: HashSet = HashSet::new(); + let Ok(()) = breadth_first_reach( + starts.into_iter().map(str::to_owned), + None, + &mut visited, + |current| Ok::<_, Infallible>(self.edges(current, direction).iter().cloned()), + ); + visited.into_iter().collect() } /// Whether `name` is a node of `kind`. diff --git a/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs b/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs index 8d91e471af..523963f25e 100644 --- a/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs +++ b/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs @@ -1,7 +1,9 @@ //! Python's unified selector resolution across SQL resources and Python nodes. -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::Infallible; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use sqlbuild_core::text::main::glob_match::glob_match; use crate::assembly::project::types::ObjectKey; @@ -192,14 +194,13 @@ fn sql_dependency_upstream(context: &SelectionContext<'_>, atoms: &Atoms) -> Ato return Atoms::new(); } let mut visited: HashSet<&ObjectKey> = starts.iter().copied().collect(); - let mut frontier: VecDeque<&ObjectKey> = starts.iter().copied().collect(); - while let Some(current) = frontier.pop_front() { - for neighbor in edge_keys(context.sql.graph, current, EdgeDirection::Upstream) { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } + let Ok(()) = breadth_first_reach(starts, None, &mut visited, |current| { + Ok::<_, Infallible>(edge_keys( + context.sql.graph, + current, + EdgeDirection::Upstream, + )) + }); let selected: Atoms = sql_atoms(visited.into_iter().cloned()); let loaders: Atoms = terminal_loader_atoms(context, &selected); selected.into_iter().chain(loaders).collect() diff --git a/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs b/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs index 1e065476f8..50df8ea5af 100644 --- a/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs +++ b/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs @@ -1,6 +1,9 @@ //! Python's `recover_output_types`: unknown types for outputs poisoned by located root errors. use std::collections::{HashMap, HashSet, VecDeque}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::semantic_checks::_helpers::recovery::bindings::{BindingOwner, binding_positions}; use crate::semantic_checks::_helpers::recovery::poison::Poison; @@ -266,19 +269,18 @@ fn unchecked_uses( let mut counts: HashMap = HashMap::new(); for model in &request.models { for output in &model.lineage { - let mut pending: Vec = output + let roots: Vec = output .upstream .iter() .filter_map(|(resource, column)| poisoned.get(resource, column)) .collect(); - let mut visited: HashSet = HashSet::new(); - while let Some(root) = pending.pop() { - if !visited.insert(root) { - continue; - } - match redirected.get(&root) { - Some(targets) => pending.extend(targets.iter().copied()), - None => *counts.entry(root).or_default() += 1, + let mut visited: HashSet = roots.iter().copied().collect(); + let Ok(()) = breadth_first_reach(roots, None, &mut visited, |root| { + Ok::<_, Infallible>(redirected.get(root).into_iter().flatten().copied()) + }); + for root in visited { + if !redirected.contains_key(&root) { + *counts.entry(root).or_default() += 1; } } } diff --git a/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs b/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs index ce8589f794..9cf9817e02 100644 --- a/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs +++ b/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs @@ -1,6 +1,12 @@ //! `helper_ctes`: the helper CTEs a model test reads and the references it cannot resolve. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; use sqlbuild_sqltext::sql_references::main::extract_sql_references::extract_sql_references; use sqlbuild_sqltext::sql_references::models::{ReferenceExtraction, SqlReference}; @@ -39,26 +45,18 @@ impl CteGraph<'_> { /// `reachable_cte_keys`: the roots then what they read, breadth first, in visit order. fn reachable(&self, roots: &[String]) -> Vec { - let mut pending: VecDeque = roots + let known: Vec<&String> = roots .iter() .filter(|root| self.ctes.contains_key(*root)) - .cloned() .collect(); - let mut visited: Vec = Vec::new(); - for root in &pending { - if !visited.contains(root) { - visited.push(root.clone()); - } - } - while let Some(key) = pending.pop_front() { - for dependency in self.reads.get(&key).into_iter().flatten() { - if !visited.contains(dependency) { - visited.push(dependency.clone()); - pending.push_back(dependency.clone()); - } - } + let mut visited: VisitOrder<&String> = VisitOrder::default(); + for root in &known { + let Ok(_) = visited.insert_new(root); } - visited + let Ok(()) = breadth_first_reach(known, None, &mut visited, |key| { + Ok::<_, Infallible>(self.reads.get(*key).into_iter().flatten()) + }); + visited.order.into_iter().cloned().collect() } /// `_read_helper_keys`: reachable authored CTEs that are not mocks. @@ -304,22 +302,11 @@ pub(crate) fn unresolvable_test_references( fn first_cycle(graph: &CteGraph<'_>, keys: &[String]) -> Option> { let allowed: HashSet<&String> = keys.iter().collect(); for origin in keys { - let mut pending: VecDeque> = VecDeque::from([vec![origin.clone()]]); - let mut visited: HashSet = HashSet::new(); - while let Some(path) = pending.pop_front() { - let last: &String = path.last()?; - for dependency in graph.parsed_reads.get(last).into_iter().flatten() { - if dependency == origin { - let mut closed: Vec = path.clone(); - closed.push(origin.clone()); - return Some(closed); - } - if allowed.contains(dependency) && visited.insert(dependency.clone()) { - let mut next: Vec = path.clone(); - next.push(dependency.clone()); - pending.push_back(next); - } - } + let reads = |last: &&String| graph.parsed_reads.get(*last).into_iter().flatten(); + let closes = |dependency: &&String| *dependency == origin; + let enters = |dependency: &&String| allowed.contains(*dependency); + if let Some(path) = breadth_first_path(origin, (reads, closes, enters)) { + return Some(path.into_iter().cloned().collect()); } } None diff --git a/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs b/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs index 62602ba23f..d23aa00acd 100644 --- a/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs +++ b/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs @@ -1,6 +1,6 @@ //! Edge sets of a DAG artifact keyed by node position. -use std::collections::{BTreeSet, HashMap, VecDeque}; +use std::collections::{BTreeSet, HashMap}; /// Upstream positions and ordered, distinct downstream positions of each node. pub(crate) struct EdgeSets { @@ -58,15 +58,3 @@ pub(crate) fn id_positions(ids: &[String]) -> HashMap<&str, usize> { .map(|(position, id)| (id.as_str(), position)) .collect() } - -/// Nodes without upstream nodes, in node order. -pub(crate) fn initially_ready( - unique: &[usize], - incoming: &HashMap>, -) -> VecDeque { - unique - .iter() - .copied() - .filter(|position| incoming.get(position).is_none_or(BTreeSet::is_empty)) - .collect() -} diff --git a/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs b/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs index 7b8e5b12fd..14e5b3a1db 100644 --- a/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs +++ b/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs @@ -1,31 +1,26 @@ //! DAG node order for emitting Dagster events. -use std::collections::{BTreeSet, HashMap, VecDeque}; +use std::collections::{HashMap, VecDeque}; use crate::dagster_invocation::_helpers::graph::{ - EdgeSets, edge_sets, id_positions, initially_ready, unique_positions, + EdgeSets, edge_sets, id_positions, unique_positions, }; +use crate::graph_walk::main::kahn_order::kahn_order; /// Node positions in Kahn order over known edges, or `None` when a cycle leaves nodes out. #[must_use] pub fn topological_order(ids: &[String], edges: &[(String, String)]) -> Option> { let positions: HashMap<&str, usize> = id_positions(ids); let unique: Vec = unique_positions(ids, &positions); - let EdgeSets { - mut incoming, - outgoing, - } = edge_sets(&unique, (&positions, edges)); - let mut ready: VecDeque = initially_ready(&unique, &incoming); - let mut ordered: Vec = Vec::new(); - while let Some(position) = ready.pop_front() { - ordered.push(position); - for downstream in outgoing.get(&position).cloned().unwrap_or_default() { - let waiting: &mut BTreeSet = incoming.entry(downstream).or_default(); - waiting.remove(&position); - if waiting.is_empty() { - ready.push_back(downstream); - } - } - } + let EdgeSets { incoming, outgoing } = edge_sets(&unique, (&positions, edges)); + let remaining: HashMap = incoming + .iter() + .map(|(position, upstream)| (*position, upstream.len())) + .collect(); + let ordered: Vec = kahn_order( + (unique.iter().copied(), remaining), + VecDeque::new(), + |position| outgoing.get(position).cloned().unwrap_or_default(), + ); (ordered.len() == ids.len()).then_some(ordered) } diff --git a/crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs b/crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs deleted file mode 100644 index f51d0a2dc1..0000000000 --- a/crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Breadth-first reachability and path nodes over combined graph edges. - -use std::collections::{HashSet, VecDeque}; - -use crate::dbt_graph::models::{DbtGraphEdges, DbtGraphKey}; - -/// Every key reachable from `start`; `start` itself only through a cycle. -pub(crate) fn reachable(start: &DbtGraphKey, edges: &DbtGraphEdges) -> HashSet { - let mut visited: HashSet = HashSet::new(); - let mut frontier: VecDeque<&DbtGraphKey> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in edges.targets.get(current).into_iter().flatten() { - if visited.insert(neighbor.clone()) { - frontier.push_back(neighbor); - } - } - } - visited -} - -/// Keys on directed paths from `start` to `end`, or `None` when `end` is not downstream. -pub(crate) fn path_nodes( - (start, end): (&DbtGraphKey, &DbtGraphKey), - downstream: &DbtGraphEdges, - upstream: &DbtGraphEdges, -) -> Option> { - let reachable_keys: HashSet = reachable(start, downstream); - if !reachable_keys.contains(end) { - return None; - } - let mut upstream_from_end: HashSet = HashSet::new(); - let mut stack: Vec<&DbtGraphKey> = vec![end]; - while let Some(current) = stack.pop() { - if !upstream_from_end.insert(current.clone()) { - continue; - } - for parent in upstream.targets.get(current).into_iter().flatten() { - if reachable_keys.contains(parent) || parent == start { - stack.push(parent); - } - } - } - let mut nodes: HashSet = reachable_keys - .intersection(&upstream_from_end) - .cloned() - .collect(); - nodes.insert(start.clone()); - nodes.insert(end.clone()); - Some(nodes) -} diff --git a/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs b/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs index ced417d413..213c16d876 100644 --- a/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs +++ b/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod closure; pub(crate) mod edges; pub(crate) mod endpoints; pub(crate) mod selection; diff --git a/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs b/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs index a4a4703bab..caf87c51e8 100644 --- a/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs +++ b/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs @@ -1,8 +1,8 @@ //! One selector term at a time: path selectors, SQLBuild selectors and dbt anchors. use std::collections::HashSet; +use std::convert::Infallible; -use crate::dbt_graph::_helpers::closure::{path_nodes, reachable}; use crate::dbt_graph::_helpers::endpoints::{endpoint_key, endpoint_names}; use crate::dbt_graph::constants::PATH_SELECTOR_SEPARATOR; use crate::dbt_graph::errors::DbtSelectionError; @@ -11,6 +11,8 @@ use crate::dbt_graph::models::{ DbtSelectionState, DbtSelectorExpansion, }; use crate::dbt_graph::types::DbtSelectorResolver; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::main::directed_path_nodes::directed_path_nodes; fn added( mut state: DbtSelectionState, @@ -30,18 +32,20 @@ fn expanded( parsed: &DbtSelectorExpansion, graph: &DbtCombinedGraph, ) -> HashSet { - let mut result: HashSet = keys.clone(); - if parsed.upstream { - for key in &keys { - result.extend(reachable(key, &graph.upstream)); + let mut reached: HashSet<&DbtGraphKey> = keys.iter().collect(); + for (requested, edges) in [ + (parsed.upstream, &graph.upstream), + (parsed.downstream, &graph.downstream), + ] { + if requested { + let mut visited: HashSet<&DbtGraphKey> = HashSet::new(); + let Ok(()) = breadth_first_reach(keys.iter(), None, &mut visited, |key| { + Ok::<_, Infallible>(edges.targets.get(*key).into_iter().flatten()) + }); + reached.extend(visited); } } - if parsed.downstream { - for key in &keys { - result.extend(reachable(key, &graph.downstream)); - } - } - result + reached.into_iter().cloned().collect() } fn path_keys( @@ -53,7 +57,20 @@ fn path_keys( let start: DbtGraphKey = endpoint_key(resolver, request.manifest_models, (start_name, raw_term))?; let end: DbtGraphKey = endpoint_key(resolver, request.manifest_models, (end_name, raw_term))?; - Ok(path_nodes((&start, &end), &graph.downstream, &graph.upstream).unwrap_or_default()) + let (mut reachable, mut path): (HashSet<&DbtGraphKey>, HashSet<&DbtGraphKey>) = + (HashSet::new(), HashSet::new()); + let Ok(found) = directed_path_nodes( + (&start, &end), + ( + |key: &&DbtGraphKey| Ok(graph.downstream.targets.get(*key).into_iter().flatten()), + |key: &&DbtGraphKey| Ok(graph.upstream.targets.get(*key).into_iter().flatten()), + ), + (&mut reachable, &mut path), + ); + if !found { + return Ok(HashSet::new()); + } + Ok(path.into_iter().cloned().collect()) } fn with_anchor(mut selection: DbtSelection, term: &str, unique_ids: &[String]) -> DbtSelection { @@ -82,10 +99,12 @@ fn anchored( .map_or(&[], Vec::as_slice); state.selection = with_anchor(state.selection, term, anchors); for unique_id in anchors { - state = added( - state, - reachable(&DbtGraphKey::dbt_model(unique_id), &graph.downstream), - ); + let start: DbtGraphKey = DbtGraphKey::dbt_model(unique_id); + let mut reached: HashSet<&DbtGraphKey> = HashSet::new(); + let Ok(()) = breadth_first_reach([&start], None, &mut reached, |key| { + Ok::<_, Infallible>(graph.downstream.targets.get(*key).into_iter().flatten()) + }); + state = added(state, reached.into_iter().cloned()); } state } diff --git a/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs b/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs index d9157a53c7..b9e41acbb5 100644 --- a/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs +++ b/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs @@ -1,8 +1,9 @@ //! SQLBuild models downstream of dbt models. -use std::collections::BTreeSet; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use std::collections::{BTreeSet, HashSet}; +use std::convert::Infallible; -use crate::dbt_graph::_helpers::closure::reachable; use crate::dbt_graph::models::{ DbtCombinedGraph, DbtGraphKey, DbtGraphOwner, DbtGraphResourceType, }; @@ -15,11 +16,16 @@ pub fn dbt_downstream_sqlbuild_models( ) -> Vec { let mut names: BTreeSet = BTreeSet::new(); for unique_id in unique_ids { - for key in reachable(&DbtGraphKey::dbt_model(unique_id), &graph.downstream) { + let start: DbtGraphKey = DbtGraphKey::dbt_model(unique_id); + let mut reached: HashSet<&DbtGraphKey> = HashSet::new(); + let Ok(()) = breadth_first_reach([&start], None, &mut reached, |key| { + Ok::<_, Infallible>(graph.downstream.targets.get(*key).into_iter().flatten()) + }); + for key in reached { if key.owner == DbtGraphOwner::Sqlbuild && key.resource_type == DbtGraphResourceType::Model { - names.insert(key.name); + names.insert(key.name.clone()); } } } diff --git a/crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs b/crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs new file mode 100644 index 0000000000..250f973053 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs @@ -0,0 +1,2 @@ +mod node_sets; +mod ready_queues; diff --git a/crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs b/crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs new file mode 100644 index 0000000000..6e345276f9 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs @@ -0,0 +1,31 @@ +//! `NodeSet` for the standard sets. + +use std::collections::{BTreeSet, HashSet}; +use std::convert::Infallible; +use std::hash::Hash; + +use crate::graph_walk::types::NodeSet; + +impl NodeSet for HashSet { + type Error = Infallible; + + fn insert_new(&mut self, key: &K) -> Result { + Ok(!self.contains(key) && self.insert(key.clone())) + } + + fn holds(&self, key: &K) -> Result { + Ok(self.contains(key)) + } +} + +impl NodeSet for BTreeSet { + type Error = Infallible; + + fn insert_new(&mut self, key: &K) -> Result { + Ok(!self.contains(key) && self.insert(key.clone())) + } + + fn holds(&self, key: &K) -> Result { + Ok(self.contains(key)) + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs b/crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs new file mode 100644 index 0000000000..a8c78f5d70 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs @@ -0,0 +1,26 @@ +//! `ReadyQueue` for first-in-first-out and smallest-first queues. + +use std::cmp::Reverse; +use std::collections::{BinaryHeap, VecDeque}; + +use crate::graph_walk::types::ReadyQueue; + +impl ReadyQueue for VecDeque { + fn offer(&mut self, key: K) { + self.push_back(key); + } + + fn take(&mut self) -> Option { + self.pop_front() + } +} + +impl ReadyQueue for BinaryHeap> { + fn offer(&mut self, key: K) { + self.push(Reverse(key)); + } + + fn take(&mut self) -> Option { + self.pop().map(|Reverse(key)| key) + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs new file mode 100644 index 0000000000..1b4c0a84f8 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs @@ -0,0 +1,34 @@ +//! `breadth_first_path`: the shortest path from a node to the first goal it reaches. + +use std::collections::{HashSet, VecDeque}; +use std::hash::Hash; + +/// The shortest path from `origin` ending at a goal neighbour, entering only nodes `enter` allows; the origin itself is never re-entered. +pub fn breadth_first_path( + origin: K, + (mut neighbours, is_goal, enter): (N, G, E), +) -> Option> +where + K: Clone + Eq + Hash, + I: IntoIterator, + N: FnMut(&K) -> I, + G: Fn(&K) -> bool, + E: Fn(&K) -> bool, +{ + let mut visited: HashSet = HashSet::from([origin.clone()]); + let mut pending: VecDeque> = VecDeque::from([vec![origin]]); + while let Some(path) = pending.pop_front() { + let last: &K = path.last()?; + for neighbour in neighbours(last) { + let mut candidate: Vec = path.clone(); + candidate.push(neighbour.clone()); + if is_goal(&neighbour) { + return Some(candidate); + } + if enter(&neighbour) && visited.insert(neighbour) { + pending.push_back(candidate); + } + } + } + None +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs new file mode 100644 index 0000000000..11c12df954 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs @@ -0,0 +1,31 @@ +//! `breadth_first_reach`: record every node reachable from the starts, breadth first. + +use std::collections::VecDeque; + +use crate::graph_walk::types::NodeSet; + +/// Record nodes within `max_depth` edges of a start in first-visit order; a start counts only when reached, so callers including starts record them first. +pub fn breadth_first_reach( + starts: impl IntoIterator, + max_depth: Option, + visited: &mut S, + mut neighbours: N, +) -> Result<(), S::Error> +where + S: NodeSet, + I: IntoIterator, + N: FnMut(&K) -> Result, +{ + let mut frontier: VecDeque<(K, u64)> = starts.into_iter().map(|start| (start, 0)).collect(); + while let Some((current, depth)) = frontier.pop_front() { + if max_depth.is_some_and(|maximum| depth >= maximum) { + continue; + } + for neighbour in neighbours(¤t)? { + if visited.insert_new(&neighbour)? { + frontier.push_back((neighbour, depth + 1)); + } + } + } + Ok(()) +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs b/crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs new file mode 100644 index 0000000000..8b6fa900d2 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs @@ -0,0 +1,33 @@ +//! `depth_first_postorder`: nodes after the nodes they lead to, depth first from each root. + +use std::collections::HashSet; +use std::hash::Hash; + +/// Every node reachable from `roots`, each after its neighbours, visiting roots and neighbours in the given order and entering each node once. +pub fn depth_first_postorder( + roots: impl IntoIterator, + mut neighbours: N, +) -> Vec +where + K: Clone + Eq + Hash, + I: IntoIterator, + N: FnMut(&K) -> I, +{ + let mut pending: Vec<(K, bool)> = roots.into_iter().map(|root| (root, false)).collect(); + pending.reverse(); + let mut entered: HashSet = HashSet::new(); + let mut ordered: Vec = Vec::new(); + while let Some((node, finished)) = pending.pop() { + if finished { + ordered.push(node); + continue; + } + if !entered.insert(node.clone()) { + continue; + } + let next: Vec = neighbours(&node).into_iter().collect(); + pending.push((node, true)); + pending.extend(next.into_iter().rev().map(|neighbour| (neighbour, false))); + } + ordered +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs b/crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs new file mode 100644 index 0000000000..26565e2dae --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs @@ -0,0 +1,37 @@ +//! `directed_path_nodes`: the nodes on directed paths from one node to another. + +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::types::NodeSet; + +/// Record in `path` every node on a downstream path from `start` to `end`, both included; false when `end` is not downstream of `start`. +pub fn directed_path_nodes( + (start, end): (K, K), + (downstream, mut upstream): (D, U), + (reachable, path): (&mut S, &mut S), +) -> Result +where + K: Clone, + S: NodeSet, + I: IntoIterator, + J: IntoIterator, + D: FnMut(&K) -> Result, + U: FnMut(&K) -> Result, +{ + breadth_first_reach([start.clone()], None, reachable, downstream)?; + if !reachable.holds(&end)? { + return Ok(false); + } + reachable.insert_new(&start)?; + let mut pending: Vec = vec![end]; + while let Some(current) = pending.pop() { + if !path.insert_new(¤t)? { + continue; + } + for parent in upstream(¤t)? { + if reachable.holds(&parent)? { + pending.push(parent); + } + } + } + Ok(true) +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs b/crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs new file mode 100644 index 0000000000..9ab19255d2 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs @@ -0,0 +1,49 @@ +//! `find_cycle`: the first cycle a depth-first walk from each root meets. + +use std::collections::HashSet; +use std::hash::Hash; + +/// The path from a root to the first node met twice on it, ending with that node; finished nodes are shared across roots. +pub fn find_cycle(roots: impl IntoIterator, mut neighbours: N) -> Option> +where + K: Clone + Eq + Hash, + I: IntoIterator, + N: FnMut(&K) -> I, +{ + let mut finished: HashSet = HashSet::new(); + for root in roots { + let mut path: Vec = Vec::new(); + let mut frames: Vec> = Vec::new(); + let mut next: Option = Some(root); + loop { + if let Some(node) = next.take() + && !finished.contains(&node) + { + if path.contains(&node) { + path.push(node); + return Some(path); + } + frames.push( + neighbours(&node) + .into_iter() + .collect::>() + .into_iter(), + ); + path.push(node); + } + let Some(frame) = frames.last_mut() else { + break; + }; + match frame.next() { + Some(neighbour) => next = Some(neighbour), + None => { + frames.pop(); + if let Some(done) = path.pop() { + finished.insert(done); + } + } + } + } + } + None +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs b/crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs new file mode 100644 index 0000000000..d84a5ce465 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs @@ -0,0 +1,40 @@ +//! `kahn_order`: nodes in dependency order with Kahn's algorithm and a caller tie-break. + +use std::collections::HashMap; +use std::hash::Hash; + +use crate::graph_walk::types::ReadyQueue; + +/// Take ready nodes from `ready`, seeded with the zero-count `nodes` in order; each successor edge lowers a count once, and nodes left uncounted are never taken. +pub fn kahn_order( + (nodes, mut remaining): (impl IntoIterator, HashMap), + mut ready: Q, + mut successors: N, +) -> Vec +where + K: Clone + Eq + Hash, + Q: ReadyQueue, + I: IntoIterator, + N: FnMut(&K) -> I, +{ + for node in nodes { + if remaining.get(&node) == Some(&0) { + ready.offer(node); + } + } + let mut ordered: Vec = Vec::with_capacity(remaining.len()); + while let Some(current) = ready.take() { + for successor in successors(¤t) { + if let Some(count) = remaining.get_mut(&successor) + && *count > 0 + { + *count -= 1; + if *count == 0 { + ready.offer(successor); + } + } + } + ordered.push(current); + } + ordered +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/mod.rs b/crates/sqlbuild-core/src/graph_walk/main/mod.rs new file mode 100644 index 0000000000..3787cafe64 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/mod.rs @@ -0,0 +1,6 @@ +pub mod breadth_first_path; +pub mod breadth_first_reach; +pub mod depth_first_postorder; +pub mod directed_path_nodes; +pub mod find_cycle; +pub mod kahn_order; diff --git a/crates/sqlbuild-core/src/graph_walk/mod.rs b/crates/sqlbuild-core/src/graph_walk/mod.rs new file mode 100644 index 0000000000..3459b08aa1 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/mod.rs @@ -0,0 +1,9 @@ +//! Graph walks shared by every graph shape: reachability, directed paths, orders and cycles. + +mod _helpers; +pub mod main; +pub mod models; +pub mod types; + +#[cfg(test)] +mod tests; diff --git a/crates/sqlbuild-core/src/graph_walk/models.rs b/crates/sqlbuild-core/src/graph_walk/models.rs new file mode 100644 index 0000000000..f98c6e8cd6 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/models.rs @@ -0,0 +1,40 @@ +//! Visited nodes kept in first-visit order. + +use std::collections::HashSet; +use std::convert::Infallible; +use std::hash::Hash; + +use crate::graph_walk::types::NodeSet; + +/// Distinct nodes in the order a walk first recorded them. +#[derive(Debug, Clone)] +pub struct VisitOrder { + /// The recorded nodes, first visit first. + pub order: Vec, + seen: HashSet, +} + +impl Default for VisitOrder { + fn default() -> Self { + Self { + order: Vec::new(), + seen: HashSet::new(), + } + } +} + +impl NodeSet for VisitOrder { + type Error = Infallible; + + fn insert_new(&mut self, key: &K) -> Result { + if !self.seen.insert(key.clone()) { + return Ok(false); + } + self.order.push(key.clone()); + Ok(true) + } + + fn holds(&self, key: &K) -> Result { + Ok(self.seen.contains(key)) + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/tests.rs b/crates/sqlbuild-core/src/graph_walk/tests.rs new file mode 100644 index 0000000000..cabcb71428 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests.rs @@ -0,0 +1,6 @@ +#[path = "tests/test_graph_walk.rs"] +mod graph_walk; +#[path = "tests/helpers.rs"] +mod helpers; +#[path = "tests/test_types.rs"] +mod test_types; diff --git a/crates/sqlbuild-core/src/graph_walk/tests/helpers.rs b/crates/sqlbuild-core/src/graph_walk/tests/helpers.rs new file mode 100644 index 0000000000..0c025983f9 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests/helpers.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use crate::graph_walk::main::kahn_order::kahn_order; +use crate::graph_walk::models::VisitOrder; +use crate::graph_walk::tests::test_types::KahnTestCase; +use crate::graph_walk::types::{NodeSet, ReadyQueue}; + +/// Adjacency from `(node, neighbours)` rows. +pub(super) fn adjacency( + rows: &[(&'static str, &[&'static str])], +) -> HashMap<&'static str, Vec<&'static str>> { + rows.iter() + .map(|(node, neighbours)| (*node, neighbours.to_vec())) + .collect() +} + +/// The neighbours of `node`, none when it has no row. +pub(super) fn neighbours( + edges: &HashMap<&'static str, Vec<&'static str>>, + node: &'static str, +) -> Vec<&'static str> { + edges.get(node).cloned().unwrap_or_default() +} + +/// `edges` reversed, each parent list in row order. +pub(super) fn inverted( + rows: &[(&'static str, &[&'static str])], +) -> HashMap<&'static str, Vec<&'static str>> { + let mut parents: HashMap<&'static str, Vec<&'static str>> = HashMap::new(); + for (node, children) in rows { + for child in *children { + parents.entry(*child).or_default().push(*node); + } + } + parents +} + +/// A visit order holding `starts` when they are included. +pub(super) fn recorded_starts(starts: &[&'static str], include: bool) -> VisitOrder<&'static str> { + let mut visited: VisitOrder<&'static str> = VisitOrder::default(); + for start in starts.iter().filter(|_| include) { + let Ok(_) = visited.insert_new(start); + } + visited +} + +/// The Kahn order of one test case under `ready`, counting every edge. +pub(super) fn kahn_case_order>( + test_case: &KahnTestCase, + ready: Q, +) -> Vec<&'static str> { + let edges = adjacency(test_case.edges); + let mut remaining: HashMap<&'static str, usize> = + test_case.nodes.iter().map(|node| (*node, 0)).collect(); + for (_, successors) in test_case.edges { + for successor in *successors { + *remaining.entry(successor).or_default() += 1; + } + } + kahn_order( + (test_case.nodes.iter().copied(), remaining), + ready, + |node: &&'static str| neighbours(&edges, node), + ) +} diff --git a/crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs b/crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs new file mode 100644 index 0000000000..d1a311f138 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs @@ -0,0 +1,267 @@ +use std::cmp::Reverse; +use std::collections::{BTreeSet, BinaryHeap, VecDeque}; + +use crate::graph_walk::main::breadth_first_path::breadth_first_path; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::main::depth_first_postorder::depth_first_postorder; +use crate::graph_walk::main::directed_path_nodes::directed_path_nodes; +use crate::graph_walk::main::find_cycle::find_cycle; +use crate::graph_walk::models::VisitOrder; +use crate::graph_walk::tests::helpers::{ + adjacency, inverted, kahn_case_order, neighbours, recorded_starts, +}; +use crate::graph_walk::tests::test_types::{ + CycleTestCase, GoalPathTestCase, KahnTestCase, OrderTestCase, PathTestCase, ReachTestCase, +}; + +#[test] +fn given_starts_when_reaching_breadth_first_then_records_nodes_in_first_visit_order() { + let test_cases = [ + ReachTestCase { + description: "starts are left out unless reached again", + edges: &[("a", &["b", "c"]), ("b", &["d", "a"]), ("c", &["d"])], + starts: &["a"], + include_starts: false, + max_depth: None, + expected_order: vec!["b", "c", "d", "a"], + }, + ReachTestCase { + description: "recorded starts lead and are not expanded twice", + edges: &[("a", &["c"]), ("b", &["a", "d"]), ("c", &["b"])], + starts: &["b", "a", "b"], + include_starts: true, + max_depth: None, + expected_order: vec!["b", "a", "d", "c"], + }, + ReachTestCase { + description: "a depth limit stops expansion at that distance", + edges: &[("a", &["b"]), ("b", &["c"]), ("c", &["d"])], + starts: &["a"], + include_starts: false, + max_depth: Some(2), + expected_order: vec!["b", "c"], + }, + ReachTestCase { + description: "a zero depth limit reaches nothing", + edges: &[("a", &["b"])], + starts: &["a"], + include_starts: false, + max_depth: Some(0), + expected_order: vec![], + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let mut visited: VisitOrder<&str> = + recorded_starts(test_case.starts, test_case.include_starts); + let Ok(()) = breadth_first_reach( + test_case.starts.iter().copied(), + test_case.max_depth, + &mut visited, + |node| Ok(neighbours(&edges, node)), + ); + assert_eq!( + visited.order, test_case.expected_order, + "{}", + test_case.description + ); + } +} + +#[test] +fn given_endpoints_when_collecting_path_nodes_then_keeps_nodes_between_them() { + let test_cases = [ + PathTestCase { + description: "branches off the paths are left out", + edges: &[("a", &["b", "x"]), ("b", &["c"]), ("x", &["y"]), ("c", &[])], + endpoints: ("a", "c"), + expected_nodes: Some(vec!["a", "b", "c"]), + }, + PathTestCase { + description: "parallel paths are all kept", + edges: &[ + ("a", &["b", "c"]), + ("b", &["d"]), + ("c", &["d"]), + ("z", &["d"]), + ], + endpoints: ("a", "d"), + expected_nodes: Some(vec!["a", "b", "c", "d"]), + }, + PathTestCase { + description: "an end not downstream of the start has no path", + edges: &[("a", &["b"]), ("c", &["a"])], + endpoints: ("a", "c"), + expected_nodes: None, + }, + ]; + for test_case in test_cases { + let downstream = adjacency(test_case.edges); + let upstream = inverted(test_case.edges); + let mut reachable: BTreeSet<&str> = BTreeSet::new(); + let mut path: BTreeSet<&str> = BTreeSet::new(); + let Ok(found) = directed_path_nodes( + test_case.endpoints, + ( + |node: &&'static str| Ok(neighbours(&downstream, node)), + |node: &&'static str| Ok(neighbours(&upstream, node)), + ), + (&mut reachable, &mut path), + ); + let nodes: Option> = found.then(|| path.into_iter().collect()); + assert_eq!(nodes, test_case.expected_nodes, "{}", test_case.description); + } +} + +#[test] +fn given_goals_when_searching_breadth_first_then_returns_the_shortest_path() { + let test_cases = [ + GoalPathTestCase { + description: "the first goal met on the shortest path ends the search", + edges: &[("a", &["b", "c"]), ("b", &["g"]), ("c", &["h"])], + origin: "a", + goals: &["g", "h"], + blocked: &[], + expected_path: Some(vec!["a", "b", "g"]), + }, + GoalPathTestCase { + description: "the origin can be the goal that closes a cycle", + edges: &[("a", &["b"]), ("b", &["a"])], + origin: "a", + goals: &["a"], + blocked: &[], + expected_path: Some(vec!["a", "b", "a"]), + }, + GoalPathTestCase { + description: "blocked nodes are not entered", + edges: &[("a", &["b"]), ("b", &["g"])], + origin: "a", + goals: &["g"], + blocked: &["b"], + expected_path: None, + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let path: Option> = breadth_first_path( + test_case.origin, + ( + |node: &&'static str| neighbours(&edges, node), + |node: &&'static str| test_case.goals.contains(node), + |node: &&'static str| !test_case.blocked.contains(node), + ), + ); + assert_eq!(path, test_case.expected_path, "{}", test_case.description); + } +} + +#[test] +fn given_counts_when_ordering_first_in_first_out_then_keeps_node_order_among_ready_nodes() { + let test_cases = [ + KahnTestCase { + description: "ready nodes leave in node order", + edges: &[("c", &["a"]), ("b", &["a"]), ("a", &[])], + nodes: &["c", "b", "a"], + expected_order: vec!["c", "b", "a"], + }, + KahnTestCase { + description: "repeated edges lower the count once each", + edges: &[("a", &["b", "b"]), ("b", &[])], + nodes: &["a", "b"], + expected_order: vec!["a", "b"], + }, + KahnTestCase { + description: "nodes on a cycle are left out", + edges: &[("a", &["b"]), ("b", &["c"]), ("c", &["b"])], + nodes: &["a", "b", "c"], + expected_order: vec!["a"], + }, + ]; + for test_case in test_cases { + let order: Vec<&str> = kahn_case_order(&test_case, VecDeque::new()); + assert_eq!(order, test_case.expected_order, "{}", test_case.description); + } +} + +#[test] +fn given_counts_when_ordering_smallest_first_then_takes_the_smallest_ready_node() { + let test_cases = [KahnTestCase { + description: "the smallest ready node leaves first", + edges: &[("c", &["a"]), ("b", &["a"]), ("a", &[])], + nodes: &["c", "b", "a"], + expected_order: vec!["b", "c", "a"], + }]; + for test_case in test_cases { + let order: Vec<&str> = + kahn_case_order(&test_case, BinaryHeap::>::new()); + assert_eq!(order, test_case.expected_order, "{}", test_case.description); + } +} + +#[test] +fn given_roots_when_ordering_depth_first_then_places_nodes_after_their_neighbours() { + let test_cases = [ + OrderTestCase { + description: "neighbours come first in their given order", + edges: &[("a", &["c", "b"]), ("b", &["d"]), ("c", &["d"])], + roots: &["a"], + expected_order: vec!["d", "c", "b", "a"], + }, + OrderTestCase { + description: "later roots skip nodes already placed", + edges: &[("b", &["c"]), ("a", &["c"])], + roots: &["b", "a"], + expected_order: vec!["c", "b", "a"], + }, + OrderTestCase { + description: "a cycle back to an entered node is not followed", + edges: &[("a", &["b"]), ("b", &["a"])], + roots: &["a"], + expected_order: vec!["b", "a"], + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let order: Vec<&str> = depth_first_postorder(test_case.roots.iter().copied(), |node| { + neighbours(&edges, node) + }); + assert_eq!(order, test_case.expected_order, "{}", test_case.description); + } +} + +#[test] +fn given_roots_when_searching_for_cycles_then_returns_the_path_to_the_repeat() { + let test_cases = [ + CycleTestCase { + description: "the path runs from the root to the repeated node", + edges: &[("a", &["b"]), ("b", &["c"]), ("c", &["b"])], + roots: &["a"], + expected_cycle: Some(vec!["a", "b", "c", "b"]), + }, + CycleTestCase { + description: "shared finished nodes are not a cycle", + edges: &[("a", &["c"]), ("b", &["c"]), ("c", &[])], + roots: &["a", "b"], + expected_cycle: None, + }, + CycleTestCase { + description: "a self edge is a cycle", + edges: &[("a", &["a"])], + roots: &["a"], + expected_cycle: Some(vec!["a", "a"]), + }, + CycleTestCase { + description: "a later root finds a cycle the earlier roots missed", + edges: &[("a", &[]), ("b", &["c"]), ("c", &["b"])], + roots: &["a", "b"], + expected_cycle: Some(vec!["b", "c", "b"]), + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let cycle: Option> = find_cycle(test_case.roots.iter().copied(), |node| { + neighbours(&edges, node) + }); + assert_eq!(cycle, test_case.expected_cycle, "{}", test_case.description); + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/tests/test_types.rs b/crates/sqlbuild-core/src/graph_walk/tests/test_types.rs new file mode 100644 index 0000000000..16a8025d68 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests/test_types.rs @@ -0,0 +1,47 @@ +pub(super) type Rows = &'static [(&'static str, &'static [&'static str])]; + +pub(super) struct ReachTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) starts: &'static [&'static str], + pub(super) include_starts: bool, + pub(super) max_depth: Option, + pub(super) expected_order: Vec<&'static str>, +} + +pub(super) struct PathTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) endpoints: (&'static str, &'static str), + pub(super) expected_nodes: Option>, +} + +pub(super) struct GoalPathTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) origin: &'static str, + pub(super) goals: &'static [&'static str], + pub(super) blocked: &'static [&'static str], + pub(super) expected_path: Option>, +} + +pub(super) struct KahnTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) nodes: &'static [&'static str], + pub(super) expected_order: Vec<&'static str>, +} + +pub(super) struct OrderTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) roots: &'static [&'static str], + pub(super) expected_order: Vec<&'static str>, +} + +pub(super) struct CycleTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) roots: &'static [&'static str], + pub(super) expected_cycle: Option>, +} diff --git a/crates/sqlbuild-core/src/graph_walk/types.rs b/crates/sqlbuild-core/src/graph_walk/types.rs new file mode 100644 index 0000000000..684d214b36 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/types.rs @@ -0,0 +1,19 @@ +//! Visited-node sets and ready queues the graph walks are generic over. + +/// A set of graph nodes a walk records and queries, possibly through a fallible host. +pub trait NodeSet { + /// The failure a fallible set or its walk's neighbour lookup reports. + type Error; + /// Record `key`, returning whether it was absent. + fn insert_new(&mut self, key: &K) -> Result; + /// Whether `key` is recorded. + fn holds(&self, key: &K) -> Result; +} + +/// Nodes a topological walk may take next, in the caller's tie-break order. +pub trait ReadyQueue { + /// Offer a node whose dependencies are all taken. + fn offer(&mut self, key: K); + /// The next node to take. + fn take(&mut self) -> Option; +} diff --git a/crates/sqlbuild-core/src/lib.rs b/crates/sqlbuild-core/src/lib.rs index 99383f712f..2569bf6dc1 100644 --- a/crates/sqlbuild-core/src/lib.rs +++ b/crates/sqlbuild-core/src/lib.rs @@ -30,6 +30,7 @@ pub mod dlt_destination; pub mod event_export; pub mod format_command; pub mod freshness_text; +pub mod graph_walk; pub mod identifier_text; pub mod ingestr_command; pub mod init_command; diff --git a/crates/sqlbuild-core/src/load_command/_helpers/graph.rs b/crates/sqlbuild-core/src/load_command/_helpers/graph.rs index 2d337e6e96..be1c836485 100644 --- a/crates/sqlbuild-core/src/load_command/_helpers/graph.rs +++ b/crates/sqlbuild-core/src/load_command/_helpers/graph.rs @@ -1,7 +1,10 @@ //! Loader graph lookups: dependencies, closures and execution order. use std::collections::{BTreeSet, HashMap}; +use std::convert::Infallible; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::main::depth_first_postorder::depth_first_postorder; use crate::load_command::models::LoadGraph; /// Indexed views of a load graph, as the Python dictionaries keyed them. @@ -50,30 +53,25 @@ impl GraphIndex { /// The loader and every loader it transitively depends on. pub(crate) fn upstream_closure(&self, loader: &str) -> BTreeSet { - let mut closure: BTreeSet = BTreeSet::new(); - let mut pending: Vec = vec![loader.to_owned()]; - while let Some(next) = pending.pop() { - if closure.insert(next.clone()) { - pending.extend(self.dependencies(&next).iter().cloned()); - } - } + let mut closure: BTreeSet = BTreeSet::from([loader.to_owned()]); + let Ok(()) = breadth_first_reach([loader.to_owned()], None, &mut closure, |next| { + Ok::<_, Infallible>(self.dependencies(next).iter().cloned()) + }); closure } /// The loader, every loader depending on it transitively, and all their sources. pub(crate) fn downstream_closure(&self, loader: &str) -> (BTreeSet, BTreeSet) { - let mut loaders: BTreeSet = BTreeSet::new(); - let mut pending: Vec = vec![loader.to_owned()]; - while let Some(next) = pending.pop() { - if !loaders.insert(next.clone()) { - continue; - } - for (candidate, dependencies) in &self.upstream { - if dependencies.contains(&next) { - pending.push(candidate.clone()); - } - } - } + let mut loaders: BTreeSet = BTreeSet::from([loader.to_owned()]); + let Ok(()) = breadth_first_reach([loader.to_owned()], None, &mut loaders, |next| { + Ok::<_, Infallible>( + self.upstream + .iter() + .filter(|(_, dependencies)| dependencies.contains(next)) + .map(|(candidate, _)| candidate.clone()) + .collect::>(), + ) + }); let sources: BTreeSet = loaders .iter() .flat_map(|name| { @@ -89,28 +87,12 @@ impl GraphIndex { /// `names` ordered dependencies first, visiting in sorted order. pub(crate) fn topological_order(&self, names: &BTreeSet) -> Vec { - let mut ordered: Vec = Vec::new(); - let mut visited: BTreeSet = BTreeSet::new(); - for name in names { - self.visit(name, names, (&mut ordered, &mut visited)); - } - ordered - } - - fn visit( - &self, - name: &str, - names: &BTreeSet, - (ordered, visited): (&mut Vec, &mut BTreeSet), - ) { - if !visited.insert(name.to_owned()) { - return; - } - for dependency in self.dependencies(name) { - if names.contains(dependency) { - self.visit(dependency, names, (ordered, visited)); - } - } - ordered.push(name.to_owned()); + depth_first_postorder(names.iter().cloned(), |name| { + self.dependencies(name) + .iter() + .filter(|dependency| names.contains(*dependency)) + .cloned() + .collect::>() + }) } } diff --git a/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs b/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs index 17635232b1..0499eecbdf 100644 --- a/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs +++ b/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs @@ -1,7 +1,9 @@ //! Applying load selectors and excludes to the selected source and loader sets. use std::collections::BTreeSet; +use std::convert::Infallible; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::load_command::_helpers::graph::GraphIndex; use crate::load_command::constants::UPSTREAM_MARK; use crate::load_command::errors::UnknownLoadSelector; @@ -177,14 +179,16 @@ impl Selection { needed.insert(loader.clone()); } } - let mut pending: Vec = needed.iter().cloned().collect(); - while let Some(loader) = pending.pop() { - for dependency in index.dependencies(&loader) { - if self.loaders.contains(dependency) && needed.insert(dependency.clone()) { - pending.push(dependency.clone()); - } - } - } + let starts: Vec = needed.iter().cloned().collect(); + let Ok(()) = breadth_first_reach(starts, None, &mut needed, |loader| { + Ok::<_, Infallible>( + index + .dependencies(loader) + .iter() + .filter(|dependency| self.loaders.contains(*dependency)) + .cloned(), + ) + }); self.loaders.retain(|loader| needed.contains(loader)); } } diff --git a/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs b/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs index 5547a19d0f..c8c784d9aa 100644 --- a/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs +++ b/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs @@ -1,6 +1,9 @@ //! Python node dependency checks: known targets, no SQL inputs on loaders, no cycles. use std::collections::{HashMap, HashSet}; +use std::hash::{Hash, Hasher}; + +use sqlbuild_core::graph_walk::main::find_cycle::find_cycle as find_dependency_cycle; use crate::project_validation::_helpers::conflicts::conflict; use crate::project_validation::models::{DependencyFact, NodeFact, NodeKind, ValidationFailure}; @@ -172,46 +175,39 @@ fn find_cycle( next: impl Fn(usize) -> Vec, label: &str, ) -> Result<(), ValidationFailure> { - let mut search: CycleSearch<'_, _> = CycleSearch { - nodes, - next, - label, - path: Vec::new(), - visited: HashSet::new(), + let named = |index: usize| NamedNode { + name: &nodes[index].name, + index, }; - for root in roots { - search.path.clear(); - search.visit(*root)?; - } - Ok(()) + let Some(cycle) = find_dependency_cycle(roots.iter().map(|root| named(*root)), |node| { + next(node.index) + .into_iter() + .map(named) + .collect::>>() + }) else { + return Ok(()); + }; + let names: Vec<&str> = cycle.iter().map(|node| node.name).collect(); + Err(conflict(format!("{label}: {}", names.join(" -> ")))) } -/// One depth-first cycle search: the nodes on the current path and those already cleared. -struct CycleSearch<'a, F> { - nodes: &'a [NodeFact], - next: F, - label: &'a str, - path: Vec<&'a str>, - visited: HashSet<&'a str>, +/// A node position whose identity in a cycle search is its name. +#[derive(Clone, Copy)] +struct NamedNode<'a> { + name: &'a str, + index: usize, } -impl<'a, F: Fn(usize) -> Vec> CycleSearch<'a, F> { - fn visit(&mut self, index: usize) -> Result<(), ValidationFailure> { - let name: &'a str = &self.nodes[index].name; - if self.visited.contains(name) { - return Ok(()); - } - if self.path.contains(&name) { - let mut cycle: Vec<&str> = self.path.clone(); - cycle.push(name); - return Err(conflict(format!("{}: {}", self.label, cycle.join(" -> ")))); - } - self.path.push(name); - for dependency in (self.next)(index) { - self.visit(dependency)?; - } - self.path.pop(); - self.visited.insert(name); - Ok(()) +impl PartialEq for NamedNode<'_> { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for NamedNode<'_> {} + +impl Hash for NamedNode<'_> { + fn hash(&self, state: &mut H) { + self.name.hash(state); } } diff --git a/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs b/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs index b9f3be44fb..1816f93c95 100644 --- a/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs +++ b/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs @@ -4,6 +4,7 @@ use std::cmp::Reverse; use std::collections::{BTreeSet, BinaryHeap, HashMap}; use sha2::{Digest, Sha256}; +use sqlbuild_core::graph_walk::main::kahn_order::kahn_order; use crate::macro_modules::constants::{SYNTHETIC_DIGEST_LENGTH, SYNTHETIC_PACKAGE_PREFIX}; use crate::macro_modules::models::MacroModuleSpec; @@ -50,22 +51,13 @@ pub(crate) fn load_order(modules: &[MacroModuleSpec]) -> Vec { .push(&module.name); } } - let mut ready: BinaryHeap> = remaining - .iter() - .filter(|(_, count)| **count == 0) - .map(|(name, _)| Reverse(*name)) - .collect(); - let mut order: Vec = Vec::new(); - while let Some(Reverse(name)) = ready.pop() { - order.push(name.to_owned()); - for dependent in dependents.get(name).into_iter().flatten() { - if let Some(count) = remaining.get_mut(dependent) { - *count -= 1; - if *count == 0 { - ready.push(Reverse(dependent)); - } - } - } - } - order + let names: Vec<&str> = remaining.keys().copied().collect(); + kahn_order( + (names, remaining), + BinaryHeap::>::new(), + |name| dependents.get(name).cloned().unwrap_or_default(), + ) + .into_iter() + .map(str::to_owned) + .collect() } diff --git a/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs b/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs index a627a23261..6d792fd38f 100644 --- a/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs +++ b/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs @@ -3,8 +3,14 @@ use ruff_python_ast::ExceptHandler; use ruff_python_ast::{ModModule, Stmt, StmtFunctionDef, StmtImportFrom}; use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; use std::iter::once; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::find_cycle::find_cycle; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; + use crate::cpython_tree::models::CpythonNode; use crate::macro_modules::_helpers::declarations::module_exports; use crate::macro_modules::_helpers::function_calls::{ @@ -292,21 +298,20 @@ fn export_dependencies( ) -> HashMap> { let mut dependencies: HashMap> = HashMap::new(); for export in exports { + let mut visited: VisitOrder<&str> = VisitOrder::default(); + let Ok(_) = visited.insert_new(&export.as_str()); + let Ok(()) = breadth_first_reach([export.as_str()], None, &mut visited, |function| { + Ok::<_, Infallible>(helper_calls[*function].iter().map(String::as_str)) + }); let mut found: Vec = Vec::new(); - let mut pending: std::collections::VecDeque<&str> = - std::collections::VecDeque::from([export.as_str()]); - let mut visited: HashSet<&str> = HashSet::new(); - while let Some(function) = pending.pop_front() { - if !visited.insert(function) { - continue; - } + for function in visited.order { found.extend(direct_dependencies[function].iter().cloned()); - for called in &helper_calls[function] { - if exports.contains(called) { - found.push(called.clone()); - } - pending.push_back(called); - } + found.extend( + helper_calls[function] + .iter() + .filter(|called| exports.contains(*called)) + .cloned(), + ); } dependencies.insert(export.clone(), unique(found)); } @@ -320,38 +325,23 @@ fn validate_local_call_cycles( ) -> Result<(), String> { let mut sorted: Vec<&String> = exports.iter().collect(); sorted.sort(); - let mut completed: HashSet = HashSet::new(); - for export in sorted { - completed = visit_cycle(export, (helper_calls, &[]), completed).map_err(|cycle| { - format!( - "Macro call cycle in '{display}': {}", - cycle.join(CYCLE_SEPARATOR) - ) - })?; - } - Ok(()) + let Some(cycle) = repeated_path(sorted, helper_calls) else { + return Ok(()); + }; + let names: Vec<&str> = cycle.iter().map(|name| name.as_str()).collect(); + Err(format!( + "Macro call cycle in '{display}': {}", + names.join(CYCLE_SEPARATOR) + )) } -/// Depth-first cycle search returning the completed names; the error is the cycle path. -pub(crate) fn visit_cycle( - name: &str, - (edges, active): (&HashMap>, &[String]), - completed: HashSet, -) -> Result, Vec> { - if let Some(start) = active.iter().position(|item| item == name) { - let mut cycle: Vec = active[start..].to_vec(); - cycle.push(name.to_owned()); - return Err(cycle); - } - if completed.contains(name) { - return Ok(completed); - } - let mut path: Vec = active.to_vec(); - path.push(name.to_owned()); - let mut updated: HashSet = completed; - for dependency in &edges[name] { - updated = visit_cycle(dependency, (edges, &path), updated)?; - } - updated.insert(name.to_owned()); - Ok(updated) +/// The first dependency cycle depth first from `roots` in order, from its repeated name back to itself. +pub(crate) fn repeated_path<'a>( + roots: Vec<&'a String>, + edges: &'a HashMap>, +) -> Option> { + let path: Vec<&String> = find_cycle(roots, |name| edges.get(*name).into_iter().flatten())?; + let repeated: &String = path.last()?; + let start: usize = path.iter().position(|name| name == &repeated)?; + Some(path[start..].to_vec()) } diff --git a/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs b/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs index 5d60896932..5eccae6498 100644 --- a/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs +++ b/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs @@ -11,7 +11,7 @@ use crate::macro_modules::_helpers::declarations::{ top_level_functions, }; use crate::macro_modules::_helpers::module_analysis::{ - ModuleAnalysis, ModuleSet, ParsedModule, analyze_module, parsed_module, visit_cycle, + ModuleAnalysis, ModuleSet, ParsedModule, analyze_module, parsed_module, repeated_path, }; use crate::macro_modules::_helpers::module_names::{module_name, package_name}; use crate::macro_modules::constants::CYCLE_SEPARATOR; @@ -123,18 +123,16 @@ fn validate_import_cycles( .collect(); let mut sorted: Vec<&String> = edges.keys().collect(); sorted.sort(); - let mut completed: HashSet = HashSet::new(); - for name in sorted { - completed = visit_cycle(name, (&edges, &[]), completed).map_err(|cycle| { - let repeated: &str = cycle.last().map_or("", String::as_str); - format!( - "Macro import cycle in '{}': {}", - project.sources[files[repeated]].relative_path.display, - cycle.join(CYCLE_SEPARATOR) - ) - })?; - } - Ok(()) + let Some(cycle) = repeated_path(sorted, &edges) else { + return Ok(()); + }; + let repeated: &str = cycle.last().map_or("", |name| name.as_str()); + let names: Vec<&str> = cycle.iter().map(|name| name.as_str()).collect(); + Err(format!( + "Macro import cycle in '{}': {}", + project.sources[files[repeated]].relative_path.display, + names.join(CYCLE_SEPARATOR) + )) } fn source_digest(contents: &str) -> String { diff --git a/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs b/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs index f8c040ac98..05ceadd2f0 100644 --- a/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs +++ b/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs @@ -1,8 +1,11 @@ //! `import_closure`: the repository-owned helper modules one rule module imports. use std::collections::BTreeSet; +use std::convert::Infallible; use std::path::{Path, PathBuf}; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; + use crate::cpython_tree::main::parse_cpython_module::parse_cpython_module; use crate::cpython_tree::main::walk_breadth_first::walk_breadth_first; use crate::cpython_tree::models::{CpythonNode, ParsedSource}; @@ -17,27 +20,21 @@ pub fn import_closure( python_version: (u8, u8), ) -> Vec { let rules_root: PathBuf = resolve(&project_dir.join(RULES_DIRECTORY_NAME)); - let mut pending: Vec = vec![source_path.to_path_buf()]; - let mut visited: BTreeSet = BTreeSet::from([source_path.to_path_buf()]); let mut helpers: BTreeSet = BTreeSet::new(); - while let Some(current) = pending.pop() { + let Ok(()) = breadth_first_reach([source_path.to_path_buf()], None, &mut helpers, |current| { let mut candidates: BTreeSet = - package_inits(¤t, &rules_root).into_iter().collect(); + package_inits(current, &rules_root).into_iter().collect(); let parsed: Option = - parse_cpython_module(&read_source(¤t), python_version); + parse_cpython_module(&read_source(current), python_version); if let Some(parsed) = &parsed { let roots: Vec> = parsed.module.body.iter().map(CpythonNode::Stmt).collect(); for node in walk_breadth_first(roots) { - candidates.extend(local_import_paths(&node, ¤t, &rules_root)); - } - } - for candidate in candidates { - if visited.insert(candidate.clone()) { - helpers.insert(candidate.clone()); - pending.push(candidate); + candidates.extend(local_import_paths(&node, current, &rules_root)); } } - } + candidates.remove(source_path); + Ok::<_, Infallible>(candidates) + }); helpers.into_iter().collect() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs index dbeb9c3956..28dd9b0966 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs @@ -1,12 +1,14 @@ -//! Breadth-first closure, edge inversion and path nodes over any Python-keyed edge mapping. - -use std::collections::VecDeque; +//! Closure, edge inversion and path nodes over any Python-keyed edge mapping. use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{ PyDict, PyDictMethods, PyFrozenSet, PyList, PyListMethods, PySet, PySetMethods, PyTuple, }; use pyo3::{pyfunction, wrap_pyfunction}; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; + +use crate::bindings::_helpers::graph::node_set::PyNodeSet; const BUILTINS_MODULE: &str = "builtins"; @@ -17,29 +19,32 @@ pub(crate) fn closure<'py>( (include_starts, max_depth): (bool, Option), ) -> PyResult> { let py: Python<'py> = edges.py(); - let visited: Bound<'py, PySet> = PySet::empty(py)?; + let mut visited: PyNodeSet<'py> = PyNodeSet(PySet::empty(py)?); if include_starts { for start in &starts { - visited.add(start)?; + visited.0.add(start)?; } } - let mut frontier: VecDeque<(Bound<'py, PyAny>, i64)> = - starts.into_iter().map(|start| (start, 0)).collect(); let none: Bound<'py, PyTuple> = PyTuple::empty(py); - while let Some((current, depth)) = frontier.pop_front() { - if max_depth.is_some_and(|maximum| depth >= maximum) { - continue; - } - for neighbor in edges.call_method1("get", (¤t, &none))?.try_iter()? { - let neighbor: Bound<'py, PyAny> = neighbor?; - if visited.contains(&neighbor)? { - continue; - } - visited.add(&neighbor)?; - frontier.push_back((neighbor, depth + 1)); - } - } - Ok(visited) + breadth_first_reach( + starts, + max_depth.map(|maximum| u64::try_from(maximum).unwrap_or(0)), + &mut visited, + |current| neighbours(edges, current, &none), + )?; + Ok(visited.0) +} + +/// The keys `edges.get(current, ())` lists. +fn neighbours<'py>( + edges: &Bound<'py, PyAny>, + current: &Bound<'py, PyAny>, + none: &Bound<'py, PyTuple>, +) -> PyResult>> { + edges + .call_method1("get", (current, none))? + .try_iter()? + .collect() } /// Keys within `max_depth` edges of any start, plus the starts when requested. @@ -130,35 +135,22 @@ fn path_nodes<'py>( downstream: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = downstream.py(); - let reachable: Bound<'py, PySet> = closure(vec![start.clone()], downstream, (false, None))?; - if !reachable.contains(&end)? { - return Ok(None); - } let upstream: Bound<'py, PyDict> = inverted(downstream)?; - let upstream_from_end: Bound<'py, PySet> = PySet::empty(py)?; - let mut stack: Vec> = vec![end.clone()]; - while let Some(current) = stack.pop() { - if upstream_from_end.contains(¤t)? { - continue; - } - upstream_from_end.add(¤t)?; - let Some(parents) = upstream.get_item(¤t)? else { - continue; - }; - for parent in parents.try_iter()? { - let parent: Bound<'py, PyAny> = parent?; - if reachable.contains(&parent)? || parent.eq(&start)? { - stack.push(parent); - } - } + let none: Bound<'py, PyTuple> = PyTuple::empty(py); + let mut reachable: PyNodeSet<'py> = PyNodeSet(PySet::empty(py)?); + let mut path: PyNodeSet<'py> = PyNodeSet(PySet::empty(py)?); + let found: bool = directed_path_nodes( + (start, end), + ( + |current: &Bound<'py, PyAny>| neighbours(downstream, current, &none), + |current: &Bound<'py, PyAny>| neighbours(upstream.as_any(), current, &none), + ), + (&mut reachable, &mut path), + )?; + if !found { + return Ok(None); } - let nodes: Bound<'py, PyAny> = reachable - .call_method1("__and__", (&upstream_from_end,))? - .call_method1("__or__", (PySet::new(py, [&start, &end])?,))?; - Ok(Some(PyFrozenSet::new( - py, - nodes.try_iter()?.collect::>>()?, - )?)) + Ok(Some(PyFrozenSet::new(py, path.0.iter())?)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs index af9d80ac3c..40cb605076 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod algorithms; mod execution_gates; pub(crate) mod keys; +pub(crate) mod node_set; pub(crate) mod project_graph; mod python_lifecycle; pub(crate) mod python_node_graph; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs new file mode 100644 index 0000000000..488de612b0 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs @@ -0,0 +1,25 @@ +//! A Python set as the visited nodes of a native graph walk over Python keys. + +use pyo3::PyErr; +use pyo3::prelude::{Bound, PyAny}; +use pyo3::types::{PySet, PySetMethods}; +use sqlbuild_core::graph_walk::types::NodeSet; + +/// Python keys a walk visited, compared with Python hashing and equality. +pub(crate) struct PyNodeSet<'py>(pub(crate) Bound<'py, PySet>); + +impl<'py> NodeSet> for PyNodeSet<'py> { + type Error = PyErr; + + fn insert_new(&mut self, key: &Bound<'py, PyAny>) -> Result { + if self.0.contains(key)? { + return Ok(false); + } + self.0.add(key)?; + Ok(true) + } + + fn holds(&self, key: &Bound<'py, PyAny>) -> Result { + self.0.contains(key) + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs index a1c5aebf53..6676aacc5f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs @@ -1,6 +1,7 @@ //! Model ordering, selection and metadata at the migration discovery boundary. use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::graph::node_set::PyNodeSet; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_migrations::column_objects::native; @@ -9,6 +10,7 @@ use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PySet, PySetMethods}; use sqlbuild_analysis::planner_migrations::main::discovery_order::discovery_order; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use std::collections::BTreeMap; pub(super) fn ordered<'py>(runtime: &Bound<'py, PyAny>) -> PyResult>> { @@ -44,20 +46,15 @@ pub(super) fn triggered( .getattr("selected_keys")? .try_iter()? .collect::>()?; - let relevant = PySet::new(py, &selected)?; - let mut frontier = selected; - while let Some(key) = frontier.pop() { - let upstream = scope + let mut relevant = PyNodeSet(PySet::new(py, &selected)?); + breadth_first_reach(selected, None, &mut relevant, |key| { + scope .getattr("upstream_deps")? - .call_method1("get", (key, pyo3::types::PyTuple::empty(py)))?; - for dependency in upstream.try_iter()? { - let dependency = dependency?; - if !relevant.contains(&dependency)? { - relevant.add(&dependency)?; - frontier.push(dependency); - } - } - } + .call_method1("get", (key, pyo3::types::PyTuple::empty(py)))? + .try_iter()? + .collect::>>>() + })?; + let relevant = relevant.0; let fingerprints = snapshot.getattr("fingerprints")?.getattr("models")?; for item in models { let name = item.getattr("name")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs index 24d78cf850..f0c4c41ace 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs @@ -1,11 +1,12 @@ //! Graph walks over the natively hosted Python-node graph record. -use std::collections::HashSet; - use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple, PyTupleMethods, }; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; + +use crate::bindings::_helpers::graph::node_set::PyNodeSet; /// `PythonNodeGraph.selected_sql_refs(selected_names=...)`: SQL references of the selected closure. pub(crate) fn graph_selected_sql_refs( @@ -25,26 +26,21 @@ pub(crate) fn graph_selected_sql_refs( let nodes_by_name: Bound<'_, PyAny> = graph.getattr("nodes_by_name")?; let upstream: Bound<'_, PyAny> = graph.getattr("upstream_deps")?; let refs: Bound<'_, PySet> = PySet::empty(py)?; - let mut pending: Vec = selected - .try_iter()? - .map(|name| name?.extract::()) - .collect::>()?; - let mut visited: HashSet = HashSet::new(); - while let Some(name) = pending.pop() { - if !visited.insert(name.clone()) { - continue; - } - let node: Bound<'_, PyAny> = nodes_by_name.call_method1("get", (name.as_str(),))?; + let starts: Vec> = selected.try_iter()?.collect::>()?; + let mut visited: PyNodeSet<'_> = PyNodeSet(PySet::new(py, &starts)?); + breadth_first_reach(starts, None, &mut visited, |name| { + upstream + .call_method1("get", (name, PyTuple::empty(py)))? + .try_iter()? + .collect::>>>() + })?; + for name in visited.0.iter() { + let node: Bound<'_, PyAny> = nodes_by_name.call_method1("get", (name,))?; if !node.is_none() { for reference in node.getattr("sql_deps")?.try_iter()? { refs.add(reference?)?; } } - let parents: Bound<'_, PyAny> = - upstream.call_method1("get", (name.as_str(), PyTuple::empty(py)))?; - for parent in parents.try_iter()? { - pending.push(parent?.extract()?); - } } Ok(PyFrozenSet::new(py, refs.iter())?.into_any().unbind()) } From be4ff2155f4885f157814d437aca3197335aa42b Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Fri, 16 Oct 2026 06:20:35 +0100 Subject: [PATCH 3/4] fix: wait for abandoned scenario workers before interpreter finalization Scenario workers are daemon threads that run native scenario planning, which calls back into the adapter. When a run is abandoned, the main thread exits while a worker can still be inside that native frame, and finalization ending the thread there aborts the process with "exception not rethrown". Register an exit hook when workers start that joins the named scenario workers within a bounded time, so they leave native code first. Add a fresh-process regression test that abandons workers inside a native frame calling slow Python. --- .../executor/pipeline/_helpers/scenario.py | 17 ++++- .../executor/pipeline/_helpers/_test_types.py | 9 +++ .../test_scenario_interpreter_exit.py | 76 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py diff --git a/src/sqlbuild/executor/pipeline/_helpers/scenario.py b/src/sqlbuild/executor/pipeline/_helpers/scenario.py index 47b2649c76..6aa524294a 100644 --- a/src/sqlbuild/executor/pipeline/_helpers/scenario.py +++ b/src/sqlbuild/executor/pipeline/_helpers/scenario.py @@ -2,6 +2,7 @@ from __future__ import annotations +import atexit import logging import queue import threading @@ -68,6 +69,8 @@ _DEBUG_LOGGER: logging.Logger = logging.getLogger("sqlbuild.execution") _STOP_POLL_SECONDS: float = 0.05 +_WORKER_EXIT_JOIN_SECONDS: float = 5.0 +_WORKER_NAME_PREFIX: str = "sqlbuild-scenario-" _SCENARIO_INTERNAL_ERROR_HELP: str = ( "This is likely a SQLBuild bug. Please file an issue with the scenario name." ) @@ -558,17 +561,29 @@ def work() -> None: except BaseException as exc: # noqa: BLE001 - delivered through the future future.set_exception(exc) + atexit.unregister(_join_scenario_workers) + _ = atexit.register(_join_scenario_workers) context: Context = copy_context() worker: int for worker in range(worker_count): threading.Thread( target=context.copy().run, args=(work,), - name=f"sqlbuild-scenario-{worker}", + name=f"{_WORKER_NAME_PREFIX}{worker}", daemon=True, ).start() +def _join_scenario_workers() -> None: + """Wait a bounded time at exit for abandoned workers, so none is ended inside native code.""" + + deadline: float = time.monotonic() + _WORKER_EXIT_JOIN_SECONDS + thread: threading.Thread + for thread in threading.enumerate(): + if thread.name.startswith(_WORKER_NAME_PREFIX): + thread.join(timeout=max(0.0, deadline - time.monotonic())) + + def _stop_scenarios[OutcomeT]( *, error: BaseException, diff --git a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py index 6222f977da..ea148bd256 100644 --- a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py +++ b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py @@ -176,3 +176,12 @@ class ConnectionPreparationTimingTestCase: expected_connection_seconds: float expected_schema_seconds: float expected_total_seconds: float + + +@dataclass(frozen=True) +class ScenarioWorkerExitTestCase: + """Scenario workers left inside native code when the interpreter exits, and the exit code.""" + + description: str + worker_count: int + expected_returncode: int diff --git a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py new file mode 100644 index 0000000000..6f4503c893 --- /dev/null +++ b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py @@ -0,0 +1,76 @@ +import subprocess +import sys +from pathlib import Path +from textwrap import dedent + +import pytest + +from tests.unit.src.sqlbuild.executor.pipeline._helpers._test_types import ( + ScenarioWorkerExitTestCase, +) + +_ABANDONED_WORKERS: str = dedent( + """ + import sys + import time + import types + from concurrent.futures import Future + + + class SlowFinalizer: + def __del__(self): + print("finalizing", sys.is_finalizing(), file=sys.stderr, flush=True) + time.sleep(1.5) + + + holder = types.ModuleType("holder") + holder.finalizer = SlowFinalizer() + sys.modules["holder"] = holder + + from sqlbuild._native import invert_edges + from sqlbuild.executor.pipeline._helpers.scenario import _start_scenario_workers + + + def slow_key(item): + time.sleep(1.0) + return item + + + def run(*, scenario, notify_start): + return invert_edges(edges={"a": ("b",), "c": ("b",)}, sort_key=slow_key) + + + count = int(sys.argv[1]) + scenarios = tuple(f"scenario_{index}" for index in range(count)) + futures = tuple(Future() for _ in scenarios) + _start_scenario_workers(scenarios=scenarios, futures=futures, run=run, worker_count=count) + time.sleep(0.3) + """ +) + + +@pytest.mark.parametrize( + "test_case", + ( + ScenarioWorkerExitTestCase("one abandoned worker inside native code", 1, 0), + ScenarioWorkerExitTestCase("several abandoned workers inside native code", 3, 0), + ), + ids=lambda case: case.description, +) +def test_given_abandoned_scenario_workers_when_interpreter_finalizes_then_process_exits_cleanly( + test_case: ScenarioWorkerExitTestCase, + tmp_path: Path, +) -> None: + script: Path = tmp_path / "scenario_exit.py" + script.write_text(_ABANDONED_WORKERS, encoding="utf-8") + + result: subprocess.CompletedProcess[str] = subprocess.run( + [sys.executable, str(script), str(test_case.worker_count)], + capture_output=True, + text=True, + timeout=60, + check=False, + ) + + assert result.returncode == test_case.expected_returncode, result.stderr + assert "finalizing True" in result.stderr From 6d4c7e10f7c1d2cb70a2ae7647bc473e6ce83547 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Fri, 16 Oct 2026 08:08:21 +0100 Subject: [PATCH 4/4] fix: tell users about the scenario exit wait and ignore a further Ctrl-C during it --- .../executor/pipeline/_helpers/scenario.py | 17 +++++++++++++++-- .../_helpers/test_scenario_interpreter_exit.py | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sqlbuild/executor/pipeline/_helpers/scenario.py b/src/sqlbuild/executor/pipeline/_helpers/scenario.py index 6aa524294a..e7e8ec9329 100644 --- a/src/sqlbuild/executor/pipeline/_helpers/scenario.py +++ b/src/sqlbuild/executor/pipeline/_helpers/scenario.py @@ -5,6 +5,7 @@ import atexit import logging import queue +import sys import threading import time from collections.abc import Callable, Iterator @@ -70,6 +71,7 @@ _DEBUG_LOGGER: logging.Logger = logging.getLogger("sqlbuild.execution") _STOP_POLL_SECONDS: float = 0.05 _WORKER_EXIT_JOIN_SECONDS: float = 5.0 +_WORKER_EXIT_NOTICE: str = "Waiting up to 5s for running scenario statements to stop...\n" _WORKER_NAME_PREFIX: str = "sqlbuild-scenario-" _SCENARIO_INTERNAL_ERROR_HELP: str = ( "This is likely a SQLBuild bug. Please file an issue with the scenario name." @@ -577,11 +579,22 @@ def work() -> None: def _join_scenario_workers() -> None: """Wait a bounded time at exit for abandoned workers, so none is ended inside native code.""" + workers: list[threading.Thread] = [ + thread + for thread in threading.enumerate() + if thread.name.startswith(_WORKER_NAME_PREFIX) and thread.is_alive() + ] + if not workers: + return + _ = sys.stderr.write(_WORKER_EXIT_NOTICE) + _ = sys.stderr.flush() deadline: float = time.monotonic() + _WORKER_EXIT_JOIN_SECONDS thread: threading.Thread - for thread in threading.enumerate(): - if thread.name.startswith(_WORKER_NAME_PREFIX): + try: + for thread in workers: thread.join(timeout=max(0.0, deadline - time.monotonic())) + except KeyboardInterrupt: + return def _stop_scenarios[OutcomeT]( diff --git a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py index 6f4503c893..9eb52a13b0 100644 --- a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py +++ b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py @@ -74,3 +74,4 @@ def test_given_abandoned_scenario_workers_when_interpreter_finalizes_then_proces assert result.returncode == test_case.expected_returncode, result.stderr assert "finalizing True" in result.stderr + assert "Waiting up to 5s for running scenario statements to stop" in result.stderr