Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional deploy files argument #933

Merged
merged 82 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
ad43ea1
add files argument to deploy
sfc-gh-gbloom Mar 22, 2024
041b385
add integration test
sfc-gh-gbloom Mar 25, 2024
159752b
stage files relative to deploy root
sfc-gh-gbloom Mar 25, 2024
dc4c967
add unit tests
sfc-gh-gbloom Mar 26, 2024
b95962d
check for empty list
sfc-gh-gbloom Mar 26, 2024
6265837
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Mar 26, 2024
2149053
release notes update
sfc-gh-gbloom Mar 26, 2024
f8b8c5e
update command description
sfc-gh-gbloom Mar 26, 2024
7a07388
unit test grooming
sfc-gh-gbloom Mar 26, 2024
9fa9f44
pr comments
sfc-gh-gbloom Mar 26, 2024
1e29c4b
add types
sfc-gh-gbloom Mar 26, 2024
e2119c3
fix ignore only on stage files
sfc-gh-gbloom Mar 27, 2024
5616e01
update snapshot
sfc-gh-gbloom Mar 27, 2024
9c4a902
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Mar 27, 2024
3a1e46d
fix set type for python3.8
sfc-gh-gbloom Mar 27, 2024
29994b0
delete remote only files
sfc-gh-gbloom Mar 27, 2024
f7963d8
fix relative paths
sfc-gh-gbloom Mar 27, 2024
1e82b1c
add integration test of removing remote only file
sfc-gh-gbloom Mar 27, 2024
e9c593b
add prune flag
sfc-gh-gbloom Apr 3, 2024
4031f68
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 3, 2024
a003915
default prune
sfc-gh-gbloom Apr 4, 2024
dc3ec90
move paths filter logic to manager
sfc-gh-gbloom Apr 4, 2024
28f916d
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 4, 2024
9749655
fix remote md5 map
sfc-gh-gbloom Apr 4, 2024
c60b20c
add build_md5_map test
sfc-gh-gbloom Apr 5, 2024
421607e
add no prune test
sfc-gh-gbloom Apr 5, 2024
868314d
change to relative to project root
sfc-gh-gbloom Apr 9, 2024
4c03ee3
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 9, 2024
da14615
type fix
sfc-gh-gbloom Apr 9, 2024
6392691
add integration coverage
sfc-gh-gbloom Apr 9, 2024
e162cda
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 10, 2024
1f22260
pr nits
sfc-gh-gbloom Apr 10, 2024
f4d01f5
pass created build files to find source from deploy path
sfc-gh-gbloom Apr 16, 2024
39c0d36
add recursive flag
sfc-gh-gbloom Apr 17, 2024
b6b80d2
add unit tests
sfc-gh-gbloom Apr 17, 2024
38ddfd6
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 17, 2024
3ac8f27
integration tests
sfc-gh-gbloom Apr 18, 2024
89381dc
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 18, 2024
3da37b1
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 18, 2024
488cac8
update snapshot
sfc-gh-gbloom Apr 18, 2024
0acd51b
relpath fix
sfc-gh-gbloom Apr 18, 2024
9ba1201
relpath fix
sfc-gh-gbloom Apr 18, 2024
c961ccd
fix windows relative path
sfc-gh-gbloom Apr 19, 2024
3f94f4e
resolved paths
sfc-gh-gbloom Apr 22, 2024
fead52e
resolve deploy root
sfc-gh-gbloom Apr 22, 2024
1177c8b
resolve deploy root
sfc-gh-gbloom Apr 23, 2024
e79ae17
fix test regex
sfc-gh-gbloom Apr 23, 2024
60551ef
debug
sfc-gh-gbloom Apr 23, 2024
b69921b
resolve source path
sfc-gh-gbloom Apr 23, 2024
d1e38b2
debug
sfc-gh-gbloom Apr 23, 2024
26497cb
debug
sfc-gh-gbloom Apr 23, 2024
7fff826
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 23, 2024
aecc5b4
handle windows path in integration test
sfc-gh-gbloom Apr 24, 2024
7176727
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 24, 2024
6ffbcf3
release notes
sfc-gh-gbloom Apr 24, 2024
8d89da9
move default arguments
sfc-gh-gbloom Apr 24, 2024
fc58314
pr nits
sfc-gh-gbloom Apr 24, 2024
b41ae52
pr nits
sfc-gh-gbloom Apr 25, 2024
26d0407
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 25, 2024
2dcd2d7
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 26, 2024
4d179c5
no recursive check in full deploy
sfc-gh-gbloom Apr 26, 2024
ccea424
prune test
sfc-gh-gbloom Apr 26, 2024
7ca66fa
add file not found test
sfc-gh-gbloom Apr 26, 2024
f7bc13e
fix non recursive full deploy
sfc-gh-gbloom Apr 26, 2024
b57f7d2
use different names in src/dest
sfc-gh-gbloom Apr 26, 2024
4568237
rename mapped_files
sfc-gh-gbloom Apr 29, 2024
c7be70e
rename source_path
sfc-gh-gbloom Apr 29, 2024
fbd6b04
nit
sfc-gh-gbloom Apr 29, 2024
a282972
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 29, 2024
ef3dedd
better docs
sfc-gh-gbloom Apr 29, 2024
4e4bf85
update snapshot
sfc-gh-gbloom Apr 29, 2024
b8f0650
better docs
sfc-gh-gbloom Apr 29, 2024
e1cc96d
use ClickException
sfc-gh-gbloom Apr 30, 2024
e3550de
tests
sfc-gh-gbloom Apr 30, 2024
63db862
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom Apr 30, 2024
1ea1785
add snapshot
sfc-gh-gbloom Apr 30, 2024
ece7214
match click exception output
sfc-gh-gbloom Apr 30, 2024
4e39f76
remove text assertion
sfc-gh-gbloom Apr 30, 2024
be7d3a1
doc
sfc-gh-gbloom Apr 30, 2024
19ab8a2
snapshot
sfc-gh-gbloom Apr 30, 2024
5b55216
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom May 2, 2024
dd293e9
Merge branch 'main' into gbloom-SNOW-1238239-deploy-files-argument
sfc-gh-gbloom May 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

