Skip to content

Commit

Permalink
Merge branch 'main' into git-repository
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-pczajka committed Mar 14, 2024
2 parents 258eb68 + 8981999 commit 757c899
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 41 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Adding `--image-name` option for image name argument in `spcs image-repository list-tags` for consistency with other commands.
* Fixed errors during `spcs image-registry login` not being formatted correctly.
* Project definition no longer accept extra fields. Any extra field will cause an error.
* Adding `--pattern` flag to `stage list` command for filtering out results with regex.

# v2.1.0

Expand Down
25 changes: 17 additions & 8 deletions src/snowflake/cli/api/commands/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,14 +378,23 @@ def like_option(help_example: str):
)


def pattern_option(help_example: str) -> typer.Option:
return typer.Option(
".*",
"--pattern",
"-p",
help="Regex pattern for filtering files by name. Git repository scope prefix is not included in regex matching."
f" For example {help_example}.",
)
def _pattern_option_callback(value):
if value and value.count("'") != value.count("\\'"):
raise ClickException('All "\'" characters in PATTERN must be escaped: "\\\'"')
return value


PatternOption = typer.Option(
None,
"--pattern",
"-p",
help=(
"Regex pattern for filtering files by name."
' For example --pattern ".*\.txt" will filter only files with .txt extension.'
),
show_default=False,
callback=_pattern_option_callback,
)


def experimental_option(
Expand Down
5 changes: 1 addition & 4 deletions src/snowflake/cli/plugins/git/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import typer
from click import ClickException
from snowflake.cli.api.commands.flags import (
PatternOption,
identifier_argument,
like_option,
pattern_option,
)
from snowflake.cli.api.commands.snow_typer import SnowTyper
from snowflake.cli.api.console.console import cli_console
Expand Down Expand Up @@ -46,9 +46,6 @@ def _repo_path_argument_callback(path):
),
callback=_repo_path_argument_callback,
)
PatternOption = pattern_option(
help_example='`list-files --pattern ".*\.txt"` lists all files with .txt extension',
)


def _assure_repository_does_not_exist(om: ObjectManager, repository_name: str) -> None:
Expand Down
5 changes: 1 addition & 4 deletions src/snowflake/cli/plugins/object/stage/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import click
import typer
from snowflake.cli.api.commands.flags import pattern_option
from snowflake.cli.api.commands.flags import PatternOption
from snowflake.cli.api.commands.snow_typer import SnowTyper
from snowflake.cli.api.output.types import (
CommandResult,
Expand All @@ -22,9 +22,6 @@
)

StageNameArgument = typer.Argument(..., help="Name of the stage.")
PatternOption = pattern_option(
help_example='list --pattern=".*\.txt will list all files with .txt extension'
)


@app.command("list", requires_connection=True)
Expand Down
11 changes: 7 additions & 4 deletions src/snowflake/cli/plugins/object/stage/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ def _to_uri(self, local_path: str):
return uri
return to_string_literal(uri)

def list_files(self, stage_name: str, pattern: str = ".*") -> SnowflakeCursor:
def list_files(
self, stage_name: str, pattern: str | None = None
) -> SnowflakeCursor:
stage_name = self.get_standard_stage_prefix(stage_name)
return self._execute_query(
f"ls {self.quote_stage_name(stage_name)} pattern = '{pattern}'"
)
query = f"ls {self.quote_stage_name(stage_name)}"
if pattern is not None:
query += f" pattern = '{pattern}'"
return self._execute_query(query)

