From 72294a71a59bb7a6359b99f64e1453a06de43aed Mon Sep 17 00:00:00 2001 From: mborodii-prog Date: Fri, 17 Jul 2026 12:29:29 +0300 Subject: [PATCH 1/7] Add user_group recipe variable --- tests/connectors/test_train.py | 216 +++++++++++++++++++------------- tests/recipes/test_variables.py | 96 ++++++++++++++ wrangles/auth.py | 30 +++++ wrangles/recipe.py | 10 ++ 4 files changed, 263 insertions(+), 89 deletions(-) diff --git a/tests/connectors/test_train.py b/tests/connectors/test_train.py index 6cf8bd017..0807cd29c 100644 --- a/tests/connectors/test_train.py +++ b/tests/connectors/test_train.py @@ -5,6 +5,33 @@ import pytest import logging import re +import importlib + + +class FakeTrainResponse: + ok = True + status_code = 200 + + def __init__(self, model_id="test-model-id"): + self.model_id = model_id + self.text = f'{{"model_id":"{model_id}"}}' + + def json(self): + return {"model_id": self.model_id} + + +def mock_train_model_create(monkeypatch, model_id="test-model-id"): + train_module = importlib.import_module("wrangles.train") + calls = [] + + def post(*args, **kwargs): + calls.append((args, kwargs)) + return FakeTrainResponse(model_id) + + monkeypatch.setattr(train_module._auth, "get_access_token", lambda: "test-token") + monkeypatch.setattr(train_module._requests, "post", post) + return calls + class LogCapture(logging.Handler): def __init__(self, *args, **kwargs): @@ -141,26 +168,30 @@ def test_classify_read_four_cols_error(mocker): """ ) -def test_classify_write_logs_new_model_id_integration(caplog): - df = pd.DataFrame({ - 'Example': ['apple', 'banana'], - 'Category': ['fruit', 'fruit'], - 'Notes': ['', ''] - }) - - try: - wrangles.recipe.run( - """ - write: - - train.classify: - name: Test Classify Model - """, - dataframe=df - ) - - assert any(record.message for record in caplog.records if record.levelname == "INFO" and "New classify model created" in record.message) - finally: - _delete_model_from_log(caplog, "New classify model created") +def test_classify_write_logs_new_model_id_integration(caplog, monkeypatch): + calls = mock_train_model_create(monkeypatch, "classify-test-model") + df = pd.DataFrame({ + 'Example': ['apple', 'banana'], + 'Category': ['fruit', 'fruit'], + 'Notes': ['', ''] + }) + + wrangles.recipe.run( + """ + write: + - train.classify: + name: Test Classify Model + """, + dataframe=df + ) + + assert any(record.message for record in caplog.records if record.levelname == "INFO" and "New classify model created" in record.message) + assert calls[0][1]["params"] == {"type": "classify", "name": "Test Classify Model"} + assert calls[0][1]["json"] == [ + ["Example", "Category", "Notes"], + ["apple", "fruit", ""], + ["banana", "fruit", ""], + ] class TestTrainExtract: """ @@ -744,10 +775,11 @@ def test_update_model(self): df = wrangles.recipe.run(recipe, dataframe=df) assert df.iloc[0]['Key'] == 'Rachel' and df.iloc[0]['Value'] == 'Updated Rachel' - def test_upsert_new_model_recipe(self): + def test_upsert_new_model_recipe(self, monkeypatch): """ - Test upsert creates new model when model_id doesn't exist + Test upsert creates a new model request without persisting a real test model. """ + calls = mock_train_model_create(monkeypatch, "lookup-upsert-test-model") df = pd.DataFrame({ 'Key': ['Rachel', 'NewCharacter'], 'Value': ['Updated Rachel', 'New Movie'] @@ -762,19 +794,18 @@ def test_upsert_new_model_recipe(self): variant: key """ - model_id = None - try: - result = wrangles.recipe.run(recipe, dataframe=df) - assert len(result) == 2 - assert 'NewCharacter' in result['Key'].tolist() - assert result['Value'].tolist() == ['Updated Rachel', 'New Movie'] - models = wrangles.data.user.models('lookup') - model = next((m for m in models if m['name'] == model_name), None) - assert model is not None - model_id = model['id'] - finally: - if model_id: - wrangles.train.delete(model_id) + result = wrangles.recipe.run(recipe, dataframe=df) + assert len(result) == 2 + assert 'NewCharacter' in result['Key'].tolist() + assert result['Value'].tolist() == ['Updated Rachel', 'New Movie'] + assert calls[0][1]["params"] == {"type": "lookup", "name": model_name, "variant": "key"} + assert calls[0][1]["json"] == { + "Columns": ["Key", "Value"], + "Data": [ + ["Rachel", "Updated Rachel"], + ["NewCharacter", "New Movie"], + ], + } def test_action_parameter_upsert(self): """ @@ -1421,33 +1452,36 @@ def test_missing_columns_error_message(self, mocker): wrangles.recipe.run(recipe, dataframe=df) -def test_lookup_write_logs_new_model_id(caplog): - """ - Integration test for lookup model creation logging - """ - df = pd.DataFrame({ - 'Key': ['apple', 'banana'], - 'Value': ['fruit', 'fruit'] - }) - - try: - wrangles.recipe.run( - """ - write: - - train.lookup: - name: Test Lookup Model Integration - variant: key - """, - dataframe=df - ) - - # Check that model_id was logged - assert any( - record.message for record in caplog.records - if record.levelname == "INFO" and "New lookup model created" in record.message - ) - finally: - _delete_model_from_log(caplog, "New lookup model created") +def test_lookup_write_logs_new_model_id(caplog, monkeypatch): + """ + Test lookup model creation logging without creating a real test model. + """ + calls = mock_train_model_create(monkeypatch, "lookup-test-model") + df = pd.DataFrame({ + 'Key': ['apple', 'banana'], + 'Value': ['fruit', 'fruit'] + }) + + wrangles.recipe.run( + """ + write: + - train.lookup: + name: Test Lookup Model Integration + variant: key + """, + dataframe=df + ) + + # Check that model_id was logged + assert any( + record.message for record in caplog.records + if record.levelname == "INFO" and "New lookup model created" in record.message + ) + assert calls[0][1]["params"] == {"type": "lookup", "name": "Test Lookup Model Integration", "variant": "key"} + assert calls[0][1]["json"] == { + "Columns": ["Key", "Value"], + "Data": [["apple", "fruit"], ["banana", "fruit"]], + } # @@ -1531,33 +1565,37 @@ def test_standardize_error(): }) ) -def test_standardize_write_logs_new_model_id(caplog): - """ - Integration test for standardize model creation logging - """ - df = pd.DataFrame({ - 'Find': ['ASAP', 'ETA'], - 'Replace': ['As Soon As Possible', 'Estimated Time of Arrival'], - 'Notes': ['', ''] - }) - - try: - wrangles.recipe.run( - """ - write: - - train.standardize: - name: Test Standardize Model Integration - """, - dataframe=df - ) - - # Check that model creation was logged - assert any( - record.message for record in caplog.records - if record.levelname == "INFO" and "Creating new standardize model" in record.message - ) - finally: - _delete_model_from_log(caplog, "New standardize model created") +def test_standardize_write_logs_new_model_id(caplog, monkeypatch): + """ + Test standardize model creation logging without creating a real test model. + """ + calls = mock_train_model_create(monkeypatch, "standardize-test-model") + df = pd.DataFrame({ + 'Find': ['ASAP', 'ETA'], + 'Replace': ['As Soon As Possible', 'Estimated Time of Arrival'], + 'Notes': ['', ''] + }) + + wrangles.recipe.run( + """ + write: + - train.standardize: + name: Test Standardize Model Integration + """, + dataframe=df + ) + + # Check that model creation was logged + assert any( + record.message for record in caplog.records + if record.levelname == "INFO" and "Creating new standardize model" in record.message + ) + assert calls[0][1]["params"] == {"type": "standardize", "name": "Test Standardize Model Integration"} + assert calls[0][1]["json"] == [ + ["Find", "Replace", "Notes"], + ["ASAP", "As Soon As Possible", ""], + ["ETA", "Estimated Time of Arrival", ""], + ] class TestTrainMetaData: diff --git a/tests/recipes/test_variables.py b/tests/recipes/test_variables.py index 6529a86f3..ceb5b2975 100644 --- a/tests/recipes/test_variables.py +++ b/tests/recipes/test_variables.py @@ -365,3 +365,99 @@ def test_variables_variable_overwrite(): variables={'recipe_variables': 'This is a string'} ) assert isinstance(df['vars'][0], dict) + + +def test_user_group_variable(monkeypatch): + """ + Test that the authenticated user's group is available as a recipe variable. + """ + monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "enterprise") + + df = wrangles.recipe.run( + """ + read: + - test: + rows: 1 + values: + group: ${user_group} + """ + ) + + assert df['group'][0] == 'enterprise' + + +def test_user_group_variable_if(monkeypatch): + """ + Test that user_group can be used in Python-style if conditions. + """ + monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "enterprise") + + df = wrangles.recipe.run( + """ + read: + - test: + rows: 1 + values: + result: kept + wrangles: + - create.column: + output: allowed + value: true + if: user_group == 'enterprise' + """ + ) + + assert df['allowed'][0] == True + + +def test_user_group_variable_user_override(monkeypatch): + """ + Test that explicit variables still override the authenticated user group. + """ + monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "enterprise") + + df = wrangles.recipe.run( + """ + read: + - test: + rows: 1 + values: + group: ${user_group} + """, + variables={"user_group": "manual"} + ) + + assert df['group'][0] == 'manual' + + +def test_user_group_variable_from_recipe_metadata(monkeypatch): + """ + Test that recipe metadata permission group is preferred for remote recipes. + """ + monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "token-group") + monkeypatch.setattr( + wrangles.recipe._data, + "model", + lambda model_id: { + "purpose": "recipe", + "production_version_id": "v1", + "user_group": "metadata-group", + } + ) + monkeypatch.setattr( + wrangles.recipe._data, + "model_content", + lambda model_id, version_id=None: { + "recipe": """ + read: + - test: + rows: 1 + values: + group: ${user_group} + """ + } + ) + + df = wrangles.recipe.run("12345678-1234-1234") + + assert df["group"][0] == "metadata-group" diff --git a/wrangles/auth.py b/wrangles/auth.py index 31c5adf00..a08ff7eb1 100644 --- a/wrangles/auth.py +++ b/wrangles/auth.py @@ -84,3 +84,33 @@ def get_access_token(): _access_token_expiry = _datetime.now() + _timedelta(0, response.json()['expires_in'] - 30) return _access_token + + +def extract_user_group(source: dict): + """ + Extract the permission-driving user group from a metadata or token payload. + """ + if not isinstance(source, dict): + return None + + return source.get("user_group") + + +def get_user_group(): + """ + Return the authenticated user's permission-driving group from the current access token. + + If no user is authenticated or the token does not contain user_group, + return None so recipes can still run without backend credentials. + """ + try: + token = get_access_token() + except Exception: + return None + + try: + claims = _jwt.decode(token, options={"verify_signature": False}) + except Exception: + return None + + return extract_user_group(claims) diff --git a/wrangles/recipe.py b/wrangles/recipe.py index 77ed87dc6..dcf449466 100644 --- a/wrangles/recipe.py +++ b/wrangles/recipe.py @@ -20,6 +20,7 @@ from . import recipe_wrangles as _recipe_wrangles from . import connectors as _connectors from . import data as _data +from . import auth as _auth from .config import ( reserved_word_replacements as _reserved_word_replacements, where_overwrite_output as _where_overwrite_output, @@ -75,6 +76,10 @@ def _load_recipe( """ if variables is None: variables = {} + user_variable_keys = set(variables.keys()) + + if "user_group" not in variables: + variables["user_group"] = _auth.get_user_group() # Accept path-like objects (e.g. pathlib.Path) by converting to str if isinstance(recipe, _os.PathLike): @@ -122,6 +127,11 @@ def _load_recipe( if metadata.get('message', None) == 'error': raise ValueError('Incorrect model_id.\nmodel_id may be wrong or does not exists') + metadata_user_group = _auth.extract_user_group(metadata) + if metadata_user_group is not None: + if "user_group" not in user_variable_keys: + variables["user_group"] = metadata_user_group + # Using model_id in wrong function purpose = metadata['purpose'] if purpose != 'recipe': From 42a4ee9f81f6feeddebc63cff93484aea719a31a Mon Sep 17 00:00:00 2001 From: mborodii-prog Date: Fri, 17 Jul 2026 12:34:27 +0300 Subject: [PATCH 2/7] user_permission_team --- tests/recipes/test_variables.py | 42 ++++++++++++++++----------------- wrangles/auth.py | 14 +++++------ wrangles/recipe.py | 12 +++++----- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/recipes/test_variables.py b/tests/recipes/test_variables.py index ceb5b2975..b9091aa1e 100644 --- a/tests/recipes/test_variables.py +++ b/tests/recipes/test_variables.py @@ -367,11 +367,11 @@ def test_variables_variable_overwrite(): assert isinstance(df['vars'][0], dict) -def test_user_group_variable(monkeypatch): +def test_user_permission_team_variable(monkeypatch): """ - Test that the authenticated user's group is available as a recipe variable. + Test that the authenticated user's permission team is available as a recipe variable. """ - monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "enterprise") + monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "enterprise") df = wrangles.recipe.run( """ @@ -379,18 +379,18 @@ def test_user_group_variable(monkeypatch): - test: rows: 1 values: - group: ${user_group} + team: ${user_permission_team} """ ) - assert df['group'][0] == 'enterprise' + assert df['team'][0] == 'enterprise' -def test_user_group_variable_if(monkeypatch): +def test_user_permission_team_variable_if(monkeypatch): """ - Test that user_group can be used in Python-style if conditions. + Test that user_permission_team can be used in Python-style if conditions. """ - monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "enterprise") + monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "enterprise") df = wrangles.recipe.run( """ @@ -403,18 +403,18 @@ def test_user_group_variable_if(monkeypatch): - create.column: output: allowed value: true - if: user_group == 'enterprise' + if: user_permission_team == 'enterprise' """ ) assert df['allowed'][0] == True -def test_user_group_variable_user_override(monkeypatch): +def test_user_permission_team_variable_user_override(monkeypatch): """ - Test that explicit variables still override the authenticated user group. + Test that explicit variables still override the authenticated permission team. """ - monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "enterprise") + monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "enterprise") df = wrangles.recipe.run( """ @@ -422,26 +422,26 @@ def test_user_group_variable_user_override(monkeypatch): - test: rows: 1 values: - group: ${user_group} + team: ${user_permission_team} """, - variables={"user_group": "manual"} + variables={"user_permission_team": "manual"} ) - assert df['group'][0] == 'manual' + assert df['team'][0] == 'manual' -def test_user_group_variable_from_recipe_metadata(monkeypatch): +def test_user_permission_team_variable_from_recipe_metadata(monkeypatch): """ - Test that recipe metadata permission group is preferred for remote recipes. + Test that recipe metadata permission team is preferred for remote recipes. """ - monkeypatch.setattr(wrangles.auth, "get_user_group", lambda: "token-group") + monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "token-team") monkeypatch.setattr( wrangles.recipe._data, "model", lambda model_id: { "purpose": "recipe", "production_version_id": "v1", - "user_group": "metadata-group", + "user_permission_team": "metadata-team", } ) monkeypatch.setattr( @@ -453,11 +453,11 @@ def test_user_group_variable_from_recipe_metadata(monkeypatch): - test: rows: 1 values: - group: ${user_group} + team: ${user_permission_team} """ } ) df = wrangles.recipe.run("12345678-1234-1234") - assert df["group"][0] == "metadata-group" + assert df["team"][0] == "metadata-team" diff --git a/wrangles/auth.py b/wrangles/auth.py index a08ff7eb1..4e91928e5 100644 --- a/wrangles/auth.py +++ b/wrangles/auth.py @@ -86,21 +86,21 @@ def get_access_token(): return _access_token -def extract_user_group(source: dict): +def extract_user_permission_team(source: dict): """ - Extract the permission-driving user group from a metadata or token payload. + Extract the permission-driving user team from a metadata or token payload. """ if not isinstance(source, dict): return None - return source.get("user_group") + return source.get("user_permission_team") -def get_user_group(): +def get_user_permission_team(): """ - Return the authenticated user's permission-driving group from the current access token. + Return the authenticated user's permission-driving team from the current access token. - If no user is authenticated or the token does not contain user_group, + If no user is authenticated or the token does not contain user_permission_team, return None so recipes can still run without backend credentials. """ try: @@ -113,4 +113,4 @@ def get_user_group(): except Exception: return None - return extract_user_group(claims) + return extract_user_permission_team(claims) diff --git a/wrangles/recipe.py b/wrangles/recipe.py index dcf449466..ac7d02139 100644 --- a/wrangles/recipe.py +++ b/wrangles/recipe.py @@ -78,8 +78,8 @@ def _load_recipe( variables = {} user_variable_keys = set(variables.keys()) - if "user_group" not in variables: - variables["user_group"] = _auth.get_user_group() + if "user_permission_team" not in variables: + variables["user_permission_team"] = _auth.get_user_permission_team() # Accept path-like objects (e.g. pathlib.Path) by converting to str if isinstance(recipe, _os.PathLike): @@ -127,10 +127,10 @@ def _load_recipe( if metadata.get('message', None) == 'error': raise ValueError('Incorrect model_id.\nmodel_id may be wrong or does not exists') - metadata_user_group = _auth.extract_user_group(metadata) - if metadata_user_group is not None: - if "user_group" not in user_variable_keys: - variables["user_group"] = metadata_user_group + metadata_user_permission_team = _auth.extract_user_permission_team(metadata) + if metadata_user_permission_team is not None: + if "user_permission_team" not in user_variable_keys: + variables["user_permission_team"] = metadata_user_permission_team # Using model_id in wrong function purpose = metadata['purpose'] From 837e2934edc58fd199e3da68cb18abcd6ddb3706 Mon Sep 17 00:00:00 2001 From: Eric Hills <53243273+ebhills@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:46:18 -0500 Subject: [PATCH 3/7] Test with more robust fallback Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/recipes/test_variables.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/recipes/test_variables.py b/tests/recipes/test_variables.py index b9091aa1e..474b8f969 100644 --- a/tests/recipes/test_variables.py +++ b/tests/recipes/test_variables.py @@ -371,7 +371,12 @@ def test_user_permission_team_variable(monkeypatch): """ Test that the authenticated user's permission team is available as a recipe variable. """ - monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "enterprise") + token = wrangles.auth._jwt.encode( + {"user_permission_team": "enterprise"}, + "test-secret", + algorithm="HS256" + ) + monkeypatch.setattr(wrangles.auth, "get_access_token", lambda: token) df = wrangles.recipe.run( """ From 94a3e4988d02713ec17e6844b4c582cc290eb40f Mon Sep 17 00:00:00 2001 From: mborodii-prog Date: Fri, 14 Aug 2026 20:47:12 +0000 Subject: [PATCH 4/7] Fix recipe variable leakage across runs Co-authored-by: ebhills <53243273+ebhills@users.noreply.github.com> --- wrangles/recipe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/wrangles/recipe.py b/wrangles/recipe.py index ac7d02139..89d0ca9e2 100644 --- a/wrangles/recipe.py +++ b/wrangles/recipe.py @@ -1229,6 +1229,7 @@ def run( """ if variables is None: variables = {} + variables = variables.copy() parent_context = _RECIPE_RUN_CONTEXT.get() run_context = { From 3d9d5c6403fb3fae5d390b669c3a8dc034c6c3c7 Mon Sep 17 00:00:00 2001 From: mborodii-prog Date: Fri, 14 Aug 2026 21:11:02 +0000 Subject: [PATCH 5/7] Rename permission group variable to applied_permission_group Co-authored-by: ebhills <53243273+ebhills@users.noreply.github.com> --- tests/recipes/test_variables.py | 42 ++++++++++++++++----------------- wrangles/auth.py | 25 +++++++++++++++----- wrangles/recipe.py | 16 ++++++++----- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/tests/recipes/test_variables.py b/tests/recipes/test_variables.py index 474b8f969..d4cebfcb5 100644 --- a/tests/recipes/test_variables.py +++ b/tests/recipes/test_variables.py @@ -367,12 +367,12 @@ def test_variables_variable_overwrite(): assert isinstance(df['vars'][0], dict) -def test_user_permission_team_variable(monkeypatch): +def test_applied_permission_group_variable(monkeypatch): """ - Test that the authenticated user's permission team is available as a recipe variable. + Test that the authenticated user's effective permission group is available as a recipe variable. """ token = wrangles.auth._jwt.encode( - {"user_permission_team": "enterprise"}, + {"applied_permission_group": "enterprise"}, "test-secret", algorithm="HS256" ) @@ -384,18 +384,18 @@ def test_user_permission_team_variable(monkeypatch): - test: rows: 1 values: - team: ${user_permission_team} + group: ${applied_permission_group} """ ) - assert df['team'][0] == 'enterprise' + assert df['group'][0] == 'enterprise' -def test_user_permission_team_variable_if(monkeypatch): +def test_applied_permission_group_variable_if(monkeypatch): """ - Test that user_permission_team can be used in Python-style if conditions. + Test that applied_permission_group can be used in Python-style if conditions. """ - monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "enterprise") + monkeypatch.setattr(wrangles.auth, "get_applied_permission_group", lambda: "enterprise") df = wrangles.recipe.run( """ @@ -408,18 +408,18 @@ def test_user_permission_team_variable_if(monkeypatch): - create.column: output: allowed value: true - if: user_permission_team == 'enterprise' + if: applied_permission_group == 'enterprise' """ ) assert df['allowed'][0] == True -def test_user_permission_team_variable_user_override(monkeypatch): +def test_applied_permission_group_variable_user_override(monkeypatch): """ - Test that explicit variables still override the authenticated permission team. + Test that explicit variables still override the authenticated permission group. """ - monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "enterprise") + monkeypatch.setattr(wrangles.auth, "get_applied_permission_group", lambda: "enterprise") df = wrangles.recipe.run( """ @@ -427,26 +427,26 @@ def test_user_permission_team_variable_user_override(monkeypatch): - test: rows: 1 values: - team: ${user_permission_team} + group: ${applied_permission_group} """, - variables={"user_permission_team": "manual"} + variables={"applied_permission_group": "manual"} ) - assert df['team'][0] == 'manual' + assert df['group'][0] == 'manual' -def test_user_permission_team_variable_from_recipe_metadata(monkeypatch): +def test_applied_permission_group_variable_from_recipe_metadata(monkeypatch): """ - Test that recipe metadata permission team is preferred for remote recipes. + Test that recipe metadata permission group is preferred for remote recipes. """ - monkeypatch.setattr(wrangles.auth, "get_user_permission_team", lambda: "token-team") + monkeypatch.setattr(wrangles.auth, "get_applied_permission_group", lambda: "token-group") monkeypatch.setattr( wrangles.recipe._data, "model", lambda model_id: { "purpose": "recipe", "production_version_id": "v1", - "user_permission_team": "metadata-team", + "applied_permission_group": "metadata-group", } ) monkeypatch.setattr( @@ -458,11 +458,11 @@ def test_user_permission_team_variable_from_recipe_metadata(monkeypatch): - test: rows: 1 values: - team: ${user_permission_team} + group: ${applied_permission_group} """ } ) df = wrangles.recipe.run("12345678-1234-1234") - assert df["team"][0] == "metadata-team" + assert df["group"][0] == "metadata-group" diff --git a/wrangles/auth.py b/wrangles/auth.py index 4e91928e5..1f386f15c 100644 --- a/wrangles/auth.py +++ b/wrangles/auth.py @@ -86,21 +86,29 @@ def get_access_token(): return _access_token -def extract_user_permission_team(source: dict): +def extract_applied_permission_group(source: dict): """ - Extract the permission-driving user team from a metadata or token payload. + Extract the effective permission group from a metadata or token payload. """ if not isinstance(source, dict): return None + if "applied_permission_group" in source: + return source.get("applied_permission_group") + return source.get("user_permission_team") -def get_user_permission_team(): +def extract_user_permission_team(source: dict): + """Backward-compatible alias for extract_applied_permission_group.""" + return extract_applied_permission_group(source) + + +def get_applied_permission_group(): """ - Return the authenticated user's permission-driving team from the current access token. + Return the authenticated user's effective permission group from the current access token. - If no user is authenticated or the token does not contain user_permission_team, + If no user is authenticated or the token does not contain the claim, return None so recipes can still run without backend credentials. """ try: @@ -113,4 +121,9 @@ def get_user_permission_team(): except Exception: return None - return extract_user_permission_team(claims) + return extract_applied_permission_group(claims) + + +def get_user_permission_team(): + """Backward-compatible alias for get_applied_permission_group.""" + return get_applied_permission_group() diff --git a/wrangles/recipe.py b/wrangles/recipe.py index 89d0ca9e2..b547e8a14 100644 --- a/wrangles/recipe.py +++ b/wrangles/recipe.py @@ -76,10 +76,14 @@ def _load_recipe( """ if variables is None: variables = {} + + if "applied_permission_group" not in variables and "user_permission_team" in variables: + variables["applied_permission_group"] = variables["user_permission_team"] + user_variable_keys = set(variables.keys()) - if "user_permission_team" not in variables: - variables["user_permission_team"] = _auth.get_user_permission_team() + if "applied_permission_group" not in variables: + variables["applied_permission_group"] = _auth.get_applied_permission_group() # Accept path-like objects (e.g. pathlib.Path) by converting to str if isinstance(recipe, _os.PathLike): @@ -127,10 +131,10 @@ def _load_recipe( if metadata.get('message', None) == 'error': raise ValueError('Incorrect model_id.\nmodel_id may be wrong or does not exists') - metadata_user_permission_team = _auth.extract_user_permission_team(metadata) - if metadata_user_permission_team is not None: - if "user_permission_team" not in user_variable_keys: - variables["user_permission_team"] = metadata_user_permission_team + metadata_applied_permission_group = _auth.extract_applied_permission_group(metadata) + if metadata_applied_permission_group is not None: + if "applied_permission_group" not in user_variable_keys and "user_permission_team" not in user_variable_keys: + variables["applied_permission_group"] = metadata_applied_permission_group # Using model_id in wrong function purpose = metadata['purpose'] From c519d398e53b74bd3a42c4a50eca5513688eb9b4 Mon Sep 17 00:00:00 2001 From: mborodii-prog Date: Tue, 25 Aug 2026 11:31:26 +0300 Subject: [PATCH 6/7] Remove user_permission_team backward compatibility --- wrangles/auth.py | 15 +-------------- wrangles/recipe.py | 5 +---- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/wrangles/auth.py b/wrangles/auth.py index 1f386f15c..ffda8510d 100644 --- a/wrangles/auth.py +++ b/wrangles/auth.py @@ -93,15 +93,7 @@ def extract_applied_permission_group(source: dict): if not isinstance(source, dict): return None - if "applied_permission_group" in source: - return source.get("applied_permission_group") - - return source.get("user_permission_team") - - -def extract_user_permission_team(source: dict): - """Backward-compatible alias for extract_applied_permission_group.""" - return extract_applied_permission_group(source) + return source.get("applied_permission_group") def get_applied_permission_group(): @@ -122,8 +114,3 @@ def get_applied_permission_group(): return None return extract_applied_permission_group(claims) - - -def get_user_permission_team(): - """Backward-compatible alias for get_applied_permission_group.""" - return get_applied_permission_group() diff --git a/wrangles/recipe.py b/wrangles/recipe.py index b547e8a14..c35f19e49 100644 --- a/wrangles/recipe.py +++ b/wrangles/recipe.py @@ -77,9 +77,6 @@ def _load_recipe( if variables is None: variables = {} - if "applied_permission_group" not in variables and "user_permission_team" in variables: - variables["applied_permission_group"] = variables["user_permission_team"] - user_variable_keys = set(variables.keys()) if "applied_permission_group" not in variables: @@ -133,7 +130,7 @@ def _load_recipe( metadata_applied_permission_group = _auth.extract_applied_permission_group(metadata) if metadata_applied_permission_group is not None: - if "applied_permission_group" not in user_variable_keys and "user_permission_team" not in user_variable_keys: + if "applied_permission_group" not in user_variable_keys: variables["applied_permission_group"] = metadata_applied_permission_group # Using model_id in wrong function From 66912b34303888f4adbab234ce45f866a98a374f Mon Sep 17 00:00:00 2001 From: mborodii-prog Date: Tue, 25 Aug 2026 11:56:45 +0300 Subject: [PATCH 7/7] Update test_recipes.py --- tests/recipes/test_recipes.py | 72 +++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/tests/recipes/test_recipes.py b/tests/recipes/test_recipes.py index b2e09ef59..380972620 100644 --- a/tests/recipes/test_recipes.py +++ b/tests/recipes/test_recipes.py @@ -127,11 +127,36 @@ def test_recipe_by_version_tag(): ) -def test_recipe_by_production_version(): +def test_recipe_by_production_version(mocker): """ Test running a recipe using a model ID and production version """ + mocker.patch( + "wrangles.data.model", + return_value={ + "purpose": "recipe", + "production_version_id": "production-version-id" + } + ) + model_content = mocker.patch( + "wrangles.data.model_content", + return_value={ + "recipe": """ + read: + - test: + rows: 20 + values: + header: value1 + """ + } + ) + df = wrangles.recipe.run("a6bac9e7-2388-4347") + + model_content.assert_called_once_with( + "a6bac9e7-2388-4347", + "production-version-id" + ) assert ( len(df) == 20 and list(df.columns) == ["header"] @@ -184,21 +209,62 @@ def test_recipe_by_production_semantic_version_falls_back_to_latest( assert "No production version exists, defaulting to latest version" in caplog.text -def test_recipe_by_version_latest(): +def test_recipe_by_version_latest(mocker): """ Test running a recipe using a model ID and latest version """ + mocker.patch( + "wrangles.data.model", + return_value={ + "purpose": "recipe", + "production_version_id": "production-version-id" + } + ) + model_content = mocker.patch( + "wrangles.data.model_content", + return_value={ + "recipe": """ + read: + - test: + rows: 10 + values: + header: value1 + """ + } + ) + df = wrangles.recipe.run("a6bac9e7-2388-4347:latest") + + model_content.assert_called_once_with("a6bac9e7-2388-4347", None) assert ( len(df) == 10 and list(df.columns) == ["header"] ) -def test_recipe_by_latest_version(): +def test_recipe_by_latest_version(mocker): """ Test running a recipe using a model ID and latest version """ + mocker.patch( + "wrangles.data.model", + return_value={"purpose": "recipe"} + ) + model_content = mocker.patch( + "wrangles.data.model_content", + return_value={ + "recipe": """ + read: + - test: + rows: 15 + values: + header: value1 + """ + } + ) + df = wrangles.recipe.run("02fc0c63-1294-415b") + + model_content.assert_called_once_with("02fc0c63-1294-415b", None) assert ( len(df) == 15 and list(df.columns) == ["header"]