Skip to content

Commit 95d2161

Browse files
SNOW-2037785 Add runtime_environment_version for Notebook (#2478)
* SNOW-2037785 Add `runtime_environment_version` for Notebook * verify `runtime_environment_version` via SQL
1 parent b4fd894 commit 95d2161

File tree

9 files changed

+214
-1
lines changed

9 files changed

+214
-1
lines changed

RELEASE-NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
## New additions
2222
* Add `snow connection remove` command
23+
* Added support for `runtime_environment_version` field in notebook entity configuration, allowing specification of runtime environment version for containerized notebooks.
2324

2425
## Fixes and improvements
2526
* Fixed DBT deploy command to properly handle fully qualified names

src/snowflake/cli/_plugins/notebook/notebook_entity.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def get_create_sql(self, replace: bool) -> str:
6060
query += f"\nCOMPUTE_POOL = '{self.model.compute_pool}'"
6161
if self.model.runtime_name:
6262
query += f"\nRUNTIME_NAME = '{self.model.runtime_name}'"
63+
if self.model.runtime_environment_version and not self.model.compute_pool:
64+
query += f"\nRUNTIME_ENVIRONMENT_VERSION = '{self.model.runtime_environment_version}'"
6365

6466
query += (
6567
";\n// Cannot use IDENTIFIER(...)"

src/snowflake/cli/_plugins/notebook/notebook_entity_model.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class NotebookEntityModel(EntityModelBaseWithArtifacts):
2020
)
2121
notebook_file: Path = Field(title="Notebook file")
2222
query_warehouse: str = Field(title="Snowflake warehouse to execute the notebook")
23+
runtime_environment_version: Optional[str] = Field(
24+
title="Runtime environment version", default=None
25+
)
2326
compute_pool: Optional[str] = Field(
2427
title="Compute pool to run the notebook in", default=None
2528
)
@@ -37,6 +40,10 @@ def validate_notebook_file(self):
3740
def validate_container_setup(self):
3841
if self.compute_pool and not self.runtime_name:
3942
raise ValueError("compute_pool is specified without runtime_name")
40-
if self.runtime_name and not self.compute_pool and not self:
43+
if self.runtime_name and not self.compute_pool:
4144
raise ValueError("runtime_name is specified without compute_pool")
45+
if self.compute_pool and self.runtime_environment_version:
46+
raise ValueError(
47+
"runtime_environment_version is only applicable for notebooks using warehouse, not compute pool"
48+
)
4249
return self

tests/notebook/test_notebook_commands.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,72 @@ def test_deploy_project_definition_version_error(
225225
"This command requires project definition of version at least 2."
226226
in result.output
227227
)
228+
229+
230+
@mock.patch("snowflake.connector.connect")
231+
@mock.patch("snowflake.cli._plugins.notebook.notebook_entity.make_snowsight_url")
232+
@mock.patch(f"{STAGE_MANAGER}.list_files")
233+
def test_deploy_notebook_with_runtime_environment_version_sql(
234+
mock_list_files,
235+
mock_make_url,
236+
mock_connector,
237+
mock_ctx,
238+
runner,
239+
project_directory,
240+
):
241+
"""Test that runtime_environment_version field generates correct SQL for warehouse-only notebooks."""
242+
ctx = mock_ctx()
243+
mock_connector.return_value = ctx
244+
mock_make_url.return_value = "http://the.notebook.url.mock"
245+
246+
with project_directory("notebook_with_runtime_env"):
247+
result = runner.invoke(
248+
["notebook", "deploy", "warehouse_notebook", "--replace"]
249+
)
250+
assert result.exit_code == 0, result.output
251+
252+
# Get the generated SQL and verify it contains RUNTIME_ENVIRONMENT_VERSION
253+
query = ctx.get_query()
254+
assert "RUNTIME_ENVIRONMENT_VERSION = 'WH-RUNTIME-2.0'" in query
255+
256+
# Verify it does NOT contain compute pool fields (should be warehouse only)
257+
assert "COMPUTE_POOL" not in query
258+
assert "RUNTIME_NAME" not in query
259+
260+
# Verify the full SQL structure is correct
261+
lines = [line for line in query.split("\n") if not line.startswith("put")]
262+
sql_content = "\n".join(lines)
263+
assert "CREATE OR REPLACE NOTEBOOK" in sql_content
264+
assert "QUERY_WAREHOUSE = 'xsmall'" in sql_content
265+
assert "RUNTIME_ENVIRONMENT_VERSION = 'WH-RUNTIME-2.0'" in sql_content
266+
267+
268+
def test_deploy_notebook_validation_error_compute_pool_with_runtime_env(
269+
runner, project_directory, alter_snowflake_yml
270+
):
271+
"""Test that using runtime_environment_version with compute_pool raises validation error."""
272+
with project_directory("notebook_v2") as project_root:
273+
# Add compute_pool, runtime_name, and runtime_environment_version to trigger validation error
274+
alter_snowflake_yml(
275+
project_root / "snowflake.yml",
276+
parameter_path="entities.my_notebook.compute_pool",
277+
value="test_compute_pool",
278+
)
279+
alter_snowflake_yml(
280+
project_root / "snowflake.yml",
281+
parameter_path="entities.my_notebook.runtime_name",
282+
value="system$basic_runtime",
283+
)
284+
alter_snowflake_yml(
285+
project_root / "snowflake.yml",
286+
parameter_path="entities.my_notebook.runtime_environment_version",
287+
value="WH-RUNTIME-2.0",
288+
)
289+
290+
result = runner.invoke(["notebook", "deploy", "--replace"])
291+
assert result.exit_code == 1, result.output
292+
assert (
293+
"runtime_environment_version is only applicable for notebooks"
294+
in result.output
295+
)
296+
assert "using warehouse, not compute pool" in result.output
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"print(\"Hello world!\")\n"
10+
]
11+
}
12+
],
13+
"metadata": {
14+
"language_info": {
15+
"name": "python"
16+
}
17+
},
18+
"nbformat": 4,
19+
"nbformat_minor": 2
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
definition_version: 2
2+
entities:
3+
warehouse_notebook:
4+
type: "notebook"
5+
query_warehouse: xsmall
6+
notebook_file: notebook.ipynb
7+
runtime_environment_version: "WH-RUNTIME-2.0"
8+
artifacts:
9+
- notebook.ipynb
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"print(\"Hello world!\")\n"
10+
]
11+
}
12+
],
13+
"metadata": {
14+
"language_info": {
15+
"name": "python"
16+
}
17+
},
18+
"nbformat": 4,
19+
"nbformat_minor": 2
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
definition_version: 2
2+
entities:
3+
warehouse_notebook:
4+
type: "notebook"
5+
query_warehouse: xsmall
6+
notebook_file: notebook.ipynb
7+
runtime_environment_version: "WH-RUNTIME-2.0"
8+
artifacts:
9+
- notebook.ipynb