## New additions
* `snow sql` command supports now client-side templating of queries.
* Added the option to specify specific files and directories to sync when running `snow app deploy`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be helpful to include the name of the option


## Fixes and improvements

Expand Down
47 changes: 43 additions & 4 deletions src/snowflake/cli/plugins/nativeapp/artifacts.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import os
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union

from click import ClickException
from click.exceptions import ClickException
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
from snowflake.cli.api.secure_path import SecurePath
from yaml import safe_load

# Map from source directories and files in the project directory to their path in the deploy directory. Both paths are absolute.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do they need to be absolute? Intuitively, I think of source paths as relative to the project root, and dest paths as relative to the deployment directory. My guess is that without this invariant, you'd have to drag both roots around, is that it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with relative paths, but switched to absolute mostly to canonicalize windows paths (C:\Users\RUNNER~1\ -> C:\Users\runneradmin\).
It also simplifies the code, and doesn't assume that the deploy root is a subdirectory of the project root.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the deploy root could be anything, we should never assume that it's within the project directory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an ideal world, these would be relative to their respective roots, but it's documented well and if we need them we can always use relative_to.

ArtifactDeploymentMap = Dict[Path, Path]


class DeployRootError(ClickException):
"""
Expand Down Expand Up @@ -196,10 +199,11 @@ def resolve_without_follow(path: Path) -> Path:

def build_bundle(
project_root: Path, deploy_root: Path, artifacts: List[ArtifactMapping]
):
) -> ArtifactDeploymentMap:
"""
Prepares a local folder (deploy_root) with configured app artifacts.
This folder can then be uploaded to a stage.
Returns a mapping of the copied source files, pointing to where they were copied to.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied. (remove "to")

Also, is it a mapping or a map that gets returned? From the code, it looks like it returns a Map.

