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

Moved Snowpark and Streamlit artifacts to separate directory in outpu… #1909

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions src/snowflake/cli/_plugins/snowpark/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,19 +220,19 @@ def build_artifacts_mappings(
) -> Tuple[EntityToImportPathsMapping, StageToArtefactMapping]:
stages_to_artifact_map: StageToArtefactMapping = defaultdict(set)
entities_to_imports_map: EntityToImportPathsMapping = defaultdict(set)
for entity_id, entity in snowpark_entities.items():
for name, entity in snowpark_entities.items():
stage = entity.stage
required_artifacts = set()
for artefact in entity.artifacts:
artefact_dto = project_paths.get_artefact_dto(artefact)
required_artifacts.add(artefact_dto)
entities_to_imports_map[entity_id].add(artefact_dto.import_path(stage))
entities_to_imports_map[name].add(artefact_dto.import_path(stage))
stages_to_artifact_map[stage].update(required_artifacts)

deps_artefact = project_paths.get_dependencies_artefact()
if deps_artefact.post_build_path.exists():
stages_to_artifact_map[stage].add(deps_artefact)
entities_to_imports_map[entity_id].add(deps_artefact.import_path(stage))
entities_to_imports_map[name].add(deps_artefact.import_path(stage))
return entities_to_imports_map, stages_to_artifact_map


