sqlmesh.core.schema_loader
1from __future__ import annotations 2 3import typing as t 4from concurrent.futures import ThreadPoolExecutor 5from pathlib import Path 6 7from sqlglot import exp 8from sqlglot.dialects.dialect import DialectType 9 10from sqlmesh.core.console import get_console 11from sqlmesh.core.engine_adapter import EngineAdapter 12from sqlmesh.core.model.definition import Model 13from sqlmesh.core.state_sync import StateReader 14from sqlmesh.utils import UniqueKeyDict, yaml 15from sqlmesh.utils.errors import SQLMeshError 16 17 18def create_external_models_file( 19 path: Path, 20 models: UniqueKeyDict[str, Model], 21 adapter: EngineAdapter, 22 state_reader: StateReader, 23 dialect: DialectType, 24 gateway: t.Optional[str] = None, 25 max_workers: int = 1, 26 strict: bool = False, 27 all_models: t.Optional[t.Dict[str, Model]] = None, 28) -> None: 29 """Create or replace a YAML file with column and types of all columns in all external models. 30 31 Args: 32 path: The path to store the YAML file. 33 models: FQN to model for the current repo/config being processed. 34 adapter: The engine adapter. 35 state_reader: The state reader. 36 dialect: The dialect to serialize the schema as. 37 gateway: If the model should be associated with a specific gateway; the gateway key 38 max_workers: The max concurrent workers to fetch columns. 39 strict: If True, raise an error if the external model is missing in the database. 40 all_models: FQN to model across all loaded repos. When provided, a dependency is only 41 classified as external if it is absent from this full set. This prevents cross-repo 42 internal models from being misclassified as external in multi-repo setups. 43 """ 44 known_models: t.Dict[str, Model] = all_models if all_models is not None else models 45 external_model_fqns = set() 46 47 for fqn, model in models.items(): 48 if model.kind.is_external: 49 external_model_fqns.add(fqn) 50 for dep in model.depends_on: 51 if dep not in known_models: 52 external_model_fqns.add(dep) 53 54 # Make sure we don't convert internal models into external ones. 55 existing_model_fqns = state_reader.nodes_exist(external_model_fqns, exclude_external=True) 56 if existing_model_fqns: 57 existing_model_fqns_str = ", ".join(existing_model_fqns) 58 get_console().log_warning( 59 f"The following models already exist and can't be converted to external: {existing_model_fqns_str}. " 60 "Perhaps these models have been removed, while downstream models that reference them weren't updated accordingly." 61 ) 62 external_model_fqns -= existing_model_fqns 63 64 with ThreadPoolExecutor(max_workers=max_workers) as pool: 65 gateway_part = {"gateway": gateway} if gateway else {} 66 67 schemas = [ 68 { 69 "name": exp.to_table(table).sql(dialect=dialect), 70 "columns": columns, 71 **gateway_part, 72 } 73 for table, columns in sorted( 74 pool.map( 75 lambda table: (table, get_columns(adapter, dialect, table, strict)), 76 external_model_fqns, 77 ) 78 ) 79 if columns 80 ] 81 82 # dont clobber existing entries from other gateways 83 entries_to_keep = ( 84 [e for e in yaml.load(path) if e.get("gateway", None) != gateway] 85 if path.exists() 86 else [] 87 ) 88 89 with open(path, "w", encoding="utf-8") as file: 90 yaml.dump(entries_to_keep + schemas, file) 91 92 93def get_columns( 94 adapter: EngineAdapter, dialect: DialectType, table: str, strict: bool 95) -> t.Optional[t.Dict[str, t.Any]]: 96 """ 97 Return the column and their types in a dictionary 98 """ 99 try: 100 columns = adapter.columns(table, include_pseudo_columns=True) 101 return {c: dtype.sql(dialect=dialect) for c, dtype in columns.items()} 102 except Exception as e: 103 msg = f"Unable to get schema for '{table}': '{e}'." 104 if strict: 105 raise SQLMeshError(msg) from e 106 get_console().log_warning(msg) 107 return None
def
create_external_models_file( path: pathlib.Path, models: sqlmesh.utils.UniqueKeyDict[str, typing.Union[sqlmesh.core.model.definition.SqlModel, sqlmesh.core.model.definition.SeedModel, sqlmesh.core.model.definition.PythonModel, sqlmesh.core.model.definition.ExternalModel]], adapter: sqlmesh.core.engine_adapter.base.EngineAdapter, state_reader: sqlmesh.core.state_sync.base.StateReader, dialect: Union[str, sqlglot.dialects.dialect.Dialect, type[sqlglot.dialects.dialect.Dialect], NoneType], gateway: Optional[str] = None, max_workers: int = 1, strict: bool = False, all_models: Optional[Dict[str, Union[sqlmesh.core.model.definition.SqlModel, sqlmesh.core.model.definition.SeedModel, sqlmesh.core.model.definition.PythonModel, sqlmesh.core.model.definition.ExternalModel]]] = None) -> None:
19def create_external_models_file( 20 path: Path, 21 models: UniqueKeyDict[str, Model], 22 adapter: EngineAdapter, 23 state_reader: StateReader, 24 dialect: DialectType, 25 gateway: t.Optional[str] = None, 26 max_workers: int = 1, 27 strict: bool = False, 28 all_models: t.Optional[t.Dict[str, Model]] = None, 29) -> None: 30 """Create or replace a YAML file with column and types of all columns in all external models. 31 32 Args: 33 path: The path to store the YAML file. 34 models: FQN to model for the current repo/config being processed. 35 adapter: The engine adapter. 36 state_reader: The state reader. 37 dialect: The dialect to serialize the schema as. 38 gateway: If the model should be associated with a specific gateway; the gateway key 39 max_workers: The max concurrent workers to fetch columns. 40 strict: If True, raise an error if the external model is missing in the database. 41 all_models: FQN to model across all loaded repos. When provided, a dependency is only 42 classified as external if it is absent from this full set. This prevents cross-repo 43 internal models from being misclassified as external in multi-repo setups. 44 """ 45 known_models: t.Dict[str, Model] = all_models if all_models is not None else models 46 external_model_fqns = set() 47 48 for fqn, model in models.items(): 49 if model.kind.is_external: 50 external_model_fqns.add(fqn) 51 for dep in model.depends_on: 52 if dep not in known_models: 53 external_model_fqns.add(dep) 54 55 # Make sure we don't convert internal models into external ones. 56 existing_model_fqns = state_reader.nodes_exist(external_model_fqns, exclude_external=True) 57 if existing_model_fqns: 58 existing_model_fqns_str = ", ".join(existing_model_fqns) 59 get_console().log_warning( 60 f"The following models already exist and can't be converted to external: {existing_model_fqns_str}. " 61 "Perhaps these models have been removed, while downstream models that reference them weren't updated accordingly." 62 ) 63 external_model_fqns -= existing_model_fqns 64 65 with ThreadPoolExecutor(max_workers=max_workers) as pool: 66 gateway_part = {"gateway": gateway} if gateway else {} 67 68 schemas = [ 69 { 70 "name": exp.to_table(table).sql(dialect=dialect), 71 "columns": columns, 72 **gateway_part, 73 } 74 for table, columns in sorted( 75 pool.map( 76 lambda table: (table, get_columns(adapter, dialect, table, strict)), 77 external_model_fqns, 78 ) 79 ) 80 if columns 81 ] 82 83 # dont clobber existing entries from other gateways 84 entries_to_keep = ( 85 [e for e in yaml.load(path) if e.get("gateway", None) != gateway] 86 if path.exists() 87 else [] 88 ) 89 90 with open(path, "w", encoding="utf-8") as file: 91 yaml.dump(entries_to_keep + schemas, file)
Create or replace a YAML file with column and types of all columns in all external models.
Arguments:
- path: The path to store the YAML file.
- models: FQN to model for the current repo/config being processed.
- adapter: The engine adapter.
- state_reader: The state reader.
- dialect: The dialect to serialize the schema as.
- gateway: If the model should be associated with a specific gateway; the gateway key
- max_workers: The max concurrent workers to fetch columns.
- strict: If True, raise an error if the external model is missing in the database.
- all_models: FQN to model across all loaded repos. When provided, a dependency is only classified as external if it is absent from this full set. This prevents cross-repo internal models from being misclassified as external in multi-repo setups.
def
get_columns( adapter: sqlmesh.core.engine_adapter.base.EngineAdapter, dialect: Union[str, sqlglot.dialects.dialect.Dialect, type[sqlglot.dialects.dialect.Dialect], NoneType], table: str, strict: bool) -> Optional[Dict[str, Any]]:
94def get_columns( 95 adapter: EngineAdapter, dialect: DialectType, table: str, strict: bool 96) -> t.Optional[t.Dict[str, t.Any]]: 97 """ 98 Return the column and their types in a dictionary 99 """ 100 try: 101 columns = adapter.columns(table, include_pseudo_columns=True) 102 return {c: dtype.sql(dialect=dialect) for c, dtype in columns.items()} 103 except Exception as e: 104 msg = f"Unable to get schema for '{table}': '{e}'." 105 if strict: 106 raise SQLMeshError(msg) from e 107 get_console().log_warning(msg) 108 return None
Return the column and their types in a dictionary