"""
resolved_root = deploy_root.resolve()
if resolved_root.exists() and not resolved_root.is_dir():
Expand All @@ -217,6 +221,7 @@ def build_bundle(
if resolved_root.exists():
delete(resolved_root)

created_files: ArtifactDeploymentMap = {}
for artifact in artifacts:
dest_path = resolve_without_follow(Path(resolved_root, artifact.dest))
source_paths = get_source_paths(artifact, project_root)
Expand All @@ -228,7 +233,9 @@ def build_bundle(

# copy all files as children of the given destination path
for source_path in source_paths:
symlink_or_copy(source_path, dest_path / source_path.name)
dest_child_path = dest_path / source_path.name
symlink_or_copy(source_path, dest_child_path)
created_files[source_path.resolve()] = dest_child_path
else:
# ensure we are copying into the deploy root, not replacing it!
if resolved_root not in dest_path.parents:
Expand All @@ -237,9 +244,11 @@ def build_bundle(
if len(source_paths) == 1:
# copy a single file as the given destination path
symlink_or_copy(source_paths[0], dest_path)
created_files[source_paths[0].resolve()] = dest_path
else:
# refuse to map multiple source files to one destination (undefined behaviour)
raise TooManyFilesError(dest_path)
return created_files
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I keep getting back to this name, and something feels off. I think it's because in my mind, bundle is something that is essentially acting on a diff. So it becomes unclear here if "created" means newly added files, or all files that are supposed to be mapped. It's the latter of course, reading the code, but the name should reflect that. How about something a lot more generic like "deploy_root_file_mapping" / "deploy_root_files" / "mapped_files" etc? Might want the team's input on that one, naming is hard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to mapped_files



def find_manifest_file(deploy_root: Path) -> Path:
Expand Down Expand Up @@ -283,3 +292,33 @@ def find_version_info_in_manifest_file(
patch_name = version_info[patch_field]

return version_name, patch_name


def project_path_to_deploy_path(
sfc-gh-bdufour marked this conversation as resolved.
Show resolved Hide resolved
project_path: Path, created_files: ArtifactDeploymentMap
) -> Path:
"""Given a source path, returns the deploy destination path. This function assumes that a bundle was created before calling it."""

project_path = project_path.resolve()

if project_path in created_files:
return created_files[project_path]

# Find the first parent directory that exists in created_files
common_root = Path(project_path).resolve()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would simply common_root = project_path do here?

while common_root:
if common_root in created_files:
break
elif common_root.parent != common_root:
common_root = common_root.parent
else:
raise FileNotFoundError(project_path)

# Construct the target deploy path
path_to_symlink = created_files[common_root]
path_to_target = Path(project_path).relative_to(common_root)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: relative_path_to_target ?

result = Path(path_to_symlink, path_to_target)

if not result.exists():
raise FileNotFoundError(result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why so opinionated here? This is a utility function to compute a mapping. It's odd to enforce that the computed path must exist, and makes the utility function generally less useful IMHO.

return result
34 changes: 31 additions & 3 deletions src/snowflake/cli/plugins/nativeapp/commands.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from typing import Optional
from pathlib import Path
from typing import List, Optional

import typer
from snowflake.cli.api.cli_global_context import cli_context
Expand Down Expand Up @@ -229,17 +230,44 @@ def app_teardown(
@app.command("deploy", requires_connection=True)
@with_project_definition("native_app")
def app_deploy(
prune: Optional[bool] = typer.Option(
default=None,
help=f"""Controls whether files that exist only remotely will be deleted from the stage.""",
sfc-gh-cgorrie marked this conversation as resolved.
Show resolved Hide resolved
),
recursive: Optional[bool] = typer.Option(
None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I would use False as default for boolean value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed it to be None here because there is the special flow of running snow app deploy with no arguments, in which case the default value is True

"--recursive",
"-r",
help=f"""Controls whether the specified directories should be deployed including all of their contents.""",
sfc-gh-cgorrie marked this conversation as resolved.
Show resolved Hide resolved
),
files: Optional[List[Path]] = typer.Argument(
default=None,
show_default=False,
help=f"""Paths of the files, relative to the project root, to be uploaded to a stage. [default: sync all local changes to the stage]""",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paths, relative to the the project root, of files you want to upload to a stage. If unspecified, the command syncs all local changes to the stage.

),
**options,
) -> CommandResult:
"""
Creates an application package in your Snowflake account and syncs the local changes to the stage without creating or updating the application.
Running this command with no arguments at all is a shorthand for "snow app deploy --prune --recursive".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... at all, as in snow app deploy, ...