Expand Down Expand Up @@ -330,7 +330,7 @@ def build(
anaconda_packages_manager = AnacondaPackagesManager()

# Clean up deploy root
project_paths.remove_up_deploy_root()
project_paths.remove_up_bundle_root()

# Resolve dependencies
if project_paths.requirements.exists():
Expand Down Expand Up @@ -389,7 +389,7 @@ def build(
for artefact in artifacts:
bundle_map = BundleMap(
project_root=artefact.project_root,
deploy_root=project_paths.deploy_root,
deploy_root=project_paths.bundle_root,
)
bundle_map.add(PathMapping(src=str(artefact.path), dest=artefact.dest))

Expand Down
31 changes: 21 additions & 10 deletions src/snowflake/cli/_plugins/snowpark/snowpark_project_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from snowflake.cli.api.constants import DEPLOYMENT_STAGE
from snowflake.cli.api.feature_flags import FeatureFlag
from snowflake.cli.api.identifiers import FQN
from snowflake.cli.api.project.project_paths import ProjectPaths
from snowflake.cli.api.project.project_paths import ProjectPaths, bundle_root
from snowflake.cli.api.project.schemas.entities.common import PathMapping
from snowflake.cli.api.secure_path import SecurePath

Expand All @@ -45,6 +45,7 @@ def get_artefact_dto(self, artifact_path: PathMapping) -> Artefact:
if FeatureFlag.ENABLE_SNOWPARK_GLOB_SUPPORT.is_enabled():
return Artefact(
project_root=self.project_root,
bundle_root=self.bundle_root,
dest=artifact_path.dest,
path=Path(artifact_path.src),
)
Expand All @@ -57,7 +58,10 @@ def get_artefact_dto(self, artifact_path: PathMapping) -> Artefact:
def get_dependencies_artefact(self) -> Artefact:
if FeatureFlag.ENABLE_SNOWPARK_GLOB_SUPPORT.is_enabled():
return Artefact(
project_root=self.project_root, dest=None, path=Path("dependencies.zip")
project_root=self.project_root,
bundle_root=self.bundle_root,
dest=None,
path=Path("dependencies.zip"),
)
else:
return ArtefactOldBuild(
Expand All @@ -74,19 +78,29 @@ def snowflake_requirements(self) -> SecurePath:
def requirements(self) -> SecurePath:
return SecurePath(self.path_relative_to_root(Path("requirements.txt")))

@property
def bundle_root(self) -> Path:
return bundle_root(self.project_root, "snowpark")


@dataclass(unsafe_hash=True)
class Artefact:
"""Helper for getting paths related to given artefact."""

project_root: Path
bundle_root: Path
path: Path
dest: str | None = None

def __init__(
self, project_root: Path, path: Path, dest: Optional[str] = None
self,
project_root: Path,
bundle_root: Path,
path: Path,
dest: Optional[str] = None,
) -> None:
self.project_root = project_root
self.bundle_root = bundle_root
self.path = path
self.dest = dest
if self.dest and not self._is_dest_a_file() and not self.dest.endswith("/"):
Expand Down Expand Up @@ -117,15 +131,15 @@ def post_build_path(self) -> Path:
"""
Returns post-build artefact path. Directories are mapped to corresponding .zip files.
"""
deploy_root = self.deploy_root()
bundle_root = self.bundle_root
path = (
self._path_until_asterisk()
if glob.has_magic(str(self.path))
else self.path.parent
)
if self._is_dest_a_file():
return deploy_root / self.dest # type: ignore
return deploy_root / (self.dest or path) / self._artefact_name
return bundle_root / self.dest # type: ignore
return bundle_root / (self.dest or path) / self._artefact_name

def upload_path(self, stage: FQN | str | None) -> str:
"""
Expand Down Expand Up @@ -153,9 +167,6 @@ def import_path(self, stage: FQN | str | None) -> str:
"""Path for UDF/sproc imports clause."""
return self.upload_path(stage) + self._artefact_name

def deploy_root(self) -> Path:
return self.project_root / "output"

def _is_dest_a_file(self) -> bool:
if not self.dest:
return False
Expand Down Expand Up @@ -183,7 +194,7 @@ class ArtefactOldBuild(Artefact):
dest: str | None = None

def __init__(self, path: Path, dest: Optional[str] = None) -> None:
super().__init__(project_root=Path(), path=path, dest=dest)
super().__init__(project_root=Path(), bundle_root=Path(), path=path, dest=dest)

@property
def _artefact_name(self) -> str:
Expand Down
10 changes: 6 additions & 4 deletions src/snowflake/cli/_plugins/streamlit/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,31 @@ def _put_streamlit_files(
if not artifacts:
return
stage_manager = StageManager()
# We treat the bundle root as deploy root
bundle_map = BundleMap(
project_root=streamlit_project_paths.project_root,
deploy_root=streamlit_project_paths.deploy_root,
deploy_root=streamlit_project_paths.bundle_root,
)
for artifact in artifacts:
bundle_map.add(PathMapping(src=str(artifact.src), dest=artifact.dest))

# Clean up deploy root
streamlit_project_paths.remove_up_deploy_root()
streamlit_project_paths.remove_up_bundle_root()

for (absolute_src, absolute_dest) in bundle_map.all_mappings(
absolute=True, expand_directories=True
):
if absolute_src.is_file():
# We treat the bundle root as deploy root
symlink_or_copy(
absolute_src,
absolute_dest,
deploy_root=streamlit_project_paths.deploy_root,
deploy_root=streamlit_project_paths.bundle_root,
)
# Temporary solution, will be replaced with diff
stage_path = (
PurePosixPath(absolute_dest)
.relative_to(streamlit_project_paths.deploy_root)
.relative_to(streamlit_project_paths.bundle_root)
.parent
)
full_stage_path = f"{stage_root}/{stage_path}".rstrip("/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

from snowflake.cli.api.project.project_paths import ProjectPaths
from snowflake.cli.api.project.project_paths import ProjectPaths, bundle_root


@dataclass
class StreamlitProjectPaths(ProjectPaths):
"""
This class allows you to manage files paths related to given project.
"""

@property
def bundle_root(self) -> Path:
return bundle_root(self.project_root, "streamlit")
16 changes: 11 additions & 5 deletions src/snowflake/cli/api/project/project_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ class ProjectPaths:
project_root: Path

@property
def deploy_root(self) -> Path:
return self.project_root / "output"
def bundle_root(self) -> Path:
return bundle_root(self.project_root)

def remove_up_deploy_root(self) -> None:
if self.deploy_root.exists():
rmtree(self.deploy_root)
def remove_up_bundle_root(self) -> None:
if self.bundle_root.exists():
rmtree(self.bundle_root)


def bundle_root(root: Path, app_type: str | None = None) -> Path:
if app_type:
return root / "output" / "bundle" / app_type
return root / "output" / "bundle"
42 changes: 22 additions & 20 deletions tests/snowpark/test_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,44 @@
lambda _: True,
)

bundle_root = Path("output") / "bundle" / "snowpark"


@pytest.mark.parametrize(
"artifacts, local_path, stage_path",
[
("src", Path("output") / "src.zip", "/"),
("src/", Path("output") / "src.zip", "/"),
("src/*", Path("output") / "src.zip", "/"),
("src/*.py", Path("output") / "src.zip", "/"),
("src", bundle_root / "src.zip", "/"),
("src/", bundle_root / "src.zip", "/"),
("src/*", bundle_root / "src.zip", "/"),
("src/*.py", bundle_root / "src.zip", "/"),
(
"src/dir/dir_app.py",
Path("output") / "src" / "dir" / "dir_app.py",
bundle_root / "src" / "dir" / "dir_app.py",
"/src/dir/",
),
(
{"src": "src/**/*", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src/", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src/*", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src/dir/dir_app.py", "dest": "source/dir/apps/"},
"output/source/dir/apps/dir_app.py",
bundle_root / "source" / "dir" / "apps" / "dir_app.py",
"/source/dir/apps/",
),
],
Expand Down Expand Up @@ -111,38 +113,38 @@ def test_build_and_deploy_with_artifacts(
@pytest.mark.parametrize(
"artifact, local_path, stage_path",
[
("src", Path("output") / "src.zip", "/"),
("src/", Path("output") / "src.zip", "/"),
("src/*", Path("output") / "src.zip", "/"),
("src/*.py", Path("output") / "src.zip", "/"),
("src", bundle_root / "src.zip", "/"),
("src/", bundle_root / "src.zip", "/"),
("src/*", bundle_root / "src.zip", "/"),
("src/*.py", bundle_root / "src.zip", "/"),
(
"src/dir/dir_app.py",
Path("output") / "src" / "dir" / "dir_app.py",
bundle_root / "src" / "dir" / "dir_app.py",
"/src/dir/",
),
(
{"src": "src/**/*", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src/", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src/*", "dest": "source/"},
Path("output") / "source" / "src.zip",
bundle_root / "source" / "src.zip",
"/source/",
),
(
{"src": "src/dir/dir_app.py", "dest": "source/dir/apps/"},
Path("output") / "source" / "dir" / "apps" / "dir_app.py",
bundle_root / "source" / "dir" / "apps" / "dir_app.py",
"/source/dir/apps/",
),
],
Expand Down
4 changes: 3 additions & 1 deletion tests/snowpark/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def test_build_with_glob_patterns_in_artifacts(

result = runner.invoke(["snowpark", "build", "--ignore-anaconda"])
assert result.exit_code == 0, result.output
_assert_zip_contains(tmp_dir / "output" / zip_name, expected_files)
_assert_zip_contains(
tmp_dir / "output" / "bundle" / "snowpark" / zip_name, expected_files
)


def _assert_zip_contains(app_zip: str, expected_files: Set[str]):
Expand Down
25 changes: 21 additions & 4 deletions tests/snowpark/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,11 @@ def test_deploy_function_no_changes(
]
assert queries == [
"create stage if not exists IDENTIFIER('MockDatabase.MockSchema.dev_deployment') comment='deployments managed by Snowflake CLI'",
f"put file://{Path(project_dir).resolve()}/output/my_snowpark_project/app.py @MockDatabase.MockSchema.dev_deployment/my_snowpark_project/ auto_compress=false parallel=4 overwrite=True",
_put_query(
Path(project_dir),
"my_snowpark_project/app.py",
"@MockDatabase.MockSchema.dev_deployment/my_snowpark_project/",
),
]


Expand Down Expand Up @@ -286,7 +290,11 @@ def test_deploy_function_needs_update_because_packages_changes(
]
assert queries == [
"create stage if not exists IDENTIFIER('MockDatabase.MockSchema.dev_deployment') comment='deployments managed by Snowflake CLI'",
f"put file://{Path(project_dir).resolve()}/output/my_snowpark_project/app.py @MockDatabase.MockSchema.dev_deployment/my_snowpark_project/ auto_compress=false parallel=4 overwrite=True",
_put_query(
Path(project_dir),
"my_snowpark_project/app.py",
"@MockDatabase.MockSchema.dev_deployment/my_snowpark_project/",
),
dedent(
"""\
create or replace function IDENTIFIER('MockDatabase.MockSchema.func1')(a string default 'default value', b variant)
Expand Down Expand Up @@ -348,8 +356,11 @@ def test_deploy_function_needs_update_because_handler_changes(
]
assert queries == [
"create stage if not exists IDENTIFIER('MockDatabase.MockSchema.dev_deployment') comment='deployments managed by Snowflake CLI'",
f"put file://{Path(project_dir).resolve()}/output/my_snowpark_project/app.py @MockDatabase.MockSchema.dev_deployment/my_snowpark_project/"
f" auto_compress=false parallel=4 overwrite=True",
_put_query(
Path(project_dir),
"my_snowpark_project/app.py",
"@MockDatabase.MockSchema.dev_deployment/my_snowpark_project/",
),
dedent(
"""\
create or replace function IDENTIFIER('MockDatabase.MockSchema.func1')(a string default 'default value', b variant)
Expand Down Expand Up @@ -712,3 +723,9 @@ def test_command_aliases(mock_connector, runner, mock_ctx, command, parameters):

queries = ctx.get_queries()
assert queries[0] == queries[1]


def _put_query(project_root: Path, source: str, dest: str):
return dedent(
f"put file://{project_root.resolve() / 'output' / 'bundle' / 'snowpark' / source} {dest} auto_compress=false parallel=4 overwrite=True"
)
Loading
Loading