|
24 | 24 | load_config_from_env, |
25 | 25 | load_config_from_paths, |
26 | 26 | load_config_from_python_module, |
| 27 | + load_configs, |
27 | 28 | ) |
28 | 29 | from sqlmesh.core.context import Context |
29 | 30 | from sqlmesh.core.engine_adapter.athena import AthenaEngineAdapter |
@@ -1132,3 +1133,163 @@ def test_environment_suffix_target_catalog(tmp_path: Path) -> None: |
1132 | 1133 | Config, |
1133 | 1134 | project_paths=[config_path], |
1134 | 1135 | ) |
| 1136 | + |
| 1137 | + |
| 1138 | +def test_load_python_config_dot_env_vars(tmp_path_factory): |
| 1139 | + main_dir = tmp_path_factory.mktemp("python_config") |
| 1140 | + config_path = main_dir / "config.py" |
| 1141 | + with open(config_path, "w", encoding="utf-8") as fd: |
| 1142 | + fd.write( |
| 1143 | + """from sqlmesh.core.config import Config, DuckDBConnectionConfig, GatewayConfig, ModelDefaultsConfig |
| 1144 | +config = Config(gateways={"duckdb_gateway": GatewayConfig(connection=DuckDBConnectionConfig())}, model_defaults=ModelDefaultsConfig(dialect='')) |
| 1145 | + """ |
| 1146 | + ) |
| 1147 | + |
| 1148 | + # The environment variable value from the dot env file should be set |
| 1149 | + # SQLMESH__ variables override config fields directly if they follow the naming structure |
| 1150 | + dot_path = main_dir / ".env" |
| 1151 | + with open(dot_path, "w", encoding="utf-8") as fd: |
| 1152 | + fd.write( |
| 1153 | + """SQLMESH__GATEWAYS__DUCKDB_GATEWAY__STATE_CONNECTION__TYPE="bigquery" |
| 1154 | +SQLMESH__GATEWAYS__DUCKDB_GATEWAY__STATE_CONNECTION__CHECK_IMPORT="false" |
| 1155 | +SQLMESH__DEFAULT_GATEWAY="duckdb_gateway" |
| 1156 | + """ |
| 1157 | + ) |
| 1158 | + |
| 1159 | + # Use mock.patch.dict to isolate environment variables between the tests |
| 1160 | + with mock.patch.dict(os.environ, {}, clear=True): |
| 1161 | + configs = load_configs( |
| 1162 | + "config", |
| 1163 | + Config, |
| 1164 | + paths=[main_dir], |
| 1165 | + ) |
| 1166 | + |
| 1167 | + assert next(iter(configs.values())) == Config( |
| 1168 | + gateways={ |
| 1169 | + "duckdb_gateway": GatewayConfig( |
| 1170 | + connection=DuckDBConnectionConfig(), |
| 1171 | + state_connection=BigQueryConnectionConfig(check_import=False), |
| 1172 | + ), |
| 1173 | + }, |
| 1174 | + model_defaults=ModelDefaultsConfig(dialect=""), |
| 1175 | + default_gateway="duckdb_gateway", |
| 1176 | + ) |
| 1177 | + |
| 1178 | + |
| 1179 | +def test_load_yaml_config_dot_env_vars(tmp_path_factory): |
| 1180 | + main_dir = tmp_path_factory.mktemp("yaml_config") |
| 1181 | + config_path = main_dir / "config.yaml" |
| 1182 | + with open(config_path, "w", encoding="utf-8") as fd: |
| 1183 | + fd.write( |
| 1184 | + """gateways: |
| 1185 | + duckdb_gateway: |
| 1186 | + connection: |
| 1187 | + type: duckdb |
| 1188 | + catalogs: |
| 1189 | + local: local.db |
| 1190 | + cloud_sales: {{ env_var('S3_BUCKET') }} |
| 1191 | + extensions: |
| 1192 | + - name: httpfs |
| 1193 | + secrets: |
| 1194 | + - type: "s3" |
| 1195 | + key_id: {{ env_var('S3_KEY') }} |
| 1196 | + secret: {{ env_var('S3_SECRET') }} |
| 1197 | +model_defaults: |
| 1198 | + dialect: "" |
| 1199 | +""" |
| 1200 | + ) |
| 1201 | + |
| 1202 | + # This test checks both using SQLMESH__ prefixed environment variables with underscores |
| 1203 | + # and setting a regular environment variable for use with env_var(). |
| 1204 | + dot_path = main_dir / ".env" |
| 1205 | + with open(dot_path, "w", encoding="utf-8") as fd: |
| 1206 | + fd.write( |
| 1207 | + """S3_BUCKET="s3://metrics_bucket/sales.db" |
| 1208 | +S3_KEY="S3_KEY_ID" |
| 1209 | +S3_SECRET="XXX_S3_SECRET_XXX" |
| 1210 | +SQLMESH__DEFAULT_GATEWAY="duckdb_gateway" |
| 1211 | +SQLMESH__MODEL_DEFAULTS__DIALECT="athena" |
| 1212 | +""" |
| 1213 | + ) |
| 1214 | + |
| 1215 | + # Use mock.patch.dict to isolate environment variables between the tests |
| 1216 | + with mock.patch.dict(os.environ, {}, clear=True): |
| 1217 | + configs = load_configs( |
| 1218 | + "config", |
| 1219 | + Config, |
| 1220 | + paths=[main_dir], |
| 1221 | + ) |
| 1222 | + |
| 1223 | + assert next(iter(configs.values())) == Config( |
| 1224 | + gateways={ |
| 1225 | + "duckdb_gateway": GatewayConfig( |
| 1226 | + connection=DuckDBConnectionConfig( |
| 1227 | + catalogs={ |
| 1228 | + "local": "local.db", |
| 1229 | + "cloud_sales": "s3://metrics_bucket/sales.db", |
| 1230 | + }, |
| 1231 | + extensions=[{"name": "httpfs"}], |
| 1232 | + secrets=[{"type": "s3", "key_id": "S3_KEY_ID", "secret": "XXX_S3_SECRET_XXX"}], |
| 1233 | + ), |
| 1234 | + ), |
| 1235 | + }, |
| 1236 | + default_gateway="duckdb_gateway", |
| 1237 | + model_defaults=ModelDefaultsConfig(dialect="athena"), |
| 1238 | + ) |
| 1239 | + |
| 1240 | + |
| 1241 | +def test_load_yaml_config_custom_dotenv_path(tmp_path_factory): |
| 1242 | + main_dir = tmp_path_factory.mktemp("yaml_config_2") |
| 1243 | + config_path = main_dir / "config.yaml" |
| 1244 | + with open(config_path, "w", encoding="utf-8") as fd: |
| 1245 | + fd.write( |
| 1246 | + """gateways: |
| 1247 | + test_gateway: |
| 1248 | + connection: |
| 1249 | + type: duckdb |
| 1250 | + database: {{ env_var('DB_NAME') }} |
| 1251 | +""" |
| 1252 | + ) |
| 1253 | + |
| 1254 | + # Create a custom dot env file in a different location |
| 1255 | + custom_env_dir = tmp_path_factory.mktemp("custom_env") |
| 1256 | + custom_env_path = custom_env_dir / ".my_env" |
| 1257 | + with open(custom_env_path, "w", encoding="utf-8") as fd: |
| 1258 | + fd.write( |
| 1259 | + """DB_NAME="custom_database.db" |
| 1260 | +SQLMESH__DEFAULT_GATEWAY="test_gateway" |
| 1261 | +SQLMESH__MODEL_DEFAULTS__DIALECT="postgres" |
| 1262 | +""" |
| 1263 | + ) |
| 1264 | + |
| 1265 | + # Test that without custom dotenv path, env vars are not loaded |
| 1266 | + with mock.patch.dict(os.environ, {}, clear=True): |
| 1267 | + with pytest.raises( |
| 1268 | + ConfigError, match=r"Default model SQL dialect is a required configuratio*" |
| 1269 | + ): |
| 1270 | + load_configs( |
| 1271 | + "config", |
| 1272 | + Config, |
| 1273 | + paths=[main_dir], |
| 1274 | + ) |
| 1275 | + |
| 1276 | + # Test that with custom dotenv path, env vars are loaded correctly |
| 1277 | + with mock.patch.dict(os.environ, {}, clear=True): |
| 1278 | + configs = load_configs( |
| 1279 | + "config", |
| 1280 | + Config, |
| 1281 | + paths=[main_dir], |
| 1282 | + dotenv_path=custom_env_path, |
| 1283 | + ) |
| 1284 | + |
| 1285 | + assert next(iter(configs.values())) == Config( |
| 1286 | + gateways={ |
| 1287 | + "test_gateway": GatewayConfig( |
| 1288 | + connection=DuckDBConnectionConfig( |
| 1289 | + database="custom_database.db", |
| 1290 | + ), |
| 1291 | + ), |
| 1292 | + }, |
| 1293 | + default_gateway="test_gateway", |
| 1294 | + model_defaults=ModelDefaultsConfig(dialect="postgres"), |
| 1295 | + ) |
0 commit comments