I think we use `` for commands. The scripts then convert it to the proper RST code format.

"""
if files is None:
files = []
if prune is None and recursive is None and len(files) == 0:
prune = True
recursive = True
else:
if prune is None:
prune = False
if recursive is None:
recursive = False

manager = NativeAppManager(
project_definition=cli_context.project_definition,
project_root=cli_context.project_root,
)

manager.build_bundle()
manager.deploy()
created_files = manager.build_bundle()
manager.deploy(prune, recursive, files, created_files)

return MessageResult(f"Deployed successfully.")
71 changes: 65 additions & 6 deletions src/snowflake/cli/plugins/nativeapp/manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
from abc import ABC, abstractmethod
from functools import cached_property
from pathlib import Path
Expand All @@ -23,8 +24,11 @@
from snowflake.cli.api.sql_execution import SqlExecutionMixin
from snowflake.cli.plugins.connection.util import make_snowsight_url
from snowflake.cli.plugins.nativeapp.artifacts import (
ArtifactDeploymentMap,
ArtifactMapping,
build_bundle,
project_path_to_deploy_path,
resolve_without_follow,
translate_artifact,
)
from snowflake.cli.plugins.nativeapp.constants import (
Expand All @@ -45,6 +49,7 @@
)
from snowflake.cli.plugins.stage.diff import (
DiffResult,
filter_from_diff,
stage_diff,
sync_local_diff_with_stage,
)
Expand Down Expand Up @@ -100,6 +105,28 @@ def ensure_correct_owner(row: dict, role: str, obj_name: str) -> None:
raise UnexpectedOwnerError(obj_name, role, actual_owner)


def _get_files_to_sync(paths_to_sync: List[Path], deploy_root: Path) -> List[str]:
"""Takes a list of paths (files and directories), returning a list of all files recursively, stripping the path to deploy root."""
sfc-gh-cgorrie marked this conversation as resolved.
Show resolved Hide resolved
paths = []
for path in paths_to_sync:
if path.is_dir():
for current_dir, _dirs, files in os.walk(path):
for file in files:
deploy_path = Path(current_dir, file).relative_to(deploy_root)
paths.append(str(deploy_path))
Comment on lines +116 to +117
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why convert back to str here? We could leave them as relative paths

Copy link
Contributor Author

@sfc-gh-gbloom sfc-gh-gbloom Apr 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DiffResult stores the paths as strings, and I convert these to a set downstream for quicker lookups

else:
paths.append(str(path.relative_to(deploy_root)))
return paths


def _verify_no_directories(paths_to_sync):
for path in paths_to_sync:
if path.is_dir():
raise ValueError(
f"{path} is a directory. Add the -r flag to deploy directories."
)


class NativeAppCommandProcessor(ABC):
@abstractmethod
def process(self, *args, **kwargs):
Expand Down Expand Up @@ -278,13 +305,20 @@ def verify_project_distribution(
return False
return True

def build_bundle(self) -> None:
def build_bundle(self) -> ArtifactDeploymentMap:
"""
Populates the local deploy root from artifact sources.
"""
build_bundle(self.project_root, self.deploy_root, self.artifacts)

def sync_deploy_root_with_stage(self, role: str) -> DiffResult:
return build_bundle(self.project_root, self.deploy_root, self.artifacts)

def sync_deploy_root_with_stage(
self,
role: str,
prune: bool,
recursive: bool,
paths_to_sync: List[Path] = [], # relative to project root
created_files: Optional[ArtifactDeploymentMap] = None,
) -> DiffResult:
"""
Ensures that the files on our remote stage match the artifacts we have in
the local filesystem. Returns the DiffResult used to make changes.
Expand All @@ -310,6 +344,23 @@ def sync_deploy_root_with_stage(self, role: str) -> DiffResult:
% self.deploy_root
)
diff: DiffResult = stage_diff(self.deploy_root, self.stage_fqn)

