Skip to content

Commit 79e5c56

Browse files
authored
SNOW-2014274: Add restricted caller permission for execute_as (#3226)
1 parent a4cdc42 commit 79e5c56

File tree

8 files changed

+28
-16
lines changed

8 files changed

+28
-16
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44

55
### Snowpark Python API Updates
66

7+
#### New Features
8+
9+
- Added support for `restricted caller` permission of `execute_as` argument in `StoredProcedure.regsiter()`
10+
711
#### Bug Fixes
812

913
- Fixed a bug in `DataFrame.group_by().pivot().agg` when the pivot column and aggregate column are the same.
14+
- Fixed a bug in local testing where transient `__pycache__` directory was unintentionally copied during stored procedure execution via import.
1015

1116
#### Deprecations
1217

src/snowflake/snowpark/_internal/ast/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1442,7 +1442,7 @@ def build_sproc( # type: ignore[no-untyped-def] # TODO(SNOW-1491199) # Function
14421442
secrets: Optional[Dict[str, str]] = None,
14431443
comment: Optional[str] = None,
14441444
statement_params: Optional[Dict[str, str]] = None,
1445-
execute_as: typing.Literal["caller", "owner"] = "owner",
1445+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
14461446
source_code_display: bool = True,
14471447
is_permanent: bool = False,
14481448
session: "snowflake.snowpark.session.Session" = None,

src/snowflake/snowpark/_internal/udf_utils.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
AGGREGATE_FUNCTION_MERGE_METHOD = "merge"
9393
AGGREGATE_FUNCTION_STATE_METHOD = "aggregate_state"
9494

95-
EXECUTE_AS_WHITELIST = frozenset(["owner", "caller"])
95+
EXECUTE_AS_WHITELIST = frozenset(["owner", "caller", "restricted caller"])
9696

9797
REGISTER_KWARGS_ALLOWLIST = {
9898
"native_app_params",
@@ -142,7 +142,9 @@ def __init__(
142142
func: Union[Callable, Tuple[str, str]],
143143
replace: bool = False,
144144
if_not_exists: bool = False,
145-
execute_as: Optional[typing.Literal["caller", "owner"]] = None,
145+
execute_as: Optional[
146+
typing.Literal["caller", "owner", "restricted caller"]
147+
] = None,
146148
anonymous: bool = False,
147149
) -> None:
148150
self.func = func
@@ -513,7 +515,9 @@ def check_register_args(
513515
)
514516

515517

516-
def check_execute_as_arg(execute_as: typing.Literal["caller", "owner"]):
518+
def check_execute_as_arg(
519+
execute_as: typing.Literal["caller", "owner", "restricted caller"]
520+
):
517521
if (
518522
not isinstance(execute_as, str)
519523
or execute_as.lower() not in EXECUTE_AS_WHITELIST
@@ -1266,7 +1270,7 @@ def create_python_udf_or_sp(
12661270
if_not_exists: bool,
12671271
raw_imports: Optional[List[Union[str, Tuple[str, str]]]],
12681272
inline_python_code: Optional[str] = None,
1269-
execute_as: Optional[typing.Literal["caller", "owner"]] = None,
1273+
execute_as: Optional[typing.Literal["caller", "owner", "restricted caller"]] = None,
12701274
api_call_source: Optional[str] = None,
12711275
strict: bool = False,
12721276
secure: bool = False,

src/snowflake/snowpark/functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10448,7 +10448,7 @@ def sproc(
1044810448
session: Optional["snowflake.snowpark.Session"] = None,
1044910449
parallel: int = 4,
1045010450
statement_params: Optional[Dict[str, str]] = None,
10451-
execute_as: typing.Literal["caller", "owner"] = "owner",
10451+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
1045210452
strict: bool = False,
1045310453
source_code_display: bool = True,
1045410454
external_access_integrations: Optional[List[str]] = None,

src/snowflake/snowpark/mock/_stored_procedure.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def __init__(
4848
input_types: List[DataType],
4949
name: str,
5050
imports: Set[str],
51-
execute_as: typing.Literal["caller", "owner"] = "owner",
51+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
5252
anonymous_sp_sql: Optional[str] = None,
5353
strict=False,
5454
_ast: Optional[proto.Expr] = None,
@@ -249,7 +249,7 @@ def _do_register_sp(
249249
*,
250250
source_code_display: bool = False,
251251
statement_params: Optional[Dict[str, str]] = None,
252-
execute_as: typing.Literal["caller", "owner"] = "owner",
252+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
253253
anonymous: bool = False,
254254
api_call_source: str,
255255
skip_upload_on_content_match: bool = False,

src/snowflake/snowpark/mock/_util.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,10 @@ def cleanup_imports():
319319
sys.path.append(module_path)
320320
if os.path.isdir(module_path):
321321
shutil.copytree(
322-
module_path, temporary_import_path.name, dirs_exist_ok=True
322+
module_path,
323+
temporary_import_path.name,
324+
dirs_exist_ok=True,
325+
ignore=shutil.ignore_patterns("__pycache__"),
323326
)
324327
else:
325328
shutil.copy2(module_path, temporary_import_path.name)

src/snowflake/snowpark/stored_procedure.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(
7575
return_type: DataType,
7676
input_types: List[DataType],
7777
name: str,
78-
execute_as: typing.Literal["caller", "owner"] = "owner",
78+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
7979
anonymous_sp_sql: Optional[str] = None,
8080
packages: Optional[List[Union[str, ModuleType]]] = None,
8181
_ast: Optional[proto.StoredProcedure] = None,
@@ -506,7 +506,7 @@ def register(
506506
replace: bool = False,
507507
if_not_exists: bool = False,
508508
parallel: int = 4,
509-
execute_as: typing.Literal["caller", "owner"] = "owner",
509+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
510510
strict: bool = False,
511511
external_access_integrations: Optional[List[str]] = None,
512512
secrets: Optional[Dict[str, str]] = None,
@@ -654,7 +654,7 @@ def register_from_file(
654654
replace: bool = False,
655655
if_not_exists: bool = False,
656656
parallel: int = 4,
657-
execute_as: typing.Literal["caller", "owner"] = "owner",
657+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
658658
strict: bool = False,
659659
external_access_integrations: Optional[List[str]] = None,
660660
secrets: Optional[Dict[str, str]] = None,
@@ -725,7 +725,7 @@ def register_from_file(
725725
Increasing the number of threads can improve performance when uploading
726726
large stored procedure files.
727727
execute_as: What permissions should the procedure have while executing. This
728-
supports caller, or owner for now.
728+
supports ``caller``, ``owner`` or ``restricted caller`` for now.
729729
strict: Whether the created stored procedure is strict. A strict stored procedure will not invoke
730730
the stored procedure if any input is null. Instead, a null value will always be returned. Note
731731
that the stored procedure might still return null for non-null inputs.
@@ -811,7 +811,7 @@ def _do_register_sp(
811811
*,
812812
source_code_display: bool = False,
813813
statement_params: Optional[Dict[str, str]] = None,
814-
execute_as: typing.Literal["caller", "owner"] = "owner",
814+
execute_as: typing.Literal["caller", "owner", "restricted caller"] = "owner",
815815
anonymous: bool = False,
816816
api_call_source: str,
817817
skip_upload_on_content_match: bool = False,

tests/integ/test_stored_procedure.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,7 +1699,7 @@ def plus1(_: Session, x: int) -> int:
16991699
session._run_query(f"drop procedure if exists {perm_sp_name}(int)")
17001700

17011701

1702-
@pytest.mark.parametrize("execute_as", [None, "owner", "caller"])
1702+
@pytest.mark.parametrize("execute_as", [None, "owner", "caller", "restricted caller"])
17031703
def test_execute_as_options(session, execute_as):
17041704
"""Make sure that a stored procedure can be run with any EXECUTE AS option."""
17051705

@@ -1716,7 +1716,7 @@ def return1(_):
17161716
assert return1_sp() == 1
17171717

17181718

1719-
@pytest.mark.parametrize("execute_as", [None, "owner", "caller"])
1719+
@pytest.mark.parametrize("execute_as", [None, "owner", "caller", "restricted caller"])
17201720
def test_execute_as_options_while_registering_from_file(
17211721
session, resources_path, tmpdir, execute_as
17221722
):

0 commit comments

Comments
 (0)