Skip to content

Commit

Permalink
fix bugs we could actually fix
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-cgorrie committed Dec 12, 2023
1 parent 81b0346 commit d95bb1d
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 41 deletions.
21 changes: 16 additions & 5 deletions src/snowcli/cli/appify/commands.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from typing import Optional

import json
import typer
from snowcli.cli.common.decorators import global_options_with_connection
from snowcli.cli.common.flags import DEFAULT_CONTEXT_SETTINGS
Expand All @@ -10,7 +11,6 @@

from snowcli.cli.appify.metadata import MetadataDumper
from snowcli.cli.appify.generate import (
load_catalog,
modifications,
generate_setup_statements,
rewrite_stage_imports,
Expand Down Expand Up @@ -52,20 +52,31 @@ def appify(
dumper = MetadataDumper(db, project.path)
dumper.execute()

catalog = load_catalog(dumper.catalog_path)
catalog = json.loads(dumper.catalog_path.read_text())
ordering = json.loads(dumper.ordering_path.read_text())
rewrite_stage_imports(catalog, dumper.referenced_stage_ids, dumper.metadata_path)

# generate the setup script
setup_statements = list(generate_setup_statements(catalog))
setup_statements = list(generate_setup_statements(catalog, ordering))
with open(project.path / "app" / "setup_script.sql", "w") as setup_sql:
setup_sql.write("\n".join(setup_statements))
setup_sql.write("\n")

# include referenced stages + metadata in our app stage
with modifications(project.path / "snowflake.yml") as snowflake_yml:
artifacts = snowflake_yml["native_app"]["artifacts"].data
artifacts.append(str(dumper.metadata_path))
artifacts.append(str(dumper.stages_path))
artifacts.append(
dict(
src=str(dumper.metadata_path.relative_to(project.path)),
dest="./metadata",
)
)
artifacts.append(
dict(
src=str(dumper.stages_path.relative_to(project.path)),
dest="./stages",
)
)
snowflake_yml["native_app"]["artifacts"] = as_document(artifacts)

return MessageResult(f"Created Native Application project from {db}.")
51 changes: 16 additions & 35 deletions src/snowcli/cli/appify/generate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Generator, List, Tuple

import re
import json
from textwrap import dedent
from contextlib import contextmanager
from pathlib import Path
Expand Down Expand Up @@ -48,24 +47,6 @@ def modifications(path: Path) -> Generator[YAML, None, None]:
f.write(yml.as_yaml())


def get_ordering(catalog: dict) -> List[Tuple[str, str]]:
"""
Return a list of (schema, object name) tuples that represent a
depth-first search of the DAG that represents their dependencies.
Object names must include arguments for callable types.
"""
return []


def load_catalog(catalog_json: Path) -> dict:
"""
Returns the metadata catalog for the database, containing reference
and kind information of the objects we dumped metadata for.
"""
with open(catalog_json, "r") as f:
return json.load(f)


def rewrite_stage_imports(
catalog: dict, stage_ids: List[str], metadata_path: Path
) -> None:
Expand All @@ -75,73 +56,73 @@ def rewrite_stage_imports(
there are missing features in NA (e.g. query_warehouse) and bugs in its get_ddl impl.
"""

def _rewrite_imports(s: str) -> str:
def _rewrite_imports(s: str, suffix: str = "") -> str:
# FIXME: likely quoting is wrong here.
for stage_id in stage_ids:
(stage_db, stage_schema, stage_name) = split_fqn_id(stage_id)
needle = f"@{stage_id}/"
replacement = f"/stages/{stage_db}/{stage_schema}/{stage_name}/"
needle = f"@{stage_id}{suffix}"
replacement = f"/stages/{stage_db}/{stage_schema}/{stage_name}{suffix}"
s = s.replace(needle, replacement)
return s

for id, object in catalog.items():
if object["kind"] in CALLABLE_KINDS:
(_db, schema, object_name) = split_fqn_id(id)
sql_path = metadata_path / schema / f"{object_name}.sql"
ddl_statement = _rewrite_imports(sql_path.read_text())
ddl_statement = _rewrite_imports(sql_path.read_text(), "/")
sql_path.write_text(ddl_statement)

elif object["kind"] == "streamlit":
(_db, schema, object_name) = split_fqn_id(id)
sql_path = metadata_path / schema / f"{object_name}.sql"
ddl_statement = sql_path.read_text()

if match := STREAMLIT_NAME.match(ddl_statement):
if match := STREAMLIT_NAME.search(ddl_statement):
name = match.group(1)
else:
raise MalformedStreamlitError("name", sql_path)

if match := STREAMLIT_MAIN_FILE.match(ddl_statement):
main_file = match.group(1)
else:
raise MalformedStreamlitError("main_file", sql_path)

if match := STREAMLIT_ROOT_LOCATION.match(ddl_statement):
if match := STREAMLIT_ROOT_LOCATION.search(ddl_statement):
root_location = match.group(1)
else:
raise MalformedStreamlitError("root_location", sql_path)

if match := STREAMLIT_MAIN_FILE.search(ddl_statement):
main_file = match.group(1)
else:
raise MalformedStreamlitError("main_file", sql_path)

from_clause = _rewrite_imports(root_location)
sql_path.write_text(
dedent(
f"""
create or replace streamlit {name}
create or replace streamlit {schema}.{name}
FROM '{from_clause}'
MAIN_FILE='{main_file};
MAIN_FILE='{main_file}';
"""
)
)


def generate_setup_statements(
catalog: dict,
ordering: List[str],
) -> Generator[str, None, None]:
"""
Generator that yields all the statements necessary to build the setup script.
"""
yield f"create application role if not exists {APP_PUBLIC};"

all_object_ids = list(catalog.keys())
schemas = list(set([split_fqn_id(x)[0] for x in all_object_ids]))
schemas = list(set([split_fqn_id(x)[1] for x in all_object_ids]))

for schema in schemas:
yield f"create or alter versioned schema {to_identifier(schema)};"
yield f"grant usage on schema {to_identifier(schema)} to application role {APP_PUBLIC};"

for fqn in get_ordering(catalog):
for fqn in ordering:
(_db, schema, object_name) = split_fqn_id(fqn)
kind = catalog[fqn]["kind"]
yield f"use schema {to_identifier(schema)};"
# XXX: is this correct quoting?
yield f"execute immediate from './metadata/{schema}/{object_name}.sql';"
if kind in GRANT_BY_KIND:
Expand Down
1 change: 1 addition & 0 deletions src/snowcli/cli/appify/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def update_references(
domain = reference[1]
if domain.upper() in ["FUNCTION"]:
cleaned_up_name = re.sub(r"^(.*\))(.*)$", r"\1", name) + '"'
cleaned_up_name = cleaned_up_name.replace(r'"', "")
cleaned_up_ref_list.append([cleaned_up_name, domain])
clean_ref_names.append(cleaned_up_name)
else:
Expand Down
2 changes: 1 addition & 1 deletion src/snowcli/cli/appify/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from snowflake.connector.cursor import DictCursor
from snowcli.cli.project.util import DB_SCHEMA_AND_NAME, IDENTIFIER

DB_SCHEMA_NAME_ARGS = f"{DB_SCHEMA_AND_NAME}([(].+[)])?"
DB_SCHEMA_NAME_ARGS = f"{DB_SCHEMA_AND_NAME}([(].*[)])?"
STAGE_IMPORT_REGEX = f"@({DB_SCHEMA_AND_NAME})/"


Expand Down

0 comments on commit d95bb1d

Please sign in to comment.