Skip to content

Commit

Permalink
Fix the stage diff algorithm (#1031)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-cgorrie authored May 2, 2024
1 parent a1b1b03 commit d190f7e
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 8 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

## Fixes and improvements
* More human-friendly errors in case of corrupted `config.toml` file.
* Fixed a bug in `snow app` that caused files to be re-uploaded unnecessarily.

# v2.2.0

Expand Down
8 changes: 4 additions & 4 deletions src/snowflake/cli/plugins/stage/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
from snowflake.connector.cursor import SnowflakeCursor
from snowflake.connector.cursor import DictCursor

from .manager import StageManager

Expand Down Expand Up @@ -144,13 +144,13 @@ def strip_stage_name(path: str) -> str:
return "/".join(path.split("/")[1:])


def build_md5_map(list_stage_cursor: SnowflakeCursor) -> Dict[str, str]:
def build_md5_map(list_stage_cursor: DictCursor) -> Dict[str, str]:
"""
Returns a mapping of relative stage paths to their md5sums.
"""
return {
strip_stage_name(name): md5
for (name, size, md5, modified) in list_stage_cursor.fetchall()
strip_stage_name(file["name"]): file["md5"]
for file in list_stage_cursor.fetchall()
}


Expand Down
29 changes: 26 additions & 3 deletions tests/stage/test_diff.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import hashlib
from pathlib import Path
from typing import Dict, List, Tuple, Union
from typing import Dict, List, Union
from unittest import mock

import pytest
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
from snowflake.cli.plugins.stage.diff import (
DiffResult,
build_md5_map,
delete_only_on_stage_files,
enumerate_files,
get_stage_path_from_file,
Expand Down Expand Up @@ -40,13 +41,18 @@ def md5_of(contents: Union[str, bytes]) -> str:

def stage_contents(
files: Dict[str, Union[str, bytes]], last_modified: str = DEFAULT_LAST_MODIFIED
) -> List[Tuple[str, int, str, str]]:
) -> List[Dict[str, Union[str, int]]]:
"""
Return file contents as they would be listed by a SNOWFLAKE_SSE stage
if they were uploaded with the given structure and contents.
"""
return [
(f"stage/{relpath}", len(contents), md5_of(contents), last_modified)
{
"name": f"stage/{relpath}",
"size": len(contents),
"md5": md5_of(contents),
"last_modified": last_modified,
}
for (relpath, contents) in files.items()
]

Expand Down Expand Up @@ -199,6 +205,23 @@ def test_put_files_on_stage(mock_put, overwrite_param):
assert mock_put.mock_calls == expected


def test_build_md5_map(mock_cursor):
actual = build_md5_map(
mock_cursor(
rows=stage_contents(FILE_CONTENTS),
columns=STAGE_LS_COLUMNS,
)
)

expected = {
"README.md": "9b650974f65cc49be96a5ed34ac6d1fd",
"my.jar": "fc605d0e2e50cf3e71873d57f4c598b0",
"ui/streamlit.py": "a7dfdfaf892ecfc5f164914123c7f2cc",
}

assert actual == expected


@mock.patch(f"{STAGE_MANAGER}.remove")
def test_sync_local_diff_with_stage(mock_remove, other_directory):
temp_dir = Path(other_directory)
Expand Down
10 changes: 9 additions & 1 deletion tests_integration/test_nativeapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,15 @@ def test_nativeapp_init_deploy(
dict(name=app_name),
)

# make sure we always delete the app
# re-deploying should be a no-op; make sure we don't issue any PUT commands
result = runner.invoke_with_connection_json(
["app", "deploy", "--debug"],
env=TEST_ENV,
)
assert result.exit_code == 0
assert "Successfully uploaded chunk 0 of file" not in result.output

# make sure we always delete the package
result = runner.invoke_with_connection_json(
["app", "teardown"],
env=TEST_ENV,
Expand Down

0 comments on commit d190f7e

Please sign in to comment.