diff --git a/src/snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py b/src/snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py index 9e38eecc2c..a16e1dc933 100644 --- a/src/snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +++ b/src/snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py @@ -58,38 +58,45 @@ def expand_templates_in_file( if src.is_dir(): return - with self.edit_file(dest) as file: - if not has_client_side_templates(file.contents) and not ( - _is_sql_file(dest) and has_sql_templates(file.contents) - ): - return - - src_file_name = src.relative_to(self._bundle_ctx.project_root) - cc.step(f"Expanding templates in {src_file_name}") - with cc.indented(): - try: - jinja_env = ( - choose_sql_jinja_env_based_on_template_syntax( - file.contents, reference_name=src_file_name + src_file_name = src.relative_to(self._bundle_ctx.project_root) + + try: + with self.edit_file(dest) as file: + if not has_client_side_templates(file.contents) and not ( + _is_sql_file(dest) and has_sql_templates(file.contents) + ): + return + cc.step(f"Expanding templates in {src_file_name}") + with cc.indented(): + try: + jinja_env = ( + choose_sql_jinja_env_based_on_template_syntax( + file.contents, reference_name=src_file_name + ) + if _is_sql_file(dest) + else get_client_side_jinja_env() ) - if _is_sql_file(dest) - else get_client_side_jinja_env() - ) - expanded_template = jinja_env.from_string(file.contents).render( - template_context or get_cli_context().template_context - ) - - # For now, we are printing the source file path in the error message - # instead of the destination file path to make it easier for the user - # to identify the file that has the error, and edit the correct file. - except jinja2.TemplateSyntaxError as e: - raise InvalidTemplateInFileError(src_file_name, e, e.lineno) from e - - except jinja2.UndefinedError as e: - raise InvalidTemplateInFileError(src_file_name, e) from e - - if expanded_template != file.contents: - file.edited_contents = expanded_template + expanded_template = jinja_env.from_string(file.contents).render( + template_context or get_cli_context().template_context + ) + + # For now, we are printing the source file path in the error message + # instead of the destination file path to make it easier for the user + # to identify the file that has the error, and edit the correct file. + except jinja2.TemplateSyntaxError as e: + raise InvalidTemplateInFileError( + src_file_name, e, e.lineno + ) from e + + except jinja2.UndefinedError as e: + raise InvalidTemplateInFileError(src_file_name, e) from e + + if expanded_template != file.contents: + file.edited_contents = expanded_template + except UnicodeDecodeError as err: + cc.warning( + f"Could not read file {src_file_name}, error: {err.reason}. Skipping this file." + ) @span("templates_processor") def process( diff --git a/tests/nativeapp/codegen/templating/test_templates_processor.py b/tests/nativeapp/codegen/templating/test_templates_processor.py index 21c7160d06..65eb5b3dac 100644 --- a/tests/nativeapp/codegen/templating/test_templates_processor.py +++ b/tests/nativeapp/codegen/templating/test_templates_processor.py @@ -28,7 +28,10 @@ from snowflake.cli.api.exceptions import InvalidTemplate from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping -from tests.nativeapp.utils import CLI_GLOBAL_TEMPLATE_CONTEXT +from tests.nativeapp.utils import ( + CLI_GLOBAL_TEMPLATE_CONTEXT, + TEMPLATE_PROCESSOR, +) @dataclass @@ -213,3 +216,24 @@ def test_file_with_undefined_variable(): assert "does not contain a valid template" in str(e.value) assert bundle_result.output_files[0].is_symlink() assert bundle_result.output_files[0].read_text() == file_contents[0] + + +@mock.patch(CLI_GLOBAL_TEMPLATE_CONTEXT, {}) +@mock.patch(f"{TEMPLATE_PROCESSOR}.cc.warning") +def test_expand_templates_in_file_unicode_decode_error(mock_cc_warning): + file_name = ["test_file.txt"] + file_contents = ["This is a test file"] + with TemporaryDirectory() as tmp_dir: + bundle_result = bundle_files(tmp_dir, file_name, file_contents) + templates_processor = TemplatesProcessor(bundle_ctx=bundle_result.bundle_ctx) + with mock.patch( + f"{TEMPLATE_PROCESSOR}.TemplatesProcessor.edit_file", + side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "invalid start byte"), + ): + src_path = Path( + bundle_result.bundle_ctx.project_root / "src" / file_name[0] + ).relative_to(bundle_result.bundle_ctx.project_root) + templates_processor.process(bundle_result.artifact_to_process, None) + mock_cc_warning.assert_called_once_with( + f"Could not read file {src_path}, error: invalid start byte. Skipping this file." + ) diff --git a/tests/nativeapp/utils.py b/tests/nativeapp/utils.py index 4a614bbbda..2eb4c852d1 100644 --- a/tests/nativeapp/utils.py +++ b/tests/nativeapp/utils.py @@ -62,6 +62,10 @@ f"{APP_PACKAGE_ENTITY}.verify_project_distribution" ) +CODE_GEN = "snowflake.cli._plugins.nativeapp.codegen" +TEMPLATE_PROCESSOR = f"{CODE_GEN}.templates.templates_processor" +ARTIFACT_PROCESSOR = f"{CODE_GEN}.artifact_processor" + SQL_EXECUTOR_EXECUTE = f"{API_MODULE}.sql_execution.BaseSqlExecutor.execute_query" SQL_EXECUTOR_EXECUTE_QUERIES = ( f"{API_MODULE}.sql_execution.BaseSqlExecutor.execute_queries"