Skip to content

Commit fcc42c7

Browse files
feat: [SNOW-1994372] implement project list versions command (#2160)
feat: [SNOW-1994372] implement project list versions command; minor fixes to project add version command
1 parent a00a1cf commit fcc42c7

File tree

6 files changed

+200
-5
lines changed

6 files changed

+200
-5
lines changed

src/snowflake/cli/_plugins/project/commands.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from snowflake.cli.api.console.console import cli_console
3737
from snowflake.cli.api.constants import ObjectType
3838
from snowflake.cli.api.identifiers import FQN
39-
from snowflake.cli.api.output.types import MessageResult, SingleQueryResult
39+
from snowflake.cli.api.output.types import MessageResult, QueryResult, SingleQueryResult
4040
from snowflake.cli.api.project.project_paths import ProjectPaths
4141

4242
app = SnowTyperFactory(
@@ -146,7 +146,7 @@ def add_version(
146146
help="Source stage to create the version from.",
147147
show_default=False,
148148
),
149-
alias: str
149+
_alias: str
150150
| None = typer.Option(
151151
None, "--alias", help="Alias for the version.", show_default=False
152152
),
@@ -162,7 +162,17 @@ def add_version(
162162
pm.add_version(
163163
project_name=entity_id,
164164
from_stage=_from,
165-
alias=alias,
165+
alias=_alias,
166166
comment=comment,
167167
)
168168
return MessageResult("Version added.")
169+
170+
171+
@app.command(requires_connection=True)
172+
def list_versions(entity_id: str = entity_argument("project"), **options):
173+
"""
174+
Lists versions of given project.
175+
"""
176+
pm = ProjectManager()
177+
results = pm.list_versions(project_name=entity_id)
178+
return QueryResult(results)

src/snowflake/cli/_plugins/project/manager.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,18 @@ def add_version(
6969
)
7070
query = f"ALTER PROJECT {project_name.identifier} ADD VERSION"
7171
if alias:
72-
query += f" IF NOT EXIST {alias}"
72+
query += f" IF NOT EXISTS {alias}"
7373
query += f" FROM {from_stage}"
7474
if comment:
7575
query += f" COMMENT = '{comment}'"
7676
return self.execute_query(query=query)
77+
78+
def list_versions(self, project_name: str | FQN):
79+
project_name = (
80+
project_name
81+
if isinstance(project_name, FQN)
82+
else FQN.from_string(project_name)
83+
)
84+
query = f"SHOW VERSIONS IN PROJECT {project_name}"
85+
86+
return self.execute_query(query=query)

tests/__snapshots__/test_help_messages.ambr

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6283,6 +6283,99 @@
62836283
+------------------------------------------------------------------------------+
62846284

62856285

6286+
'''
6287+
# ---
6288+
# name: test_help_messages[project.list-versions]
6289+
'''
6290+
6291+
Usage: root project list-versions [OPTIONS] [ENTITY_ID]
6292+
6293+
Lists versions of given project.
6294+
6295+
+- Arguments ------------------------------------------------------------------+
6296+
| entity_id [ENTITY_ID] ID of project entity. |
6297+
| [default: None] |
6298+
+------------------------------------------------------------------------------+
6299+
+- Options --------------------------------------------------------------------+
6300+
| --help -h Show this message and exit. |
6301+
+------------------------------------------------------------------------------+
6302+
+- Connection configuration ---------------------------------------------------+
6303+
| --connection,--environment -c TEXT Name of the connection, as |
6304+
| defined in your config.toml |
6305+
| file. Default: default. |
6306+
| --host TEXT Host address for the |
6307+
| connection. Overrides the |
6308+
| value specified for the |
6309+
| connection. |
6310+
| --port INTEGER Port for the connection. |
6311+
| Overrides the value |
6312+
| specified for the |
6313+
| connection. |
6314+
| --account,--accountname TEXT Name assigned to your |
6315+
| Snowflake account. Overrides |
6316+
| the value specified for the |
6317+
| connection. |
6318+
| --user,--username TEXT Username to connect to |
6319+
| Snowflake. Overrides the |
6320+
| value specified for the |
6321+
| connection. |
6322+
| --password TEXT Snowflake password. |
6323+
| Overrides the value |
6324+
| specified for the |
6325+
| connection. |
6326+
| --authenticator TEXT Snowflake authenticator. |
6327+
| Overrides the value |
6328+
| specified for the |
6329+
| connection. |
6330+
| --private-key-file,--private… TEXT Snowflake private key file |
6331+
| path. Overrides the value |
6332+
| specified for the |
6333+
| connection. |
6334+
| --token-file-path TEXT Path to file with an OAuth |
6335+
| token that should be used |
6336+
| when connecting to Snowflake |
6337+
| --database,--dbname TEXT Database to use. Overrides |
6338+
| the value specified for the |
6339+
| connection. |
6340+
| --schema,--schemaname TEXT Database schema to use. |
6341+
| Overrides the value |
6342+
| specified for the |
6343+
| connection. |
6344+
| --role,--rolename TEXT Role to use. Overrides the |
6345+
| value specified for the |
6346+
| connection. |
6347+
| --warehouse TEXT Warehouse to use. Overrides |
6348+
| the value specified for the |
6349+
| connection. |
6350+
| --temporary-connection -x Uses a connection defined |
6351+
| with command line |
6352+
| parameters, instead of one |
6353+
| defined in config |
6354+
| --mfa-passcode TEXT Token to use for |
6355+
| multi-factor authentication |
6356+
| (MFA) |
6357+
| --enable-diag Whether to generate a |
6358+
| connection diagnostic |
6359+
| report. |
6360+
| --diag-log-path TEXT Path for the generated |
6361+
| report. Defaults to system |
6362+
| temporary directory. |
6363+
| --diag-allowlist-path TEXT Path to a JSON file |
6364+
| containing allowlist |
6365+
| parameters. |
6366+
+------------------------------------------------------------------------------+
6367+
+- Global configuration -------------------------------------------------------+
6368+
| --format [TABLE|JSON] Specifies the output format. |
6369+
| [default: TABLE] |
6370+
| --verbose -v Displays log entries for log levels info |
6371+
| and higher. |
6372+
| --debug Displays log entries for log levels debug |
6373+
| and higher; debug logs contain additional |
6374+
| information. |
6375+
| --silent Turns off intermediate output to console. |
6376+
+------------------------------------------------------------------------------+
6377+
6378+
62866379
'''
62876380
# ---
62886381
# name: test_help_messages[project.list]
@@ -6403,6 +6496,7 @@
64036496
| dry-run Validates a project. |
64046497
| execute Executes a project. |
64056498
| list Lists all available projects. |
6499+
| list-versions Lists versions of given project. |
64066500
+------------------------------------------------------------------------------+
64076501

64086502

@@ -13531,6 +13625,7 @@
1353113625
| dry-run Validates a project. |
1353213626
| execute Executes a project. |
1353313627
| list Lists all available projects. |
13628+
| list-versions Lists versions of given project. |
1353413629
+------------------------------------------------------------------------------+
1353513630

1353613631

tests/dcm_project/test_dcm_project.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,12 @@ def test_list_command_alias(mock_connect, runner):
157157
== queries[1]
158158
== "show projects like '%PROJECT_NAME%' in database my_db"
159159
)
160+
161+
162+
@mock.patch(ProjectManager)
163+
def test_list_versions(mock_pm, runner):
164+
result = runner.invoke(["project", "list-versions", "fooBar"])
165+
166+
assert result.exit_code == 0, result.output
167+
168+
mock_pm().list_versions.assert_called_once_with(project_name="fooBar")

tests/dcm_project/test_dcm_project_manager.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_add_version(mock_execute_query, runner, project_directory):
2626
)
2727

2828
mock_execute_query.assert_called_once_with(
29-
query="ALTER PROJECT my_project ADD VERSION IF NOT EXIST v1 FROM @stage_foo COMMENT = 'fancy'"
29+
query="ALTER PROJECT my_project ADD VERSION IF NOT EXISTS v1 FROM @stage_foo COMMENT = 'fancy'"
3030
)
3131

3232

@@ -50,3 +50,13 @@ def test_validate_project(mock_execute_query, runner, project_directory):
5050
mock_execute_query.assert_called_once_with(
5151
query="EXECUTE PROJECT IDENTIFIER('my_project') WITH VERSION v42 DRY_RUN=TRUE"
5252
)
53+
54+
55+
@mock.patch(execute_queries)
56+
def test_list_versions(mock_execute_query, runner):
57+
mgr = ProjectManager()
58+
mgr.list_versions(project_name=TEST_PROJECT)
59+
60+
mock_execute_query.assert_called_once_with(
61+
query="SHOW VERSIONS IN PROJECT my_project"
62+
)

tests_integration/test_dcm_project.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import pytest
1616

17+
from snowflake.cli.api.secure_path import SecurePath
18+
1719

1820
@pytest.mark.integration
1921
@pytest.mark.qa_only
@@ -56,3 +58,62 @@ def test_project_deploy(
5658
assert len(result.json) == 1
5759
project = result.json[0]
5860
assert project["name"].lower() == "my_project".lower()
61+
62+
63+
@pytest.mark.integration
64+
@pytest.mark.qa_only
65+
def test_project_add_version(
66+
runner,
67+
snowflake_session,
68+
test_database,
69+
project_directory,
70+
):
71+
with project_directory("dcm_project") as root:
72+
# Create a new project
73+
result = runner.invoke_with_connection_json(["project", "create-version"])
74+
assert result.exit_code == 0, result.output
75+
if (root / "output").exists():
76+
SecurePath(root / "output").rmdir(recursive=True)
77+
78+
# Modify sql file and upload it to a new stage
79+
with open(root / "file_a.sql", mode="w") as fp:
80+
fp.write(
81+
"define table identifier('{{ table_name }}') (fooBar string, baz string);"
82+
)
83+
84+
stage_name = "dcm_project_stage"
85+
result = runner.invoke_with_connection_json(["stage", "create", stage_name])
86+
assert result.exit_code == 0, result.output
87+
88+
result = runner.invoke_with_connection_json(
89+
["stage", "copy", ".", f"@{stage_name}"]
90+
)
91+
assert result.exit_code == 0, result.output
92+
93+
# create a new version of the project
94+
result = runner.invoke_with_connection_json(
95+
[
96+
"project",
97+
"add-version",
98+
"my_project",
99+
"--from",
100+
f"@{stage_name}",
101+
"--alias",
102+
"v2",
103+
]
104+
)
105+
assert result.exit_code == 0, result.output
106+
107+
# list project versions
108+
result = runner.invoke_with_connection_json(
109+
[
110+
"project",
111+
"list-versions",
112+
"MY_PROJECT",
113+
]
114+
)
115+
assert result.exit_code == 0, result.output
116+
assert len(result.json) == 2
117+
assert result.json[0]["name"].lower() == "VERSION$2".lower()
118+
assert result.json[0]["alias"].lower() == "v2".lower()
119+
assert result.json[1]["name"].lower() == "VERSION$1".lower()

0 commit comments

Comments
 (0)