@staticmethod
def _assure_is_existing_directory(path: Path) -> None:
Expand Down
46 changes: 36 additions & 10 deletions tests/__snapshots__/test_help_messages.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,11 @@
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
│ --enable-diag Run python connector diagnostic │
│ test │
│ --diag-log-path TEXT Diagnostic report path │
│ --diag-allowlist-path TEXT Diagnostic report path to optional │
│ allowlist │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
Expand Down Expand Up @@ -975,6 +980,11 @@
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
│ --enable-diag Run python connector diagnostic │
│ test │
│ --diag-log-path TEXT Diagnostic report path │
│ --diag-allowlist-path TEXT Diagnostic report path to optional │
│ allowlist │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
Expand Down Expand Up @@ -1045,6 +1055,11 @@
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
│ --enable-diag Run python connector diagnostic │
│ test │
│ --diag-log-path TEXT Diagnostic report path │
│ --diag-allowlist-path TEXT Diagnostic report path to optional │
│ allowlist │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
Expand Down Expand Up @@ -1076,11 +1091,9 @@
│ [required] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --pattern -p TEXT Regex pattern for filtering files by name. Git │
│ repository scope prefix is not included in regex │
│ matching. For example `list-files --pattern │
│ ".*\.txt"` lists all files with .txt extension. │
│ [default: .*] │
│ --pattern -p TEXT Regex pattern for filtering files by name. For │
│ example --pattern ".*\.txt" will filter only files │
│ with .txt extension. │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Connection configuration ───────────────────────────────────────────────────╮
Expand Down Expand Up @@ -1118,6 +1131,11 @@
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
│ --enable-diag Run python connector diagnostic │
│ test │
│ --diag-log-path TEXT Diagnostic report path │
│ --diag-allowlist-path TEXT Diagnostic report path to optional │
│ allowlist │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
Expand Down Expand Up @@ -1188,6 +1206,11 @@
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
│ --enable-diag Run python connector diagnostic │
│ test │
│ --diag-log-path TEXT Diagnostic report path │
│ --diag-allowlist-path TEXT Diagnostic report path to optional │
│ allowlist │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
Expand Down Expand Up @@ -1260,6 +1283,11 @@
│ of one defined in config │
│ --mfa-passcode TEXT Token to use for multi-factor │
│ authentication (MFA) │
│ --enable-diag Run python connector diagnostic │
│ test │
│ --diag-log-path TEXT Diagnostic report path │
│ --diag-allowlist-path TEXT Diagnostic report path to optional │
│ allowlist │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Global configuration ───────────────────────────────────────────────────────╮
│ --format [TABLE|JSON] Specifies the output format. │
Expand Down Expand Up @@ -1765,11 +1793,9 @@
│ * stage_name TEXT Name of the stage. [default: None] [required] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --pattern -p TEXT Regex pattern for filtering files by name. Git │
│ repository scope prefix is not included in regex │
│ matching. For example list --pattern=".*\.txt will │
│ list all files with .txt extension. │
│ [default: .*] │
│ --pattern -p TEXT Regex pattern for filtering files by name. For │
│ example --pattern ".*\.txt" will filter only files │
│ with .txt extension. │
│ --help -h Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Connection configuration ───────────────────────────────────────────────────╮
Expand Down
2 changes: 1 addition & 1 deletion tests/git/test_git_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_list_files(mock_connector, runner, mock_ctx):
result = runner.invoke(["git", "list-files", "@repo_name/branches/main/"])

assert result.exit_code == 0, result.output
assert ctx.get_query() == "ls @repo_name/branches/main/ pattern = '.*'"
assert ctx.get_query() == "ls @repo_name/branches/main/"


@mock.patch("snowflake.connector.connect")
Expand Down
9 changes: 9 additions & 0 deletions tests/object/stage/test_stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ def test_stage_list_pattern(mock_execute, runner, mock_cursor):
mock_execute.assert_called_once_with("ls @stageName pattern = 'REGEX'")


def test_stage_list_pattern_error(runner):
result = runner.invoke(
["object", "stage", "list", "--pattern", "REGEX without escaped '", "stageName"]
)
assert result.exit_code == 1, result.output
assert "Error" in result.output
assert 'All "\'" characters in PATTERN must be escaped: "\\\'"' in result.output


@mock.patch(f"{STAGE_MANAGER}._execute_query")
def test_stage_list_quoted(mock_execute, runner, mock_cursor):
mock_execute.return_value = mock_cursor(["row"], [])
Expand Down
29 changes: 19 additions & 10 deletions tests_integration/test_stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,32 @@ def test_stage(runner, snowflake_session, test_database, tmp_path):
assert contains_row_with(result.json, row_from_snowflake_session(expect)[0])

filename = "test.txt"
another_filename = "another.md"
with tempfile.TemporaryDirectory() as td:
file_path = os.path.join(td, filename)
Path(file_path).touch()
file_path = Path(td) / filename
another_file_path = Path(td) / another_filename

result = runner.invoke_with_connection_json(
["object", "stage", "copy", file_path, f"@{stage_name}"]
)
assert result.exit_code == 0, result.output
assert contains_row_with(
result.json,
{"source": filename, "target": filename, "status": "UPLOADED"},
)
for path in [file_path, another_file_path]:
path.touch()
result = runner.invoke_with_connection_json(
["object", "stage", "copy", str(path), f"@{stage_name}"]
)
assert result.exit_code == 0, result.output
assert contains_row_with(
result.json,
{"source": path.name, "target": path.name, "status": "UPLOADED"},
)

result = runner.invoke_with_connection_json(["object", "stage", "list", stage_name])
expect = snowflake_session.execute_string(f"list @{stage_name}")
assert result.json == row_from_snowflake_session(expect)

result = runner.invoke_with_connection_json(
["object", "stage", "list", stage_name, "--pattern", ".*md"]
)
assert contains_row_with(result.json, {"name": f"{stage_name}/{another_filename}"})
assert not_contains_row_with(result.json, {"name": f"{stage_name}/{filename}"})

# Operation fails because directory exists
result = runner.invoke_with_connection_json(
["object", "stage", "copy", f"@{stage_name}", tmp_path.parent.__str__()]
Expand Down

0 comments on commit 757c899

Please sign in to comment.