Skip to content

Commit a7c9ccf

Browse files
refactor(cloud): Add CloudOrganization class and get_organization method (#905)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent a4fa901 commit a7c9ccf

File tree

2 files changed

+91
-16
lines changed

2 files changed

+91
-16
lines changed

airbyte/cloud/workspaces.py

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from dataclasses import dataclass
3939
from functools import cached_property
4040
from pathlib import Path
41-
from typing import TYPE_CHECKING, Any, Literal
41+
from typing import TYPE_CHECKING, Any, Literal, overload
4242

4343
import yaml
4444

@@ -52,6 +52,7 @@
5252
CustomCloudSourceDefinition,
5353
)
5454
from airbyte.destinations.base import Destination
55+
from airbyte.exceptions import AirbyteError
5556
from airbyte.secrets.base import SecretString
5657

5758

@@ -61,6 +62,20 @@
6162
from airbyte.sources.base import Source
6263

6364

65+
@dataclass
66+
class CloudOrganization:
67+
"""Information about an organization in Airbyte Cloud.
68+
69+
This is a minimal value object returned by CloudWorkspace.get_organization().
70+
"""
71+
72+
organization_id: str
73+
"""The organization ID."""
74+
75+
organization_name: str | None = None
76+
"""Display name of the organization."""
77+
78+
6479
@dataclass
6580
class CloudWorkspace:
6681
"""A remote workspace on the Airbyte Cloud.
@@ -89,6 +104,7 @@ def _organization_info(self) -> dict[str, Any]:
89104
"""Fetch and cache organization info for this workspace.
90105
91106
Uses the Config API endpoint for an efficient O(1) lookup.
107+
This is an internal method; use get_organization() for public access.
92108
"""
93109
return api_util.get_workspace_organization_info(
94110
workspace_id=self.workspace_id,
@@ -97,21 +113,71 @@ def _organization_info(self) -> dict[str, Any]:
97113
client_secret=self.client_secret,
98114
)
99115

100-
@property
101-
def organization_id(self) -> str | None:
102-
"""The ID of the organization this workspace belongs to.
116+
@overload
117+
def get_organization(self) -> CloudOrganization: ...
103118

104-
This value is cached after the first lookup.
105-
"""
106-
return self._organization_info.get("organizationId")
119+
@overload
120+
def get_organization(
121+
self,
122+
*,
123+
raise_on_error: Literal[True],
124+
) -> CloudOrganization: ...
107125

108-
@property
109-
def organization_name(self) -> str | None:
110-
"""The name of the organization this workspace belongs to.
126+
@overload
127+
def get_organization(
128+
self,
129+
*,
130+
raise_on_error: Literal[False],
131+
) -> CloudOrganization | None: ...
132+
133+
def get_organization(
134+
self,
135+
*,
136+
raise_on_error: bool = True,
137+
) -> CloudOrganization | None:
138+
"""Get the organization this workspace belongs to.
139+
140+
Fetching organization info requires ORGANIZATION_READER permissions on the organization,
141+
which may not be available with workspace-scoped credentials.
142+
143+
Args:
144+
raise_on_error: If True (default), raises AirbyteError on permission or API errors.
145+
If False, returns None instead of raising.
111146
112-
This value is cached after the first lookup.
147+
Returns:
148+
CloudOrganization object with organization_id and organization_name,
149+
or None if raise_on_error=False and an error occurred.
150+
151+
Raises:
152+
AirbyteError: If raise_on_error=True and the organization info cannot be fetched
153+
(e.g., due to insufficient permissions or missing data).
113154
"""
114-
return self._organization_info.get("organizationName")
155+
try:
156+
info = self._organization_info
157+
except AirbyteError:
158+
if raise_on_error:
159+
raise
160+
return None
161+
162+
organization_id = info.get("organizationId")
163+
organization_name = info.get("organizationName")
164+
165+
# Validate that both organization_id and organization_name are non-null and non-empty
166+
if not organization_id or not organization_name:
167+
if raise_on_error:
168+
raise AirbyteError(
169+
message="Organization info is incomplete.",
170+
context={
171+
"organization_id": organization_id,
172+
"organization_name": organization_name,
173+
},
174+
)
175+
return None
176+
177+
return CloudOrganization(
178+
organization_id=organization_id,
179+
organization_name=organization_name,
180+
)
115181

116182
# Test connection and creds
117183

airbyte/mcp/cloud_ops.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ class CloudWorkspaceResult(BaseModel):
159159
workspace_url: str | None = None
160160
"""URL to access the workspace in Airbyte Cloud."""
161161
organization_id: str
162-
"""ID of the organization this workspace belongs to."""
162+
"""ID of the organization (requires ORGANIZATION_READER permission)."""
163163
organization_name: str | None = None
164-
"""Name of the organization this workspace belongs to."""
164+
"""Name of the organization (requires ORGANIZATION_READER permission)."""
165165

166166

167167
class LogReadResult(BaseModel):
@@ -515,12 +515,21 @@ def check_airbyte_cloud_workspace(
515515
client_secret=client_secret,
516516
)
517517

518+
# Try to get organization info, but fail gracefully if we don't have permissions.
519+
# Fetching organization info requires ORGANIZATION_READER permissions on the organization,
520+
# which may not be available with workspace-scoped credentials.
521+
organization = workspace.get_organization(raise_on_error=False)
522+
518523
return CloudWorkspaceResult(
519524
workspace_id=workspace_response.workspace_id,
520525
workspace_name=workspace_response.name,
521526
workspace_url=workspace.workspace_url,
522-
organization_id=workspace.organization_id or "[error: organization ID not discovered]",
523-
organization_name=workspace.organization_name,
527+
organization_id=(
528+
organization.organization_id
529+
if organization
530+
else "[unavailable - requires ORGANIZATION_READER permission]"
531+
),
532+
organization_name=organization.organization_name if organization else None,
524533
)
525534

526535

0 commit comments

Comments
 (0)