# If we are syncing specific files/directories, remove everything else from the diff
if len(paths_to_sync) > 0:
paths_to_sync = [resolve_without_follow(p) for p in paths_to_sync]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Generally not a fan of re-assigning incoming parameters

if not recursive:
_verify_no_directories(paths_to_sync)
deploy_paths = [
project_path_to_deploy_path(p, created_files) for p in paths_to_sync
]
paths_to_keep = set(
_get_files_to_sync(deploy_paths, self.deploy_root.resolve())
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, subjective, but the naming confuses me here... We have deploy_paths, paths_to_sync, paths_to_keep and files_to_sync. Can you replace the names with more descriptive options? I'm losing track of what's what.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed everything to paths_to_sync

filter_from_diff(diff, paths_to_keep, prune)

if prune is False:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if not prune? Feels odd to check for False explicitly

diff.only_on_stage = []

cc.message(str(diff))

# Upload diff-ed files to application package stage
Expand Down Expand Up @@ -435,7 +486,13 @@ def _apply_package_scripts(self) -> None:
err, role=self.package_role, warehouse=self.package_warehouse
)

def deploy(self) -> DiffResult:
def deploy(
self,
prune: bool,
recursive: bool,
paths_to_sync: List[Path] = [],
created_files: Optional[ArtifactDeploymentMap] = None,
) -> DiffResult:
"""app deploy process"""

# 1. Create an empty application package, if none exists
Expand All @@ -446,6 +503,8 @@ def deploy(self) -> DiffResult:
self._apply_package_scripts()

# 3. Upload files from deploy root local folder to the above stage
diff = self.sync_deploy_root_with_stage(self.package_role)
diff = self.sync_deploy_root_with_stage(
self.package_role, prune, recursive, paths_to_sync, created_files
)

return diff
2 changes: 1 addition & 1 deletion src/snowflake/cli/plugins/nativeapp/run_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,5 @@ def process(
)
return

diff = self.deploy()
diff = self.deploy(prune=True, recursive=True)
self._create_dev_app(diff)
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ def process(
self._apply_package_scripts()

# Upload files from deploy root local folder to the above stage
self.sync_deploy_root_with_stage(self.package_role)
self.sync_deploy_root_with_stage(
self.package_role, prune=True, recursive=True
)

# Warn if the version exists in a release directive(s)
existing_release_directives = (
Expand Down
34 changes: 29 additions & 5 deletions src/snowflake/cli/plugins/stage/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Set

from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
from snowflake.cli.api.console import cli_console as cc
from snowflake.cli.api.exceptions import (
SnowflakeSQLExecutionError,
)
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
from snowflake.connector.cursor import SnowflakeCursor

Expand Down Expand Up @@ -149,12 +152,33 @@ def build_md5_map(list_stage_cursor: SnowflakeCursor) -> 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()
}


def stage_diff(local_path: Path, stage_fqn: str) -> DiffResult:
def filter_from_diff(
diff: DiffResult, paths_to_keep: Set[str], prune: bool
) -> DiffResult:
"""Modifies the given diff, keeping only the provided paths. If prune is false, remote-only paths will be empty."""
diff.different = [i for i in diff.different if i in paths_to_keep]
diff.only_local = [i for i in diff.only_local if i in paths_to_keep]
if prune:
diff.only_on_stage = [i for i in diff.only_on_stage if i in paths_to_keep]
else:
files_not_removed = [i for i in diff.only_on_stage if i in paths_to_keep]
if len(files_not_removed) > 0:
cc.warning(
f"The following files exist only on the stage:\n{files_not_removed}\nUse the --prune flag to delete them from the stage."
)
diff.only_on_stage = []
return diff


def stage_diff(
local_path: Path,
stage_fqn: str,
) -> DiffResult:
"""
Diffs the files in a stage with a local folder.
"""
Expand Down
Loading
Loading