|
24 | 24 |
|
25 | 25 | from airbyte.constants import CLOUD_API_ROOT, CLOUD_CONFIG_API_ROOT, CLOUD_CONFIG_API_ROOT_ENV_VAR |
26 | 26 | from airbyte.exceptions import ( |
| 27 | + AirbyteConnectionSyncActiveError, |
27 | 28 | AirbyteConnectionSyncError, |
28 | 29 | AirbyteError, |
29 | 30 | AirbyteMissingResourceError, |
@@ -2092,6 +2093,70 @@ def get_connection_state( |
2092 | 2093 | ) |
2093 | 2094 |
|
2094 | 2095 |
|
| 2096 | +def replace_connection_state( |
| 2097 | + connection_id: str, |
| 2098 | + connection_state_dict: dict[str, Any], |
| 2099 | + *, |
| 2100 | + api_root: str, |
| 2101 | + client_id: SecretString | None, |
| 2102 | + client_secret: SecretString | None, |
| 2103 | + bearer_token: SecretString | None, |
| 2104 | +) -> dict[str, Any]: |
| 2105 | + """Replace the state for a connection. |
| 2106 | +
|
| 2107 | + Uses the Config API endpoint: POST /v1/state/create_or_update_safe |
| 2108 | +
|
| 2109 | + Returns HTTP 423 if a sync is currently running, preventing state |
| 2110 | + corruption from concurrent modifications. |
| 2111 | +
|
| 2112 | + Important: This endpoint replaces the ENTIRE connection state. It does not |
| 2113 | + perform per-stream deduplication or merging on the backend. Callers that need |
| 2114 | + to update a single stream must first fetch the current state, merge the desired |
| 2115 | + stream change into the full state object, and then send the complete state back. |
| 2116 | + See ``CloudConnection.set_stream_state()`` for this fetch-modify-push pattern. |
| 2117 | +
|
| 2118 | + The provided ``connection_id`` is injected into both the outer request |
| 2119 | + wrapper and the inner ``connection_state`` payload to ensure consistency. |
| 2120 | +
|
| 2121 | + Args: |
| 2122 | + connection_id: The connection ID to update state for. |
| 2123 | + connection_state_dict: The full ConnectionState dict to set. Must include: |
| 2124 | + - stateType: "global", "stream", or "legacy" |
| 2125 | + - One of: state (legacy), streamState (stream), globalState (global) |
| 2126 | + All streams must be included; any stream omitted will have its state dropped. |
| 2127 | + api_root: The API root URL. |
| 2128 | + client_id: OAuth client ID. |
| 2129 | + client_secret: OAuth client secret. |
| 2130 | + bearer_token: Bearer token for authentication (alternative to client credentials). |
| 2131 | +
|
| 2132 | + Returns: |
| 2133 | + Dictionary containing the updated ConnectionState object. |
| 2134 | + """ |
| 2135 | + try: |
| 2136 | + return _make_config_api_request( |
| 2137 | + path="/state/create_or_update_safe", |
| 2138 | + json={ |
| 2139 | + "connectionId": connection_id, |
| 2140 | + "connectionState": { |
| 2141 | + **connection_state_dict, |
| 2142 | + "connectionId": connection_id, |
| 2143 | + }, |
| 2144 | + }, |
| 2145 | + api_root=api_root, |
| 2146 | + client_id=client_id, |
| 2147 | + client_secret=client_secret, |
| 2148 | + bearer_token=bearer_token, |
| 2149 | + ) |
| 2150 | + except AirbyteError as ex: |
| 2151 | + if ex.context and ex.context.get("status_code") == HTTPStatus.LOCKED: |
| 2152 | + raise AirbyteConnectionSyncActiveError( |
| 2153 | + message="Cannot update connection state while a sync is running.", |
| 2154 | + connection_id=connection_id, |
| 2155 | + guidance="Wait for the current sync to complete before updating state.", |
| 2156 | + ) from ex |
| 2157 | + raise |
| 2158 | + |
| 2159 | + |
2095 | 2160 | def get_connection_catalog( |
2096 | 2161 | connection_id: str, |
2097 | 2162 | *, |
@@ -2127,6 +2192,49 @@ def get_connection_catalog( |
2127 | 2192 | ) |
2128 | 2193 |
|
2129 | 2194 |
|
| 2195 | +def replace_connection_catalog( |
| 2196 | + connection_id: str, |
| 2197 | + configured_catalog_dict: dict[str, Any], |
| 2198 | + *, |
| 2199 | + api_root: str, |
| 2200 | + client_id: SecretString | None, |
| 2201 | + client_secret: SecretString | None, |
| 2202 | + bearer_token: SecretString | None, |
| 2203 | +) -> dict[str, Any]: |
| 2204 | + """Replace the configured catalog for a connection. |
| 2205 | +
|
| 2206 | + Uses the Config API endpoint: POST /v1/web_backend/connections/update |
| 2207 | +
|
| 2208 | + This is a patch-style update that replaces the connection's entire syncCatalog |
| 2209 | + with the provided catalog. All other connection settings remain unchanged. |
| 2210 | +
|
| 2211 | + Args: |
| 2212 | + connection_id: The connection ID to update catalog for. |
| 2213 | + configured_catalog_dict: The configured catalog dict (``{"streams": [...]}``) to set. |
| 2214 | + api_root: The API root URL. |
| 2215 | + client_id: OAuth client ID. |
| 2216 | + client_secret: OAuth client secret. |
| 2217 | + bearer_token: Bearer token for authentication (alternative to client credentials). |
| 2218 | +
|
| 2219 | + Returns: |
| 2220 | + Dictionary containing the updated WebBackendConnectionRead response. |
| 2221 | + """ |
| 2222 | + return _make_config_api_request( |
| 2223 | + path="/web_backend/connections/update", |
| 2224 | + json={ |
| 2225 | + "connectionId": connection_id, |
| 2226 | + "syncCatalog": configured_catalog_dict, |
| 2227 | + # Resets are destructive and cause customer-side data outage. |
| 2228 | + # If a reset is desired, caller will need to decide & manage. |
| 2229 | + "skipReset": True, |
| 2230 | + }, |
| 2231 | + api_root=api_root, |
| 2232 | + client_id=client_id, |
| 2233 | + client_secret=client_secret, |
| 2234 | + bearer_token=bearer_token, |
| 2235 | + ) |
| 2236 | + |
| 2237 | + |
2130 | 2238 | def get_organization_info( |
2131 | 2239 | organization_id: str, |
2132 | 2240 | *, |
|
0 commit comments