Skip to content

Commit dcbee9c

Browse files
gyermichbilldirks
andauthored
[BUGFIX] Make workspaces optional for cloud_user_info (#11378)
Co-authored-by: Bill Dirks <bill@greatexpectations.io>
1 parent 264b4e0 commit dcbee9c

File tree

3 files changed

+89
-25
lines changed

3 files changed

+89
-25
lines changed

great_expectations/data_context/data_context/cloud_data_context.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,7 @@
7676

7777
class NoUserIdError(Exception):
7878
def __init__(self):
79-
super().__init__("No user id in /account/me response")
80-
81-
82-
class NoWorkspacesError(Exception):
83-
def __init__(self):
84-
super().__init__("No workspaces in /account/me response")
85-
86-
87-
class WorkspacesKeyError(Exception):
88-
def __init__(self):
89-
super().__init__(
90-
"Workspaces returned in /account/me response don't have required keys: (id, role)"
91-
)
79+
super().__init__("No user id in /accounts/me response")
9280

9381

9482
class WorkspaceNotSetError(Exception):
@@ -223,16 +211,11 @@ def _get_cloud_user_info(self) -> CloudUserInfo:
223211
user_id = data.get("user_id") or data.get("id")
224212
if not user_id:
225213
raise NoUserIdError()
226-
response_workspaces = data.get("workspaces")
227-
if not response_workspaces:
228-
raise NoWorkspacesError()
229-
workspaces = []
230-
for response_workspace in response_workspaces:
231-
if "id" not in response_workspace or "role" not in response_workspace:
232-
raise WorkspacesKeyError()
233-
workspaces.append(
234-
Workspace(id=response_workspace["id"], role=response_workspace["role"])
235-
)
214+
response_workspaces = data.get("workspaces", [])
215+
workspaces = [
216+
Workspace(id=response_workspace["id"], role=response_workspace["role"])
217+
for response_workspace in response_workspaces
218+
]
236219
return CloudUserInfo(user_id=uuid.UUID(user_id), workspaces=workspaces)
237220

238221
def cloud_user_info(self, force_refresh: bool = False) -> CloudUserInfo:

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pandas<2.2; python_version >= "3.12"
1515
posthog>3
1616
# patch version updates `typing_extensions` to the needed version
1717
pydantic>=1.10.7
18-
pyparsing>=2.4
18+
pyparsing>=2.4,!=3.2.4
1919
python-dateutil>=2.8.1
2020
requests>=2.20
2121
ruamel.yaml>=0.16

tests/analytics/test_analytics.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22
from unittest import mock
3-
from uuid import UUID
3+
from uuid import UUID, uuid4
44

55
import pytest
66

@@ -317,6 +317,87 @@ def test_analytics_enabled_after_setting_explicitly(
317317
)
318318

319319

320+
@pytest.mark.unit
321+
def test_cloud_context_init_with_system_user_no_workspaces(
322+
unset_gx_env_variables: None,
323+
monkeypatch,
324+
mocker,
325+
):
326+
monkeypatch.setattr(ENV_CONFIG, "gx_analytics_enabled", True) # Enable usage stats
327+
328+
user_id = uuid4()
329+
organization_id = uuid4()
330+
workspace_id = uuid4()
331+
332+
# Mock cloud API response for system user with no workspaces
333+
def mock_request_cloud_backend(*args, **kwargs):
334+
mock_response = mocker.MagicMock()
335+
mock_response.json.return_value = {
336+
"user_id": str(user_id),
337+
"workspaces": [],
338+
}
339+
return mock_response
340+
341+
mock_config = {
342+
"cloud_base_url": "https://api.test.greatexpectations.io",
343+
"cloud_access_token": "test_token_123",
344+
"cloud_organization_id": str(organization_id),
345+
"cloud_workspace_id": str(workspace_id),
346+
}
347+
348+
mock_data_context_config = {
349+
"config_version": 4.0,
350+
"datasources": {},
351+
"stores": {},
352+
"expectations_store_name": "expectations_store",
353+
"validation_results_store_name": "validation_results_store",
354+
"checkpoint_store_name": "checkpoint_store",
355+
"data_docs_sites": {},
356+
"analytics_enabled": True,
357+
}
358+
359+
with (
360+
mock.patch(
361+
"great_expectations.data_context.data_context.cloud_data_context.CloudDataContext.retrieve_data_context_config_from_cloud",
362+
return_value=mock_data_context_config,
363+
),
364+
mock.patch(
365+
"great_expectations.data_context.data_context.cloud_data_context.CloudDataContext._save_project_config"
366+
),
367+
mock.patch(
368+
"great_expectations.data_context.data_context.cloud_data_context.CloudDataContext._check_if_latest_version"
369+
),
370+
mock.patch(
371+
"great_expectations.data_context.data_context.cloud_data_context.CloudDataContext._request_cloud_backend",
372+
side_effect=mock_request_cloud_backend,
373+
),
374+
mock.patch(
375+
"great_expectations.data_context.data_context.cloud_data_context.init_analytics"
376+
) as mock_init,
377+
mock.patch("posthog.capture"),
378+
):
379+
# This should not raise an error for system users with no workspaces
380+
gx.get_context(
381+
mode="cloud",
382+
cloud_base_url=mock_config["cloud_base_url"],
383+
cloud_access_token=mock_config["cloud_access_token"],
384+
cloud_organization_id=mock_config["cloud_organization_id"],
385+
cloud_workspace_id=mock_config["cloud_workspace_id"],
386+
)
387+
388+
# Verify analytics initialization was called with the system user's ID
389+
mock_init.assert_called_once_with(
390+
enable=True,
391+
user_id=user_id,
392+
data_context_id=mock.ANY,
393+
organization_id=organization_id,
394+
oss_id=mock.ANY,
395+
cloud_mode=True,
396+
mode="cloud",
397+
user_agent_str=None,
398+
)
399+
400+
320401
@pytest.mark.parametrize("initial_user_agent_str", [None, "old user agent string"])
321402
@pytest.mark.parametrize("new_user_agent_str", [None, "new user agent string"])
322403
@pytest.mark.unit

0 commit comments

Comments
 (0)