tests_integration/test_notebooks.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,79 @@ def test_deploy_single_notebook(runner, project_directory, test_database):
9696
"custom_stage/particular_notebook_path/notebook.ipynb",
9797
],
9898
)
99+
100+
101+
@pytest.mark.integration
102+
def test_deploy_containerized_notebook_with_compute_pool(
103+
runner, project_directory, test_database
104+
):
105+
"""Test deployment of containerized notebook with compute pool."""
106+
with project_directory("notebook_containerized_v2"):
107+
# Deploy the containerized notebook with compute pool
108+
result = runner.invoke_with_connection(
109+
["notebook", "deploy", "containerized_notebook", "--replace"]
110+
)
111+
assert result.exit_code == 0, result.output
112+
assert (
113+
"Uploading artifacts to @notebooks/containerized_notebook" in result.output
114+
)
115+
assert "Notebook successfully deployed and available under" in result.output
116+
117+
# Verify the notebook was created with the correct artifacts
118+
assert_stage_has_files(
119+
runner,
120+
"notebooks",
121+
["notebooks/containerized_notebook/notebook.ipynb"],
122+
)
123+
124+
# Test that the notebook can be deployed again with --replace
125+
result = runner.invoke_with_connection(
126+
["notebook", "deploy", "containerized_notebook", "--replace"]
127+
)
128+
assert result.exit_code == 0, result.output
129+
assert "Notebook successfully deployed and available under" in result.output
130+
131+
132+
@pytest.mark.integration
133+
def test_deploy_notebook_with_runtime_environment_version(
134+
runner, project_directory, test_database
135+
):
136+
"""Test deployment of notebook with runtime_environment_version field for warehouse (no compute pool)."""
137+
with project_directory("notebook_with_runtime_env"):
138+
# Deploy the notebook with runtime_environment_version
139+
result = runner.invoke_with_connection(
140+
["notebook", "deploy", "warehouse_notebook", "--replace"]
141+
)
142+
assert result.exit_code == 0, result.output
143+
assert "Uploading artifacts to @notebooks/warehouse_notebook" in result.output
144+
assert "Notebook successfully deployed and available under" in result.output
145+
146+
# Verify the notebook was created with the correct artifacts
147+
assert_stage_has_files(
148+
runner,
149+
"notebooks",
150+
["notebooks/warehouse_notebook/notebook.ipynb"],
151+
)
152+
153+
# Test that the notebook can be deployed again with --replace
154+
result = runner.invoke_with_connection(
155+
["notebook", "deploy", "warehouse_notebook", "--replace"]
156+
)
157+
assert result.exit_code == 0, result.output
158+
assert "Notebook successfully deployed and available under" in result.output
159+
160+
# Verify describe notebook shows runtime_environment_version is set using direct SQL
161+
result = runner.invoke_with_connection_json(
162+
["sql", "-q", "DESCRIBE NOTEBOOK warehouse_notebook"]
163+
)
164+
assert result.exit_code == 0, result.output
165+
# Check that runtime_environment_version appears in the describe result
166+
describe_output = (
167+
str(result.json).lower() if result.json else result.output.lower()
168+
)
169+
assert (
170+
"runtime_environment_version" in describe_output
171+
), f"runtime_environment_version not found in describe output: {result.output}"
172+
assert (
173+
"wh-runtime-2.0" in describe_output
174+
), f"Expected runtime version 'WH-RUNTIME-2.0' not found in describe output: {result.output}"

0 commit comments

Comments
 (0)