3838from dataclasses import dataclass
3939from functools import cached_property
4040from pathlib import Path
41- from typing import TYPE_CHECKING , Any , Literal
41+ from typing import TYPE_CHECKING , Any , Literal , overload
4242
4343import yaml
4444
5252 CustomCloudSourceDefinition ,
5353)
5454from airbyte .destinations .base import Destination
55+ from airbyte .exceptions import AirbyteError
5556from airbyte .secrets .base import SecretString
5657
5758
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
6580class 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
0 commit comments