Skip to content

Commit

Permalink
Add query execution against snowflake account
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jhilgart committed Apr 18, 2024
1 parent eec6f50 commit cb93781
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 16 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,19 @@ After you've edited your semantic model, you can validate this file before uploa
from semantic_model_generator.validate_model import validate

YAML_PATH="/path/to/your/model_yaml.yaml"
SNOWFLAKE_ACCOUNT="<your-snowflake-account>"

validate(yaml_path=YAML_PATH)
validate(
yaml_path=YAML_PATH,
snowflake_account=SNOWFLAKE_ACCOUNT
)

```

2. Using the command line. Ensure `poetry shell` is activated.

```bash
python -m semantic_model_generator.validate_model --yaml_path="/path/to/your/model_yaml.yaml"
python -m semantic_model_generator.validate_model --yaml_path="/path/to/your/model_yaml.yaml --snowflake_account="<your-account-name>"
```
## Release
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import tempfile
from unittest import mock
from unittest.mock import MagicMock, patch

import pytest

from semantic_model_generator.validate_model import validate


@pytest.fixture
def mock_snowflake_connection():
"""Fixture to mock the snowflake_connection function."""
with patch("semantic_model_generator.validate_model.SnowflakeConnector") as mock:
mock.return_value = MagicMock()
yield mock


_VALID_YAML = """name: my test semantic model
tables:
- name: ALIAS
Expand Down Expand Up @@ -113,20 +123,46 @@ def temp_invalid_yaml_formatting_file():


@mock.patch("semantic_model_generator.validate_model.logger")
def test_valid_yaml(mock_logger, temp_valid_yaml_file):
def test_valid_yaml(mock_logger, temp_valid_yaml_file, mock_snowflake_connection):
account_name = "snowflake test"

_ = validate(temp_valid_yaml_file)
validate(temp_valid_yaml_file, account_name)

expected_log_call = mock.call.info(f"Successfully validated {temp_valid_yaml_file}")
expected_log_call_1 = mock.call.info(
f"Successfully validated {temp_valid_yaml_file}"
)
expected_log_call_2 = mock.call.info("Checking logical table: ALIAS")
expected_log_call_3 = mock.call.info("Validated logical table: ALIAS")
assert (
expected_log_call in mock_logger.mock_calls
expected_log_call_1 in mock_logger.mock_calls
), "Expected log message not found in logger calls"
assert (
expected_log_call_2 in mock_logger.mock_calls
), "Expected log message not found in logger calls"
assert (
expected_log_call_3 in mock_logger.mock_calls
), "Expected log message not found in logger calls"
snowflake_query_one = (
"SELECT ALIAS, ZIP_CODE FROM AUTOSQL_DATASET_BIRD_V2.ADDRESS.ALIAS LIMIT 100"
)
snowflake_query_two = (
"SELECT ALIAS, ZIP_CODE FROM AUTOSQL_DATASET_BIRD_V2.ADDRESS.ALIAS LIMIT 100"
)
assert any(
snowflake_query_one in str(call)
for call in mock_snowflake_connection.mock_calls
), "Query not executed"
assert any(
snowflake_query_two in str(call)
for call in mock_snowflake_connection.mock_calls
), "Query not executed"


@mock.patch("semantic_model_generator.validate_model.logger")
def test_invalid_yaml_formatting(mock_logger, temp_invalid_yaml_formatting_file):
account_name = "snowflake test"
with pytest.raises(ValueError) as exc_info:
validate(temp_invalid_yaml_formatting_file)
validate(temp_invalid_yaml_formatting_file, account_name)

expected_error_fragment = (
"Failed to parse tables field: "
Expand Down
36 changes: 27 additions & 9 deletions semantic_model_generator/validate_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,47 @@
from loguru import logger

from semantic_model_generator.data_processing.proto_utils import yaml_to_semantic_model
from semantic_model_generator.snowflake_utils.snowflake_connector import (
SnowflakeConnector,
)
from semantic_model_generator.sqlgen.generate_sql import generate_select_with_all_cols


def validate(yaml_path: str) -> None:
def validate(yaml_path: str, snowflake_account: str) -> None:
"""
For now, validate just ensures that the yaml is correctly formatted and we can parse into our protos.
yaml_path: str The absolute path to the location of your yaml file. Something like path/to/your/file.yaml.
snowflake_account: str The name of the snowflake account.
TODO: ensure that all expressions are valid by running a query containing all columns and expressions.
"""
with open(yaml_path) as f:
yaml_str = f.read()
model = yaml_to_semantic_model(yaml_str)

connector = SnowflakeConnector(
account_name=snowflake_account,
ndv_per_column=10, # number of sample values to pull per column.
max_workers=1,
)

for table in model.tables:
logger.info(f"Checking logical table: {table.name}")
try:
_ = generate_select_with_all_cols(table, 100)
# TODO: next, run select and validate this works
except Exception as ex:
logger.warning(
f"Failed to generate a select query for logical table {table.name}. Error: {ex}"
)
logger.info(f"Validated logical table: {table.name}")
# Each table can be a different database/schema.
# Create new connection for each one.
with connector.connect(
db_name=table.base_table.database, schema_name=table.base_table.schema
) as conn:
try:
select = generate_select_with_all_cols(table, 100)
# Run the query
_ = conn.cursor().execute(select)
except Exception as e:
raise ValueError(
f"Unable to execute query with your logical table against physical tables on Snowflake. Query = {select}. Error = {e}"
)
logger.info(f"Validated logical table: {table.name}")

logger.info(f"Successfully validated {yaml_path}")

Expand Down

0 comments on commit cb93781

Please sign in to comment.