airbyte.agents.connectors
Airbyte Agents connectors, and the single-action execute interface.
⚠️ Experimental Interface
The Airbyte Agents Python interfaces are experimental. Class names, method signatures, and result models may change or be removed without notice between minor versions of PyAirbyte. Pin an exact PyAirbyte version if you depend on them.
1# Copyright (c) 2026 Airbyte, Inc., all rights reserved. 2"""Airbyte Agents connectors, and the single-action `execute` interface. 3 4> ## ⚠️ Experimental Interface 5> 6> **The Airbyte Agents Python interfaces are experimental.** Class names, method signatures, 7> and result models may change or be removed without notice between minor versions of 8> PyAirbyte. Pin an exact PyAirbyte version if you depend on them. 9""" 10 11from __future__ import annotations 12 13from typing import TYPE_CHECKING, Any, NamedTuple 14 15from airbyte.agents import _api_util 16from airbyte.agents.models import AgentConnectorDetails, AgentExecuteResult 17from airbyte.exceptions import PyAirbyteInputError 18 19 20if TYPE_CHECKING: 21 from collections.abc import Iterator 22 23 from airbyte.cloud._credentials import _AirbyteCredentials 24 25 26UNSUPPORTED_ACTIONS: set[str] = {"download"} 27"""Actions PyAirbyte rejects before sending them to the Agents API. 28 29`download` returns a binary stream rather than JSON, and PyAirbyte does not yet support 30streaming responses, so it is rejected with actionable guidance instead of failing later 31inside the transport layer. 32""" 33 34_PAGINATION_ARGS: dict[str, str] = {"page_size": "limit", "cursor": "cursor"} 35"""Pagination conveniences PyAirbyte merges into the connector's `params`. 36 37Maps the PyAirbyte argument name to the connector's own `params` key: the Agents API calls 38page size `limit`, which PyAirbyte does not expose under that name because `limit` reads as 39a cap on the whole result set rather than on one page. 40""" 41 42 43class _ConnectorLookup(NamedTuple): 44 """What to look a connector up by, once the lookup arguments have been validated. 45 46 Both fields are set when the caller passed a positional value that could be either an 47 ID or a name, in which case an ID match takes precedence over a name match. 48 """ 49 50 connector_id: str | None 51 name: str | None 52 53 54def _resolve_connector_lookup( 55 id_or_name: str | None, 56 /, 57 *, 58 id: str | None, # noqa: A002 # Mirrors the public `id` alias it validates. 59 connector_id: str | None, 60 name: str | None, 61) -> _ConnectorLookup: 62 """Validate connector lookup arguments and return what to look the connector up by. 63 64 `id` and `connector_id` are synonyms, so exactly one of them, `name`, or the positional 65 `id_or_name` is required. Conflicting synonym values are rejected, as is a blank value, 66 which would otherwise be treated as an omitted argument. 67 """ 68 all_args = { 69 "id_or_name": id_or_name, 70 "id": id, 71 "connector_id": connector_id, 72 "name": name, 73 } 74 75 blank_args = sorted( 76 key for key, value in all_args.items() if value is not None and not value.strip() 77 ) 78 if blank_args: 79 raise PyAirbyteInputError( 80 message="Connector lookup arguments cannot be blank.", 81 guidance="Omit the argument entirely, or pass a non-blank value.", 82 context={"blank_args": blank_args}, 83 ) 84 85 if id_or_name: 86 keyword_args = sorted( 87 key for key, value in all_args.items() if value and key != "id_or_name" 88 ) 89 if keyword_args: 90 raise PyAirbyteInputError( 91 message="A positional connector lookup cannot be combined with keyword arguments.", 92 guidance="Pass the value positionally, or pass `id`, `connector_id`, or `name`.", 93 context={"keyword_args": keyword_args}, 94 ) 95 return _ConnectorLookup(connector_id=id_or_name, name=id_or_name) 96 97 provided = { 98 key: value for key, value in {"id": id, "connector_id": connector_id}.items() if value 99 } 100 if len(set(provided.values())) > 1: 101 raise PyAirbyteInputError( 102 message="`id` and `connector_id` were given conflicting values.", 103 guidance="These arguments are synonyms, so pass only one of them.", 104 context={"provided": sorted(provided)}, 105 ) 106 107 if bool(provided) == bool(name): 108 raise PyAirbyteInputError( 109 message="Exactly one connector lookup argument is required.", 110 guidance=( 111 "Pass a connector ID or name positionally, or as `id`, `connector_id`, " 112 "or `name`." 113 ), 114 ) 115 116 return _ConnectorLookup(connector_id=next(iter(provided.values()), None), name=name) 117 118 119class AgentConnector: 120 """A connector in an Airbyte Agents workspace. 121 122 Get one from `AgentWorkspace.get_connector()` rather than constructing it directly. 123 124 ```python 125 from airbyte import agents 126 127 workspace = agents.AgentWorkspace.from_env() 128 connector = workspace.get_connector("GitHub") # by ID or name (case insensitive) 129 result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"}) 130 for entity in result.entities: 131 print(entity["title"]) 132 ``` 133 """ 134 135 def __init__( 136 self, 137 connector_id: str, 138 *, 139 credentials: _AirbyteCredentials, 140 name: str | None = None, 141 ) -> None: 142 """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`.""" 143 self.connector_id = connector_id 144 """The connector ID.""" 145 146 self._credentials = credentials 147 self._name = name 148 self._details: AgentConnectorDetails | None = None 149 150 @property 151 def name(self) -> str | None: 152 """The connector name, fetched from the Agents API if not already known.""" 153 if self._name is None: 154 self._name = self.describe().name 155 return self._name 156 157 def describe(self, *, force_refresh: bool = False) -> AgentConnectorDetails: 158 """Return connector metadata from the Agents API `inspect` endpoint. 159 160 The result is cached; pass `force_refresh=True` to fetch it again. 161 """ 162 if self._details is None or force_refresh: 163 self._details = AgentConnectorDetails.model_validate( 164 _api_util.inspect_agent_connector( 165 connector_id=self.connector_id, 166 credentials=self._credentials, 167 organization_id=self._credentials.organization_id, 168 ) 169 ) 170 self._name = self._details.name or self._name 171 return self._details 172 173 def execute( # noqa: PLR0913 # Explicit args are the point of this public API. 174 self, 175 entity_type: str, 176 action: str, 177 api_args: dict[str, Any] | None = None, 178 *, 179 select_fields: list[str] | None = None, 180 exclude_fields: list[str] | None = None, 181 page_size: int | None = None, 182 cursor: str | None = None, 183 skip_truncation: bool = True, 184 intent: str | None = None, 185 ) -> AgentExecuteResult: 186 """Execute a single action against one entity type on this connector. 187 188 `entity_type` and `action` are connector-specific, for example `issues` and `list`. 189 Use `describe()` to see what a connector supports. 190 191 `api_args` holds connector-specific arguments passed through to the connector, for 192 example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted 193 by PyAirbyte or by the Agents API itself: 194 195 - `select_fields` and `exclude_fields` prune fields from returned entities. 196 - `page_size` and `cursor` are merged into `api_args` as pagination arguments, where 197 the connector receives `page_size` as its own `limit`. Pass the `end_cursor` of a 198 previous result as `cursor` to fetch the next page. 199 - `skip_truncation` disables the Agents API's default truncation of large payloads. 200 - `intent` is a free-text description of why the action is being run, which some 201 connectors use to refine results. 202 203 The `download` action is rejected, because it returns a binary stream and PyAirbyte 204 does not yet support streaming responses. 205 """ 206 if action in UNSUPPORTED_ACTIONS: 207 raise PyAirbyteInputError( 208 message=f"The {action!r} action is not supported by PyAirbyte.", 209 guidance=( 210 "This action returns a binary stream instead of JSON, and PyAirbyte does " 211 "not yet support streaming responses." 212 ), 213 context={"entity_type": entity_type, "action": action}, 214 ) 215 216 request_body: dict[str, Any] = { 217 "entity": entity_type, 218 "action": action, 219 "params": _build_params(api_args=api_args, page_size=page_size, cursor=cursor), 220 "skip_truncation": skip_truncation, 221 } 222 if select_fields is not None: 223 request_body["select_fields"] = select_fields 224 if exclude_fields is not None: 225 request_body["exclude_fields"] = exclude_fields 226 if intent is not None: 227 request_body["intent"] = intent 228 229 return AgentExecuteResult.model_validate( 230 _api_util.execute_agent_connector_action( 231 connector_id=self.connector_id, 232 request_body=request_body, 233 credentials=self._credentials, 234 organization_id=self._credentials.organization_id, 235 ) 236 ) 237 238 def list_entities( 239 self, 240 entity_type: str, 241 api_args: dict[str, Any] | None = None, 242 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 243 ) -> AgentExecuteResult: 244 """Run the `list` action, which returns a page of entities of `entity_type`.""" 245 return self.execute(entity_type, "list", api_args, **kwargs) 246 247 def iter_entities( 248 self, 249 entity_type: str, 250 api_args: dict[str, Any] | None = None, 251 *, 252 limit: int | None = None, 253 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `list_entities()`. 254 ) -> Iterator[dict[str, Any]]: 255 """Yield entities of `entity_type`, following the connector's pagination cursor. 256 257 This is the pagination-free way to read entities: each page is fetched lazily as 258 the caller iterates, so no cursor bookkeeping is needed. 259 260 ```python 261 for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}): 262 print(issue["title"]) 263 ``` 264 265 `limit` caps how many entities are yielded in total, which matters for entity types 266 with no natural end. Pass `page_size` to control how many are fetched per request. 267 268 Iteration stops early if the connector reports another page without advancing its 269 cursor, rather than requesting the same page forever. 270 271 Use `list_entities()` instead when a single page is enough, or when the result's 272 `status`, `warning`, or `execution_metadata` are needed. 273 """ 274 cursor: str | None = kwargs.pop("cursor", None) 275 seen_cursors: set[str] = set() 276 yielded = 0 277 278 while True: 279 result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs) 280 for agent_entity in result.entities: 281 yield agent_entity 282 yielded += 1 283 if limit is not None and yielded >= limit: 284 return 285 286 cursor = result.end_cursor 287 if not result.has_next_page or cursor is None or cursor in seen_cursors: 288 return 289 seen_cursors.add(cursor) 290 291 def search_entities( 292 self, 293 entity_type: str, 294 api_args: dict[str, Any] | None = None, 295 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 296 ) -> AgentExecuteResult: 297 """Run the `search` action, which returns matching entities of `entity_type`.""" 298 return self.execute(entity_type, "search", api_args, **kwargs) 299 300 def get_entity( 301 self, 302 entity_type: str, 303 api_args: dict[str, Any] | None = None, 304 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 305 ) -> AgentExecuteResult: 306 """Run the `get` action, which returns a single entity of `entity_type`.""" 307 return self.execute(entity_type, "get", api_args, **kwargs) 308 309 def create_entity( 310 self, 311 entity_type: str, 312 api_args: dict[str, Any] | None = None, 313 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 314 ) -> AgentExecuteResult: 315 """Run the `create` action, which creates an entity of `entity_type`.""" 316 return self.execute(entity_type, "create", api_args, **kwargs) 317 318 def update_entity( 319 self, 320 entity_type: str, 321 api_args: dict[str, Any] | None = None, 322 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 323 ) -> AgentExecuteResult: 324 """Run the `update` action, which updates an entity of `entity_type`.""" 325 return self.execute(entity_type, "update", api_args, **kwargs) 326 327 def delete_entity( 328 self, 329 entity_type: str, 330 api_args: dict[str, Any] | None = None, 331 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 332 ) -> AgentExecuteResult: 333 """Run the `delete` action, which deletes an entity of `entity_type`.""" 334 return self.execute(entity_type, "delete", api_args, **kwargs) 335 336 337def _build_params( 338 *, 339 api_args: dict[str, Any] | None, 340 page_size: int | None, 341 cursor: str | None, 342) -> dict[str, Any]: 343 """Merge the pagination conveniences into the connector-specific `api_args`.""" 344 params: dict[str, Any] = dict(api_args or {}) 345 pagination: dict[str, Any] = {"page_size": page_size, "cursor": cursor} 346 347 conflicts = sorted( 348 name 349 for name, param_key in _PAGINATION_ARGS.items() 350 if pagination[name] is not None and param_key in params 351 ) 352 if conflicts: 353 raise PyAirbyteInputError( 354 message="Pagination arguments were provided twice.", 355 guidance=( 356 "Pass each of `page_size` and `cursor` either as a keyword argument or " 357 "within `api_args`, but not both. Note that `page_size` is sent to the " 358 "connector as `limit`." 359 ), 360 context={"duplicated_args": conflicts}, 361 ) 362 363 params.update( 364 { 365 param_key: pagination[name] 366 for name, param_key in _PAGINATION_ARGS.items() 367 if pagination[name] is not None 368 } 369 ) 370 return params
Actions PyAirbyte rejects before sending them to the Agents API.
download returns a binary stream rather than JSON, and PyAirbyte does not yet support
streaming responses, so it is rejected with actionable guidance instead of failing later
inside the transport layer.
120class AgentConnector: 121 """A connector in an Airbyte Agents workspace. 122 123 Get one from `AgentWorkspace.get_connector()` rather than constructing it directly. 124 125 ```python 126 from airbyte import agents 127 128 workspace = agents.AgentWorkspace.from_env() 129 connector = workspace.get_connector("GitHub") # by ID or name (case insensitive) 130 result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"}) 131 for entity in result.entities: 132 print(entity["title"]) 133 ``` 134 """ 135 136 def __init__( 137 self, 138 connector_id: str, 139 *, 140 credentials: _AirbyteCredentials, 141 name: str | None = None, 142 ) -> None: 143 """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`.""" 144 self.connector_id = connector_id 145 """The connector ID.""" 146 147 self._credentials = credentials 148 self._name = name 149 self._details: AgentConnectorDetails | None = None 150 151 @property 152 def name(self) -> str | None: 153 """The connector name, fetched from the Agents API if not already known.""" 154 if self._name is None: 155 self._name = self.describe().name 156 return self._name 157 158 def describe(self, *, force_refresh: bool = False) -> AgentConnectorDetails: 159 """Return connector metadata from the Agents API `inspect` endpoint. 160 161 The result is cached; pass `force_refresh=True` to fetch it again. 162 """ 163 if self._details is None or force_refresh: 164 self._details = AgentConnectorDetails.model_validate( 165 _api_util.inspect_agent_connector( 166 connector_id=self.connector_id, 167 credentials=self._credentials, 168 organization_id=self._credentials.organization_id, 169 ) 170 ) 171 self._name = self._details.name or self._name 172 return self._details 173 174 def execute( # noqa: PLR0913 # Explicit args are the point of this public API. 175 self, 176 entity_type: str, 177 action: str, 178 api_args: dict[str, Any] | None = None, 179 *, 180 select_fields: list[str] | None = None, 181 exclude_fields: list[str] | None = None, 182 page_size: int | None = None, 183 cursor: str | None = None, 184 skip_truncation: bool = True, 185 intent: str | None = None, 186 ) -> AgentExecuteResult: 187 """Execute a single action against one entity type on this connector. 188 189 `entity_type` and `action` are connector-specific, for example `issues` and `list`. 190 Use `describe()` to see what a connector supports. 191 192 `api_args` holds connector-specific arguments passed through to the connector, for 193 example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted 194 by PyAirbyte or by the Agents API itself: 195 196 - `select_fields` and `exclude_fields` prune fields from returned entities. 197 - `page_size` and `cursor` are merged into `api_args` as pagination arguments, where 198 the connector receives `page_size` as its own `limit`. Pass the `end_cursor` of a 199 previous result as `cursor` to fetch the next page. 200 - `skip_truncation` disables the Agents API's default truncation of large payloads. 201 - `intent` is a free-text description of why the action is being run, which some 202 connectors use to refine results. 203 204 The `download` action is rejected, because it returns a binary stream and PyAirbyte 205 does not yet support streaming responses. 206 """ 207 if action in UNSUPPORTED_ACTIONS: 208 raise PyAirbyteInputError( 209 message=f"The {action!r} action is not supported by PyAirbyte.", 210 guidance=( 211 "This action returns a binary stream instead of JSON, and PyAirbyte does " 212 "not yet support streaming responses." 213 ), 214 context={"entity_type": entity_type, "action": action}, 215 ) 216 217 request_body: dict[str, Any] = { 218 "entity": entity_type, 219 "action": action, 220 "params": _build_params(api_args=api_args, page_size=page_size, cursor=cursor), 221 "skip_truncation": skip_truncation, 222 } 223 if select_fields is not None: 224 request_body["select_fields"] = select_fields 225 if exclude_fields is not None: 226 request_body["exclude_fields"] = exclude_fields 227 if intent is not None: 228 request_body["intent"] = intent 229 230 return AgentExecuteResult.model_validate( 231 _api_util.execute_agent_connector_action( 232 connector_id=self.connector_id, 233 request_body=request_body, 234 credentials=self._credentials, 235 organization_id=self._credentials.organization_id, 236 ) 237 ) 238 239 def list_entities( 240 self, 241 entity_type: str, 242 api_args: dict[str, Any] | None = None, 243 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 244 ) -> AgentExecuteResult: 245 """Run the `list` action, which returns a page of entities of `entity_type`.""" 246 return self.execute(entity_type, "list", api_args, **kwargs) 247 248 def iter_entities( 249 self, 250 entity_type: str, 251 api_args: dict[str, Any] | None = None, 252 *, 253 limit: int | None = None, 254 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `list_entities()`. 255 ) -> Iterator[dict[str, Any]]: 256 """Yield entities of `entity_type`, following the connector's pagination cursor. 257 258 This is the pagination-free way to read entities: each page is fetched lazily as 259 the caller iterates, so no cursor bookkeeping is needed. 260 261 ```python 262 for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}): 263 print(issue["title"]) 264 ``` 265 266 `limit` caps how many entities are yielded in total, which matters for entity types 267 with no natural end. Pass `page_size` to control how many are fetched per request. 268 269 Iteration stops early if the connector reports another page without advancing its 270 cursor, rather than requesting the same page forever. 271 272 Use `list_entities()` instead when a single page is enough, or when the result's 273 `status`, `warning`, or `execution_metadata` are needed. 274 """ 275 cursor: str | None = kwargs.pop("cursor", None) 276 seen_cursors: set[str] = set() 277 yielded = 0 278 279 while True: 280 result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs) 281 for agent_entity in result.entities: 282 yield agent_entity 283 yielded += 1 284 if limit is not None and yielded >= limit: 285 return 286 287 cursor = result.end_cursor 288 if not result.has_next_page or cursor is None or cursor in seen_cursors: 289 return 290 seen_cursors.add(cursor) 291 292 def search_entities( 293 self, 294 entity_type: str, 295 api_args: dict[str, Any] | None = None, 296 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 297 ) -> AgentExecuteResult: 298 """Run the `search` action, which returns matching entities of `entity_type`.""" 299 return self.execute(entity_type, "search", api_args, **kwargs) 300 301 def get_entity( 302 self, 303 entity_type: str, 304 api_args: dict[str, Any] | None = None, 305 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 306 ) -> AgentExecuteResult: 307 """Run the `get` action, which returns a single entity of `entity_type`.""" 308 return self.execute(entity_type, "get", api_args, **kwargs) 309 310 def create_entity( 311 self, 312 entity_type: str, 313 api_args: dict[str, Any] | None = None, 314 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 315 ) -> AgentExecuteResult: 316 """Run the `create` action, which creates an entity of `entity_type`.""" 317 return self.execute(entity_type, "create", api_args, **kwargs) 318 319 def update_entity( 320 self, 321 entity_type: str, 322 api_args: dict[str, Any] | None = None, 323 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 324 ) -> AgentExecuteResult: 325 """Run the `update` action, which updates an entity of `entity_type`.""" 326 return self.execute(entity_type, "update", api_args, **kwargs) 327 328 def delete_entity( 329 self, 330 entity_type: str, 331 api_args: dict[str, Any] | None = None, 332 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 333 ) -> AgentExecuteResult: 334 """Run the `delete` action, which deletes an entity of `entity_type`.""" 335 return self.execute(entity_type, "delete", api_args, **kwargs)
A connector in an Airbyte Agents workspace.
Get one from AgentWorkspace.get_connector() rather than constructing it directly.
from airbyte import agents
workspace = agents.AgentWorkspace.from_env()
connector = workspace.get_connector("GitHub") # by ID or name (case insensitive)
result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"})
for entity in result.entities:
print(entity["title"])
136 def __init__( 137 self, 138 connector_id: str, 139 *, 140 credentials: _AirbyteCredentials, 141 name: str | None = None, 142 ) -> None: 143 """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`.""" 144 self.connector_id = connector_id 145 """The connector ID.""" 146 147 self._credentials = credentials 148 self._name = name 149 self._details: AgentConnectorDetails | None = None
Initialize an AgentConnector. Prefer AgentWorkspace.get_connector().
151 @property 152 def name(self) -> str | None: 153 """The connector name, fetched from the Agents API if not already known.""" 154 if self._name is None: 155 self._name = self.describe().name 156 return self._name
The connector name, fetched from the Agents API if not already known.
158 def describe(self, *, force_refresh: bool = False) -> AgentConnectorDetails: 159 """Return connector metadata from the Agents API `inspect` endpoint. 160 161 The result is cached; pass `force_refresh=True` to fetch it again. 162 """ 163 if self._details is None or force_refresh: 164 self._details = AgentConnectorDetails.model_validate( 165 _api_util.inspect_agent_connector( 166 connector_id=self.connector_id, 167 credentials=self._credentials, 168 organization_id=self._credentials.organization_id, 169 ) 170 ) 171 self._name = self._details.name or self._name 172 return self._details
Return connector metadata from the Agents API inspect endpoint.
The result is cached; pass force_refresh=True to fetch it again.
174 def execute( # noqa: PLR0913 # Explicit args are the point of this public API. 175 self, 176 entity_type: str, 177 action: str, 178 api_args: dict[str, Any] | None = None, 179 *, 180 select_fields: list[str] | None = None, 181 exclude_fields: list[str] | None = None, 182 page_size: int | None = None, 183 cursor: str | None = None, 184 skip_truncation: bool = True, 185 intent: str | None = None, 186 ) -> AgentExecuteResult: 187 """Execute a single action against one entity type on this connector. 188 189 `entity_type` and `action` are connector-specific, for example `issues` and `list`. 190 Use `describe()` to see what a connector supports. 191 192 `api_args` holds connector-specific arguments passed through to the connector, for 193 example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted 194 by PyAirbyte or by the Agents API itself: 195 196 - `select_fields` and `exclude_fields` prune fields from returned entities. 197 - `page_size` and `cursor` are merged into `api_args` as pagination arguments, where 198 the connector receives `page_size` as its own `limit`. Pass the `end_cursor` of a 199 previous result as `cursor` to fetch the next page. 200 - `skip_truncation` disables the Agents API's default truncation of large payloads. 201 - `intent` is a free-text description of why the action is being run, which some 202 connectors use to refine results. 203 204 The `download` action is rejected, because it returns a binary stream and PyAirbyte 205 does not yet support streaming responses. 206 """ 207 if action in UNSUPPORTED_ACTIONS: 208 raise PyAirbyteInputError( 209 message=f"The {action!r} action is not supported by PyAirbyte.", 210 guidance=( 211 "This action returns a binary stream instead of JSON, and PyAirbyte does " 212 "not yet support streaming responses." 213 ), 214 context={"entity_type": entity_type, "action": action}, 215 ) 216 217 request_body: dict[str, Any] = { 218 "entity": entity_type, 219 "action": action, 220 "params": _build_params(api_args=api_args, page_size=page_size, cursor=cursor), 221 "skip_truncation": skip_truncation, 222 } 223 if select_fields is not None: 224 request_body["select_fields"] = select_fields 225 if exclude_fields is not None: 226 request_body["exclude_fields"] = exclude_fields 227 if intent is not None: 228 request_body["intent"] = intent 229 230 return AgentExecuteResult.model_validate( 231 _api_util.execute_agent_connector_action( 232 connector_id=self.connector_id, 233 request_body=request_body, 234 credentials=self._credentials, 235 organization_id=self._credentials.organization_id, 236 ) 237 )
Execute a single action against one entity type on this connector.
entity_type and action are connector-specific, for example issues and list.
Use describe() to see what a connector supports.
api_args holds connector-specific arguments passed through to the connector, for
example {"repository": "airbytehq/PyAirbyte"}. All other arguments are interpreted
by PyAirbyte or by the Agents API itself:
select_fieldsandexclude_fieldsprune fields from returned entities.page_sizeandcursorare merged intoapi_argsas pagination arguments, where the connector receivespage_sizeas its ownlimit. Pass theend_cursorof a previous result ascursorto fetch the next page.skip_truncationdisables the Agents API's default truncation of large payloads.intentis a free-text description of why the action is being run, which some connectors use to refine results.
The download action is rejected, because it returns a binary stream and PyAirbyte
does not yet support streaming responses.
239 def list_entities( 240 self, 241 entity_type: str, 242 api_args: dict[str, Any] | None = None, 243 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 244 ) -> AgentExecuteResult: 245 """Run the `list` action, which returns a page of entities of `entity_type`.""" 246 return self.execute(entity_type, "list", api_args, **kwargs)
Run the list action, which returns a page of entities of entity_type.
248 def iter_entities( 249 self, 250 entity_type: str, 251 api_args: dict[str, Any] | None = None, 252 *, 253 limit: int | None = None, 254 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `list_entities()`. 255 ) -> Iterator[dict[str, Any]]: 256 """Yield entities of `entity_type`, following the connector's pagination cursor. 257 258 This is the pagination-free way to read entities: each page is fetched lazily as 259 the caller iterates, so no cursor bookkeeping is needed. 260 261 ```python 262 for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}): 263 print(issue["title"]) 264 ``` 265 266 `limit` caps how many entities are yielded in total, which matters for entity types 267 with no natural end. Pass `page_size` to control how many are fetched per request. 268 269 Iteration stops early if the connector reports another page without advancing its 270 cursor, rather than requesting the same page forever. 271 272 Use `list_entities()` instead when a single page is enough, or when the result's 273 `status`, `warning`, or `execution_metadata` are needed. 274 """ 275 cursor: str | None = kwargs.pop("cursor", None) 276 seen_cursors: set[str] = set() 277 yielded = 0 278 279 while True: 280 result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs) 281 for agent_entity in result.entities: 282 yield agent_entity 283 yielded += 1 284 if limit is not None and yielded >= limit: 285 return 286 287 cursor = result.end_cursor 288 if not result.has_next_page or cursor is None or cursor in seen_cursors: 289 return 290 seen_cursors.add(cursor)
Yield entities of entity_type, following the connector's pagination cursor.
This is the pagination-free way to read entities: each page is fetched lazily as the caller iterates, so no cursor bookkeeping is needed.
for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
print(issue["title"])
limit caps how many entities are yielded in total, which matters for entity types
with no natural end. Pass page_size to control how many are fetched per request.
Iteration stops early if the connector reports another page without advancing its cursor, rather than requesting the same page forever.
Use list_entities() instead when a single page is enough, or when the result's
status, warning, or execution_metadata are needed.
292 def search_entities( 293 self, 294 entity_type: str, 295 api_args: dict[str, Any] | None = None, 296 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 297 ) -> AgentExecuteResult: 298 """Run the `search` action, which returns matching entities of `entity_type`.""" 299 return self.execute(entity_type, "search", api_args, **kwargs)
Run the search action, which returns matching entities of entity_type.
301 def get_entity( 302 self, 303 entity_type: str, 304 api_args: dict[str, Any] | None = None, 305 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 306 ) -> AgentExecuteResult: 307 """Run the `get` action, which returns a single entity of `entity_type`.""" 308 return self.execute(entity_type, "get", api_args, **kwargs)
Run the get action, which returns a single entity of entity_type.
310 def create_entity( 311 self, 312 entity_type: str, 313 api_args: dict[str, Any] | None = None, 314 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 315 ) -> AgentExecuteResult: 316 """Run the `create` action, which creates an entity of `entity_type`.""" 317 return self.execute(entity_type, "create", api_args, **kwargs)
Run the create action, which creates an entity of entity_type.
319 def update_entity( 320 self, 321 entity_type: str, 322 api_args: dict[str, Any] | None = None, 323 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 324 ) -> AgentExecuteResult: 325 """Run the `update` action, which updates an entity of `entity_type`.""" 326 return self.execute(entity_type, "update", api_args, **kwargs)
Run the update action, which updates an entity of entity_type.
328 def delete_entity( 329 self, 330 entity_type: str, 331 api_args: dict[str, Any] | None = None, 332 **kwargs: Any, # noqa: ANN401 # Forwarded verbatim to `execute()`. 333 ) -> AgentExecuteResult: 334 """Run the `delete` action, which deletes an entity of `entity_type`.""" 335 return self.execute(entity_type, "delete", api_args, **kwargs)
Run the delete action, which deletes an entity of entity_type.