airbyte-agent-orb 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airbyte_agent_orb/__init__.py +139 -0
- airbyte_agent_orb/_vendored/__init__.py +1 -0
- airbyte_agent_orb/_vendored/connector_sdk/__init__.py +82 -0
- airbyte_agent_orb/_vendored/connector_sdk/auth_strategies.py +1171 -0
- airbyte_agent_orb/_vendored/connector_sdk/auth_template.py +135 -0
- airbyte_agent_orb/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_orb/_vendored/connector_sdk/cloud_utils/client.py +338 -0
- airbyte_agent_orb/_vendored/connector_sdk/connector_model_loader.py +1121 -0
- airbyte_agent_orb/_vendored/connector_sdk/constants.py +78 -0
- airbyte_agent_orb/_vendored/connector_sdk/exceptions.py +23 -0
- airbyte_agent_orb/_vendored/connector_sdk/executor/__init__.py +31 -0
- airbyte_agent_orb/_vendored/connector_sdk/executor/hosted_executor.py +230 -0
- airbyte_agent_orb/_vendored/connector_sdk/executor/local_executor.py +1848 -0
- airbyte_agent_orb/_vendored/connector_sdk/executor/models.py +202 -0
- airbyte_agent_orb/_vendored/connector_sdk/extensions.py +693 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/__init__.py +37 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/adapters/httpx_adapter.py +260 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/config.py +98 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/exceptions.py +119 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/protocols.py +114 -0
- airbyte_agent_orb/_vendored/connector_sdk/http/response.py +104 -0
- airbyte_agent_orb/_vendored/connector_sdk/http_client.py +693 -0
- airbyte_agent_orb/_vendored/connector_sdk/introspection.py +481 -0
- airbyte_agent_orb/_vendored/connector_sdk/logging/__init__.py +11 -0
- airbyte_agent_orb/_vendored/connector_sdk/logging/logger.py +273 -0
- airbyte_agent_orb/_vendored/connector_sdk/logging/types.py +93 -0
- airbyte_agent_orb/_vendored/connector_sdk/observability/__init__.py +11 -0
- airbyte_agent_orb/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_orb/_vendored/connector_sdk/observability/models.py +19 -0
- airbyte_agent_orb/_vendored/connector_sdk/observability/redactor.py +81 -0
- airbyte_agent_orb/_vendored/connector_sdk/observability/session.py +103 -0
- airbyte_agent_orb/_vendored/connector_sdk/performance/__init__.py +6 -0
- airbyte_agent_orb/_vendored/connector_sdk/performance/instrumentation.py +57 -0
- airbyte_agent_orb/_vendored/connector_sdk/performance/metrics.py +93 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/__init__.py +75 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/base.py +212 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/components.py +244 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/connector.py +120 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/extensions.py +301 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/operations.py +156 -0
- airbyte_agent_orb/_vendored/connector_sdk/schema/security.py +241 -0
- airbyte_agent_orb/_vendored/connector_sdk/secrets.py +182 -0
- airbyte_agent_orb/_vendored/connector_sdk/telemetry/__init__.py +10 -0
- airbyte_agent_orb/_vendored/connector_sdk/telemetry/config.py +32 -0
- airbyte_agent_orb/_vendored/connector_sdk/telemetry/events.py +59 -0
- airbyte_agent_orb/_vendored/connector_sdk/telemetry/tracker.py +155 -0
- airbyte_agent_orb/_vendored/connector_sdk/types.py +274 -0
- airbyte_agent_orb/_vendored/connector_sdk/utils.py +127 -0
- airbyte_agent_orb/_vendored/connector_sdk/validation.py +997 -0
- airbyte_agent_orb/_vendored/connector_sdk/validation_replication.py +970 -0
- airbyte_agent_orb/connector.py +1179 -0
- airbyte_agent_orb/connector_model.py +2163 -0
- airbyte_agent_orb/models.py +532 -0
- airbyte_agent_orb/types.py +1090 -0
- airbyte_agent_orb-0.1.6.dist-info/METADATA +153 -0
- airbyte_agent_orb-0.1.6.dist-info/RECORD +58 -0
- airbyte_agent_orb-0.1.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Orb connector.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeVar, overload
|
|
12
|
+
try:
|
|
13
|
+
from typing import Literal
|
|
14
|
+
except ImportError:
|
|
15
|
+
from typing_extensions import Literal
|
|
16
|
+
|
|
17
|
+
from .connector_model import OrbConnectorModel
|
|
18
|
+
from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
|
|
19
|
+
from .types import (
|
|
20
|
+
CustomersGetParams,
|
|
21
|
+
CustomersListParams,
|
|
22
|
+
InvoicesGetParams,
|
|
23
|
+
InvoicesListParams,
|
|
24
|
+
PlansGetParams,
|
|
25
|
+
PlansListParams,
|
|
26
|
+
SubscriptionsGetParams,
|
|
27
|
+
SubscriptionsListParams,
|
|
28
|
+
AirbyteSearchParams,
|
|
29
|
+
CustomersSearchFilter,
|
|
30
|
+
CustomersSearchQuery,
|
|
31
|
+
SubscriptionsSearchFilter,
|
|
32
|
+
SubscriptionsSearchQuery,
|
|
33
|
+
PlansSearchFilter,
|
|
34
|
+
PlansSearchQuery,
|
|
35
|
+
InvoicesSearchFilter,
|
|
36
|
+
InvoicesSearchQuery,
|
|
37
|
+
)
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from .models import OrbAuthConfig
|
|
40
|
+
|
|
41
|
+
# Import response models and envelope models at runtime
|
|
42
|
+
from .models import (
|
|
43
|
+
OrbCheckResult,
|
|
44
|
+
OrbExecuteResult,
|
|
45
|
+
OrbExecuteResultWithMeta,
|
|
46
|
+
CustomersListResult,
|
|
47
|
+
SubscriptionsListResult,
|
|
48
|
+
PlansListResult,
|
|
49
|
+
InvoicesListResult,
|
|
50
|
+
Customer,
|
|
51
|
+
Invoice,
|
|
52
|
+
Plan,
|
|
53
|
+
Subscription,
|
|
54
|
+
AirbyteSearchHit,
|
|
55
|
+
AirbyteSearchResult,
|
|
56
|
+
CustomersSearchData,
|
|
57
|
+
CustomersSearchResult,
|
|
58
|
+
SubscriptionsSearchData,
|
|
59
|
+
SubscriptionsSearchResult,
|
|
60
|
+
PlansSearchData,
|
|
61
|
+
PlansSearchResult,
|
|
62
|
+
InvoicesSearchData,
|
|
63
|
+
InvoicesSearchResult,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# TypeVar for decorator type preservation
|
|
67
|
+
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
68
|
+
|
|
69
|
+
DEFAULT_MAX_OUTPUT_CHARS = 50_000 # ~50KB default, configurable per-tool
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _raise_output_too_large(message: str) -> None:
|
|
73
|
+
try:
|
|
74
|
+
from pydantic_ai import ModelRetry # type: ignore[import-not-found]
|
|
75
|
+
except Exception as exc:
|
|
76
|
+
raise RuntimeError(message) from exc
|
|
77
|
+
raise ModelRetry(message)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _check_output_size(result: Any, max_chars: int | None, tool_name: str) -> Any:
|
|
81
|
+
if max_chars is None or max_chars <= 0:
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
serialized = json.dumps(result, default=str)
|
|
86
|
+
except (TypeError, ValueError):
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
if len(serialized) > max_chars:
|
|
90
|
+
truncated_preview = serialized[:500] + "..." if len(serialized) > 500 else serialized
|
|
91
|
+
_raise_output_too_large(
|
|
92
|
+
f"Tool '{tool_name}' output too large ({len(serialized):,} chars, limit {max_chars:,}). "
|
|
93
|
+
"Please narrow your query by: using the 'fields' parameter to select only needed fields, "
|
|
94
|
+
"adding filters, or reducing the 'limit'. "
|
|
95
|
+
f"Preview: {truncated_preview}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class OrbConnector:
|
|
104
|
+
"""
|
|
105
|
+
Type-safe Orb API connector.
|
|
106
|
+
|
|
107
|
+
Auto-generated from OpenAPI specification with full type safety.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
connector_name = "orb"
|
|
111
|
+
connector_version = "0.1.1"
|
|
112
|
+
vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
|
|
113
|
+
|
|
114
|
+
# Map of (entity, action) -> needs_envelope for envelope wrapping decision
|
|
115
|
+
_ENVELOPE_MAP = {
|
|
116
|
+
("customers", "list"): True,
|
|
117
|
+
("customers", "get"): None,
|
|
118
|
+
("subscriptions", "list"): True,
|
|
119
|
+
("subscriptions", "get"): None,
|
|
120
|
+
("plans", "list"): True,
|
|
121
|
+
("plans", "get"): None,
|
|
122
|
+
("invoices", "list"): True,
|
|
123
|
+
("invoices", "get"): None,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Map of (entity, action) -> {python_param_name: api_param_name}
|
|
127
|
+
# Used to convert snake_case TypedDict keys to API parameter names in execute()
|
|
128
|
+
_PARAM_MAP = {
|
|
129
|
+
('customers', 'list'): {'limit': 'limit', 'cursor': 'cursor'},
|
|
130
|
+
('customers', 'get'): {'customer_id': 'customer_id'},
|
|
131
|
+
('subscriptions', 'list'): {'limit': 'limit', 'cursor': 'cursor', 'customer_id': 'customer_id', 'external_customer_id': 'external_customer_id', 'status': 'status'},
|
|
132
|
+
('subscriptions', 'get'): {'subscription_id': 'subscription_id'},
|
|
133
|
+
('plans', 'list'): {'limit': 'limit', 'cursor': 'cursor'},
|
|
134
|
+
('plans', 'get'): {'plan_id': 'plan_id'},
|
|
135
|
+
('invoices', 'list'): {'limit': 'limit', 'cursor': 'cursor', 'customer_id': 'customer_id', 'external_customer_id': 'external_customer_id', 'subscription_id': 'subscription_id', 'invoice_date_gt': 'invoice_date_gt', 'invoice_date_gte': 'invoice_date_gte', 'invoice_date_lt': 'invoice_date_lt', 'invoice_date_lte': 'invoice_date_lte', 'status': 'status'},
|
|
136
|
+
('invoices', 'get'): {'invoice_id': 'invoice_id'},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
auth_config: OrbAuthConfig | None = None,
|
|
142
|
+
external_user_id: str | None = None,
|
|
143
|
+
airbyte_client_id: str | None = None,
|
|
144
|
+
airbyte_client_secret: str | None = None,
|
|
145
|
+
connector_id: str | None = None,
|
|
146
|
+
on_token_refresh: Any | None = None ):
|
|
147
|
+
"""
|
|
148
|
+
Initialize a new orb connector instance.
|
|
149
|
+
|
|
150
|
+
Supports both local and hosted execution modes:
|
|
151
|
+
- Local mode: Provide `auth_config` for direct API calls
|
|
152
|
+
- Hosted mode: Provide Airbyte credentials with either `connector_id` or `external_user_id`
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
auth_config: Typed authentication configuration (required for local mode)
|
|
156
|
+
external_user_id: External user ID (for hosted mode lookup)
|
|
157
|
+
airbyte_client_id: Airbyte OAuth client ID (required for hosted mode)
|
|
158
|
+
airbyte_client_secret: Airbyte OAuth client secret (required for hosted mode)
|
|
159
|
+
connector_id: Specific connector/source ID (for hosted mode, skips lookup)
|
|
160
|
+
on_token_refresh: Optional callback for OAuth2 token refresh persistence.
|
|
161
|
+
Called with new_tokens dict when tokens are refreshed. Can be sync or async.
|
|
162
|
+
Example: lambda tokens: save_to_database(tokens)
|
|
163
|
+
Examples:
|
|
164
|
+
# Local mode (direct API calls)
|
|
165
|
+
connector = OrbConnector(auth_config=OrbAuthConfig(api_key="..."))
|
|
166
|
+
# Hosted mode with explicit connector_id (no lookup needed)
|
|
167
|
+
connector = OrbConnector(
|
|
168
|
+
airbyte_client_id="client_abc123",
|
|
169
|
+
airbyte_client_secret="secret_xyz789",
|
|
170
|
+
connector_id="existing-source-uuid"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Hosted mode with lookup by external_user_id
|
|
174
|
+
connector = OrbConnector(
|
|
175
|
+
external_user_id="user-123",
|
|
176
|
+
airbyte_client_id="client_abc123",
|
|
177
|
+
airbyte_client_secret="secret_xyz789"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Local mode with OAuth2 token refresh callback
|
|
181
|
+
def save_tokens(new_tokens: dict) -> None:
|
|
182
|
+
# Persist updated tokens to your storage (file, database, etc.)
|
|
183
|
+
with open("tokens.json", "w") as f:
|
|
184
|
+
json.dump(new_tokens, f)
|
|
185
|
+
|
|
186
|
+
connector = OrbConnector(
|
|
187
|
+
auth_config=OrbAuthConfig(access_token="...", refresh_token="..."),
|
|
188
|
+
on_token_refresh=save_tokens
|
|
189
|
+
)
|
|
190
|
+
"""
|
|
191
|
+
# Hosted mode: Airbyte credentials + either connector_id OR external_user_id
|
|
192
|
+
is_hosted = airbyte_client_id and airbyte_client_secret and (connector_id or external_user_id)
|
|
193
|
+
|
|
194
|
+
if is_hosted:
|
|
195
|
+
from ._vendored.connector_sdk.executor import HostedExecutor
|
|
196
|
+
self._executor = HostedExecutor(
|
|
197
|
+
airbyte_client_id=airbyte_client_id,
|
|
198
|
+
airbyte_client_secret=airbyte_client_secret,
|
|
199
|
+
connector_id=connector_id,
|
|
200
|
+
external_user_id=external_user_id,
|
|
201
|
+
connector_definition_id=str(OrbConnectorModel.id) if not connector_id else None,
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
# Local mode: auth_config required
|
|
205
|
+
if not auth_config:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
"Either provide Airbyte credentials (airbyte_client_id, airbyte_client_secret) with "
|
|
208
|
+
"connector_id or external_user_id for hosted mode, or auth_config for local mode"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
from ._vendored.connector_sdk.executor import LocalExecutor
|
|
212
|
+
|
|
213
|
+
# Build config_values dict from server variables
|
|
214
|
+
config_values = None
|
|
215
|
+
|
|
216
|
+
self._executor = LocalExecutor(
|
|
217
|
+
model=OrbConnectorModel,
|
|
218
|
+
auth_config=auth_config.model_dump() if auth_config else None,
|
|
219
|
+
config_values=config_values,
|
|
220
|
+
on_token_refresh=on_token_refresh
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Update base_url with server variables if provided
|
|
224
|
+
|
|
225
|
+
# Initialize entity query objects
|
|
226
|
+
self.customers = CustomersQuery(self)
|
|
227
|
+
self.subscriptions = SubscriptionsQuery(self)
|
|
228
|
+
self.plans = PlansQuery(self)
|
|
229
|
+
self.invoices = InvoicesQuery(self)
|
|
230
|
+
|
|
231
|
+
# ===== TYPED EXECUTE METHOD (Recommended Interface) =====
|
|
232
|
+
|
|
233
|
+
@overload
|
|
234
|
+
async def execute(
|
|
235
|
+
self,
|
|
236
|
+
entity: Literal["customers"],
|
|
237
|
+
action: Literal["list"],
|
|
238
|
+
params: "CustomersListParams"
|
|
239
|
+
) -> "CustomersListResult": ...
|
|
240
|
+
|
|
241
|
+
@overload
|
|
242
|
+
async def execute(
|
|
243
|
+
self,
|
|
244
|
+
entity: Literal["customers"],
|
|
245
|
+
action: Literal["get"],
|
|
246
|
+
params: "CustomersGetParams"
|
|
247
|
+
) -> "Customer": ...
|
|
248
|
+
|
|
249
|
+
@overload
|
|
250
|
+
async def execute(
|
|
251
|
+
self,
|
|
252
|
+
entity: Literal["subscriptions"],
|
|
253
|
+
action: Literal["list"],
|
|
254
|
+
params: "SubscriptionsListParams"
|
|
255
|
+
) -> "SubscriptionsListResult": ...
|
|
256
|
+
|
|
257
|
+
@overload
|
|
258
|
+
async def execute(
|
|
259
|
+
self,
|
|
260
|
+
entity: Literal["subscriptions"],
|
|
261
|
+
action: Literal["get"],
|
|
262
|
+
params: "SubscriptionsGetParams"
|
|
263
|
+
) -> "Subscription": ...
|
|
264
|
+
|
|
265
|
+
@overload
|
|
266
|
+
async def execute(
|
|
267
|
+
self,
|
|
268
|
+
entity: Literal["plans"],
|
|
269
|
+
action: Literal["list"],
|
|
270
|
+
params: "PlansListParams"
|
|
271
|
+
) -> "PlansListResult": ...
|
|
272
|
+
|
|
273
|
+
@overload
|
|
274
|
+
async def execute(
|
|
275
|
+
self,
|
|
276
|
+
entity: Literal["plans"],
|
|
277
|
+
action: Literal["get"],
|
|
278
|
+
params: "PlansGetParams"
|
|
279
|
+
) -> "Plan": ...
|
|
280
|
+
|
|
281
|
+
@overload
|
|
282
|
+
async def execute(
|
|
283
|
+
self,
|
|
284
|
+
entity: Literal["invoices"],
|
|
285
|
+
action: Literal["list"],
|
|
286
|
+
params: "InvoicesListParams"
|
|
287
|
+
) -> "InvoicesListResult": ...
|
|
288
|
+
|
|
289
|
+
@overload
|
|
290
|
+
async def execute(
|
|
291
|
+
self,
|
|
292
|
+
entity: Literal["invoices"],
|
|
293
|
+
action: Literal["get"],
|
|
294
|
+
params: "InvoicesGetParams"
|
|
295
|
+
) -> "Invoice": ...
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@overload
|
|
299
|
+
async def execute(
|
|
300
|
+
self,
|
|
301
|
+
entity: str,
|
|
302
|
+
action: Literal["list", "get", "search"],
|
|
303
|
+
params: Mapping[str, Any]
|
|
304
|
+
) -> OrbExecuteResult[Any] | OrbExecuteResultWithMeta[Any, Any] | Any: ...
|
|
305
|
+
|
|
306
|
+
async def execute(
|
|
307
|
+
self,
|
|
308
|
+
entity: str,
|
|
309
|
+
action: Literal["list", "get", "search"],
|
|
310
|
+
params: Mapping[str, Any] | None = None
|
|
311
|
+
) -> Any:
|
|
312
|
+
"""
|
|
313
|
+
Execute an entity operation with full type safety.
|
|
314
|
+
|
|
315
|
+
This is the recommended interface for blessed connectors as it:
|
|
316
|
+
- Uses the same signature as non-blessed connectors
|
|
317
|
+
- Provides full IDE autocomplete for entity/action/params
|
|
318
|
+
- Makes migration from generic to blessed connectors seamless
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
entity: Entity name (e.g., "customers")
|
|
322
|
+
action: Operation action (e.g., "create", "get", "list")
|
|
323
|
+
params: Operation parameters (typed based on entity+action)
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Typed response based on the operation
|
|
327
|
+
|
|
328
|
+
Example:
|
|
329
|
+
customer = await connector.execute(
|
|
330
|
+
entity="customers",
|
|
331
|
+
action="get",
|
|
332
|
+
params={"id": "cus_123"}
|
|
333
|
+
)
|
|
334
|
+
"""
|
|
335
|
+
from ._vendored.connector_sdk.executor import ExecutionConfig
|
|
336
|
+
|
|
337
|
+
# Remap parameter names from snake_case (TypedDict keys) to API parameter names
|
|
338
|
+
resolved_params = dict(params) if params is not None else None
|
|
339
|
+
if resolved_params:
|
|
340
|
+
param_map = self._PARAM_MAP.get((entity, action), {})
|
|
341
|
+
if param_map:
|
|
342
|
+
resolved_params = {param_map.get(k, k): v for k, v in resolved_params.items()}
|
|
343
|
+
|
|
344
|
+
# Use ExecutionConfig for both local and hosted executors
|
|
345
|
+
config = ExecutionConfig(
|
|
346
|
+
entity=entity,
|
|
347
|
+
action=action,
|
|
348
|
+
params=resolved_params
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
result = await self._executor.execute(config)
|
|
352
|
+
|
|
353
|
+
if not result.success:
|
|
354
|
+
raise RuntimeError(f"Execution failed: {result.error}")
|
|
355
|
+
|
|
356
|
+
# Check if this operation has extractors configured
|
|
357
|
+
has_extractors = self._ENVELOPE_MAP.get((entity, action), False)
|
|
358
|
+
|
|
359
|
+
if has_extractors:
|
|
360
|
+
# With extractors - return Pydantic envelope with data and meta
|
|
361
|
+
if result.meta is not None:
|
|
362
|
+
return OrbExecuteResultWithMeta[Any, Any](
|
|
363
|
+
data=result.data,
|
|
364
|
+
meta=result.meta
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
return OrbExecuteResult[Any](data=result.data)
|
|
368
|
+
else:
|
|
369
|
+
# No extractors - return raw response data
|
|
370
|
+
return result.data
|
|
371
|
+
|
|
372
|
+
# ===== HEALTH CHECK METHOD =====
|
|
373
|
+
|
|
374
|
+
async def check(self) -> OrbCheckResult:
|
|
375
|
+
"""
|
|
376
|
+
Perform a health check to verify connectivity and credentials.
|
|
377
|
+
|
|
378
|
+
Executes a lightweight list operation (limit=1) to validate that
|
|
379
|
+
the connector can communicate with the API and credentials are valid.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
OrbCheckResult with status ("healthy" or "unhealthy") and optional error message
|
|
383
|
+
|
|
384
|
+
Example:
|
|
385
|
+
result = await connector.check()
|
|
386
|
+
if result.status == "healthy":
|
|
387
|
+
print("Connection verified!")
|
|
388
|
+
else:
|
|
389
|
+
print(f"Check failed: {result.error}")
|
|
390
|
+
"""
|
|
391
|
+
result = await self._executor.check()
|
|
392
|
+
|
|
393
|
+
if result.success and isinstance(result.data, dict):
|
|
394
|
+
return OrbCheckResult(
|
|
395
|
+
status=result.data.get("status", "unhealthy"),
|
|
396
|
+
error=result.data.get("error"),
|
|
397
|
+
checked_entity=result.data.get("checked_entity"),
|
|
398
|
+
checked_action=result.data.get("checked_action"),
|
|
399
|
+
)
|
|
400
|
+
else:
|
|
401
|
+
return OrbCheckResult(
|
|
402
|
+
status="unhealthy",
|
|
403
|
+
error=result.error or "Unknown error during health check",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# ===== INTROSPECTION METHODS =====
|
|
407
|
+
|
|
408
|
+
@classmethod
|
|
409
|
+
def tool_utils(
|
|
410
|
+
cls,
|
|
411
|
+
func: _F | None = None,
|
|
412
|
+
*,
|
|
413
|
+
update_docstring: bool = True,
|
|
414
|
+
enable_hosted_mode_features: bool = True,
|
|
415
|
+
max_output_chars: int | None = DEFAULT_MAX_OUTPUT_CHARS,
|
|
416
|
+
) -> _F | Callable[[_F], _F]:
|
|
417
|
+
"""
|
|
418
|
+
Decorator that adds tool utilities like docstring augmentation and output limits.
|
|
419
|
+
|
|
420
|
+
Usage:
|
|
421
|
+
@mcp.tool()
|
|
422
|
+
@OrbConnector.tool_utils
|
|
423
|
+
async def execute(entity: str, action: str, params: dict):
|
|
424
|
+
...
|
|
425
|
+
|
|
426
|
+
@mcp.tool()
|
|
427
|
+
@OrbConnector.tool_utils(update_docstring=False, max_output_chars=None)
|
|
428
|
+
async def execute(entity: str, action: str, params: dict):
|
|
429
|
+
...
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
update_docstring: When True, append connector capabilities to __doc__.
|
|
433
|
+
enable_hosted_mode_features: When False, omit hosted-mode search sections from docstrings.
|
|
434
|
+
max_output_chars: Max serialized output size before raising. Use None to disable.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
def decorate(inner: _F) -> _F:
|
|
438
|
+
if update_docstring:
|
|
439
|
+
description = generate_tool_description(
|
|
440
|
+
OrbConnectorModel,
|
|
441
|
+
enable_hosted_mode_features=enable_hosted_mode_features,
|
|
442
|
+
)
|
|
443
|
+
original_doc = inner.__doc__ or ""
|
|
444
|
+
if original_doc.strip():
|
|
445
|
+
full_doc = f"{original_doc.strip()}\n{description}"
|
|
446
|
+
else:
|
|
447
|
+
full_doc = description
|
|
448
|
+
else:
|
|
449
|
+
full_doc = ""
|
|
450
|
+
|
|
451
|
+
if inspect.iscoroutinefunction(inner):
|
|
452
|
+
|
|
453
|
+
@wraps(inner)
|
|
454
|
+
async def aw(*args: Any, **kwargs: Any) -> Any:
|
|
455
|
+
result = await inner(*args, **kwargs)
|
|
456
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
457
|
+
|
|
458
|
+
wrapped = aw
|
|
459
|
+
else:
|
|
460
|
+
|
|
461
|
+
@wraps(inner)
|
|
462
|
+
def sw(*args: Any, **kwargs: Any) -> Any:
|
|
463
|
+
result = inner(*args, **kwargs)
|
|
464
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
465
|
+
|
|
466
|
+
wrapped = sw
|
|
467
|
+
|
|
468
|
+
if update_docstring:
|
|
469
|
+
wrapped.__doc__ = full_doc
|
|
470
|
+
return wrapped # type: ignore[return-value]
|
|
471
|
+
|
|
472
|
+
if func is not None:
|
|
473
|
+
return decorate(func)
|
|
474
|
+
return decorate
|
|
475
|
+
|
|
476
|
+
def list_entities(self) -> list[dict[str, Any]]:
|
|
477
|
+
"""
|
|
478
|
+
Get structured data about available entities, actions, and parameters.
|
|
479
|
+
|
|
480
|
+
Returns a list of entity descriptions with:
|
|
481
|
+
- entity_name: Name of the entity (e.g., "contacts", "deals")
|
|
482
|
+
- description: Entity description from the first endpoint
|
|
483
|
+
- available_actions: List of actions (e.g., ["list", "get", "create"])
|
|
484
|
+
- parameters: Dict mapping action -> list of parameter dicts
|
|
485
|
+
|
|
486
|
+
Example:
|
|
487
|
+
entities = connector.list_entities()
|
|
488
|
+
for entity in entities:
|
|
489
|
+
print(f"{entity['entity_name']}: {entity['available_actions']}")
|
|
490
|
+
"""
|
|
491
|
+
return describe_entities(OrbConnectorModel)
|
|
492
|
+
|
|
493
|
+
def entity_schema(self, entity: str) -> dict[str, Any] | None:
|
|
494
|
+
"""
|
|
495
|
+
Get the JSON schema for an entity.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
entity: Entity name (e.g., "contacts", "companies")
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
JSON schema dict describing the entity structure, or None if not found.
|
|
502
|
+
|
|
503
|
+
Example:
|
|
504
|
+
schema = connector.entity_schema("contacts")
|
|
505
|
+
if schema:
|
|
506
|
+
print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
|
|
507
|
+
"""
|
|
508
|
+
entity_def = next(
|
|
509
|
+
(e for e in OrbConnectorModel.entities if e.name == entity),
|
|
510
|
+
None
|
|
511
|
+
)
|
|
512
|
+
if entity_def is None:
|
|
513
|
+
logging.getLogger(__name__).warning(
|
|
514
|
+
f"Entity '{entity}' not found. Available entities: "
|
|
515
|
+
f"{[e.name for e in OrbConnectorModel.entities]}"
|
|
516
|
+
)
|
|
517
|
+
return entity_def.entity_schema if entity_def else None
|
|
518
|
+
|
|
519
|
+
@property
|
|
520
|
+
def connector_id(self) -> str | None:
|
|
521
|
+
"""Get the connector/source ID (only available in hosted mode).
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
The connector ID if in hosted mode, None if in local mode.
|
|
525
|
+
|
|
526
|
+
Example:
|
|
527
|
+
connector = await OrbConnector.create_hosted(...)
|
|
528
|
+
print(f"Created connector: {connector.connector_id}")
|
|
529
|
+
"""
|
|
530
|
+
if hasattr(self, '_executor') and hasattr(self._executor, '_connector_id'):
|
|
531
|
+
return self._executor._connector_id
|
|
532
|
+
return None
|
|
533
|
+
|
|
534
|
+
# ===== HOSTED MODE FACTORY =====
|
|
535
|
+
|
|
536
|
+
@classmethod
|
|
537
|
+
async def create_hosted(
|
|
538
|
+
cls,
|
|
539
|
+
*,
|
|
540
|
+
external_user_id: str,
|
|
541
|
+
airbyte_client_id: str,
|
|
542
|
+
airbyte_client_secret: str,
|
|
543
|
+
auth_config: "OrbAuthConfig",
|
|
544
|
+
name: str | None = None,
|
|
545
|
+
replication_config: dict[str, Any] | None = None,
|
|
546
|
+
source_template_id: str | None = None,
|
|
547
|
+
) -> "OrbConnector":
|
|
548
|
+
"""
|
|
549
|
+
Create a new hosted connector on Airbyte Cloud.
|
|
550
|
+
|
|
551
|
+
This factory method:
|
|
552
|
+
1. Creates a source on Airbyte Cloud with the provided credentials
|
|
553
|
+
2. Returns a connector configured with the new connector_id
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
external_user_id: Workspace identifier in Airbyte Cloud
|
|
557
|
+
airbyte_client_id: Airbyte OAuth client ID
|
|
558
|
+
airbyte_client_secret: Airbyte OAuth client secret
|
|
559
|
+
auth_config: Typed auth config (same as local mode)
|
|
560
|
+
name: Optional source name (defaults to connector name + external_user_id)
|
|
561
|
+
replication_config: Optional replication settings dict.
|
|
562
|
+
Required for connectors with x-airbyte-replication-config (REPLICATION mode sources).
|
|
563
|
+
source_template_id: Source template ID. Required when organization has
|
|
564
|
+
multiple source templates for this connector type.
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
A OrbConnector instance configured in hosted mode
|
|
568
|
+
|
|
569
|
+
Example:
|
|
570
|
+
# Create a new hosted connector with API key auth
|
|
571
|
+
connector = await OrbConnector.create_hosted(
|
|
572
|
+
external_user_id="my-workspace",
|
|
573
|
+
airbyte_client_id="client_abc",
|
|
574
|
+
airbyte_client_secret="secret_xyz",
|
|
575
|
+
auth_config=OrbAuthConfig(api_key="..."),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# Use the connector
|
|
579
|
+
result = await connector.execute("entity", "list", {})
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
from ._vendored.connector_sdk.cloud_utils import AirbyteCloudClient
|
|
583
|
+
|
|
584
|
+
client = AirbyteCloudClient(
|
|
585
|
+
client_id=airbyte_client_id,
|
|
586
|
+
client_secret=airbyte_client_secret,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
try:
|
|
590
|
+
# Build credentials from auth_config (if provided)
|
|
591
|
+
credentials = auth_config.model_dump(exclude_none=True) if auth_config else None
|
|
592
|
+
replication_config_dict = replication_config.model_dump(exclude_none=True) if replication_config else None
|
|
593
|
+
|
|
594
|
+
# Create source on Airbyte Cloud
|
|
595
|
+
source_name = name or f"{cls.connector_name} - {external_user_id}"
|
|
596
|
+
source_id = await client.create_source(
|
|
597
|
+
name=source_name,
|
|
598
|
+
connector_definition_id=str(OrbConnectorModel.id),
|
|
599
|
+
external_user_id=external_user_id,
|
|
600
|
+
credentials=credentials,
|
|
601
|
+
replication_config=replication_config_dict,
|
|
602
|
+
source_template_id=source_template_id,
|
|
603
|
+
)
|
|
604
|
+
finally:
|
|
605
|
+
await client.close()
|
|
606
|
+
|
|
607
|
+
# Return connector configured with the new connector_id
|
|
608
|
+
return cls(
|
|
609
|
+
airbyte_client_id=airbyte_client_id,
|
|
610
|
+
airbyte_client_secret=airbyte_client_secret,
|
|
611
|
+
connector_id=source_id,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class CustomersQuery:
|
|
618
|
+
"""
|
|
619
|
+
Query class for Customers entity operations.
|
|
620
|
+
"""
|
|
621
|
+
|
|
622
|
+
def __init__(self, connector: OrbConnector):
|
|
623
|
+
"""Initialize query with connector reference."""
|
|
624
|
+
self._connector = connector
|
|
625
|
+
|
|
626
|
+
async def list(
|
|
627
|
+
self,
|
|
628
|
+
limit: int | None = None,
|
|
629
|
+
cursor: str | None = None,
|
|
630
|
+
**kwargs
|
|
631
|
+
) -> CustomersListResult:
|
|
632
|
+
"""
|
|
633
|
+
Returns a paginated list of customers
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
limit: Number of items to return per page
|
|
637
|
+
cursor: Cursor for pagination
|
|
638
|
+
**kwargs: Additional parameters
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
CustomersListResult
|
|
642
|
+
"""
|
|
643
|
+
params = {k: v for k, v in {
|
|
644
|
+
"limit": limit,
|
|
645
|
+
"cursor": cursor,
|
|
646
|
+
**kwargs
|
|
647
|
+
}.items() if v is not None}
|
|
648
|
+
|
|
649
|
+
result = await self._connector.execute("customers", "list", params)
|
|
650
|
+
# Cast generic envelope to concrete typed result
|
|
651
|
+
return CustomersListResult(
|
|
652
|
+
data=result.data,
|
|
653
|
+
meta=result.meta
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
async def get(
|
|
659
|
+
self,
|
|
660
|
+
customer_id: str,
|
|
661
|
+
**kwargs
|
|
662
|
+
) -> Customer:
|
|
663
|
+
"""
|
|
664
|
+
Get a single customer by ID
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
customer_id: Customer ID
|
|
668
|
+
**kwargs: Additional parameters
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Customer
|
|
672
|
+
"""
|
|
673
|
+
params = {k: v for k, v in {
|
|
674
|
+
"customer_id": customer_id,
|
|
675
|
+
**kwargs
|
|
676
|
+
}.items() if v is not None}
|
|
677
|
+
|
|
678
|
+
result = await self._connector.execute("customers", "get", params)
|
|
679
|
+
return result
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
async def search(
|
|
684
|
+
self,
|
|
685
|
+
query: CustomersSearchQuery,
|
|
686
|
+
limit: int | None = None,
|
|
687
|
+
cursor: str | None = None,
|
|
688
|
+
fields: list[list[str]] | None = None,
|
|
689
|
+
) -> CustomersSearchResult:
|
|
690
|
+
"""
|
|
691
|
+
Search customers records from Airbyte cache.
|
|
692
|
+
|
|
693
|
+
This operation searches cached data from Airbyte syncs.
|
|
694
|
+
Only available in hosted execution mode.
|
|
695
|
+
|
|
696
|
+
Available filter fields (CustomersSearchFilter):
|
|
697
|
+
- id: The unique identifier of the customer
|
|
698
|
+
- external_customer_id: The ID of the customer in an external system
|
|
699
|
+
- name: The name of the customer
|
|
700
|
+
- email: The email address of the customer
|
|
701
|
+
- created_at: The date and time when the customer was created
|
|
702
|
+
- payment_provider: The payment provider used by the customer
|
|
703
|
+
- payment_provider_id: The ID of the customer in the payment provider's system
|
|
704
|
+
- timezone: The timezone setting of the customer
|
|
705
|
+
- shipping_address: The shipping address of the customer
|
|
706
|
+
- billing_address: The billing address of the customer
|
|
707
|
+
- balance: The current balance of the customer
|
|
708
|
+
- currency: The currency of the customer
|
|
709
|
+
- auto_collection: Whether auto collection is enabled
|
|
710
|
+
- metadata: Additional metadata for the customer
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
714
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
715
|
+
limit: Maximum results to return (default 1000)
|
|
716
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
717
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
718
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
CustomersSearchResult with hits (list of AirbyteSearchHit[CustomersSearchData]) and pagination info
|
|
722
|
+
|
|
723
|
+
Raises:
|
|
724
|
+
NotImplementedError: If called in local execution mode
|
|
725
|
+
"""
|
|
726
|
+
params: dict[str, Any] = {"query": query}
|
|
727
|
+
if limit is not None:
|
|
728
|
+
params["limit"] = limit
|
|
729
|
+
if cursor is not None:
|
|
730
|
+
params["cursor"] = cursor
|
|
731
|
+
if fields is not None:
|
|
732
|
+
params["fields"] = fields
|
|
733
|
+
|
|
734
|
+
result = await self._connector.execute("customers", "search", params)
|
|
735
|
+
|
|
736
|
+
# Parse response into typed result
|
|
737
|
+
return CustomersSearchResult(
|
|
738
|
+
hits=[
|
|
739
|
+
AirbyteSearchHit[CustomersSearchData](
|
|
740
|
+
id=hit.get("id"),
|
|
741
|
+
score=hit.get("score"),
|
|
742
|
+
data=CustomersSearchData(**hit.get("data", {}))
|
|
743
|
+
)
|
|
744
|
+
for hit in result.get("hits", [])
|
|
745
|
+
],
|
|
746
|
+
next_cursor=result.get("next_cursor"),
|
|
747
|
+
took_ms=result.get("took_ms")
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
class SubscriptionsQuery:
|
|
751
|
+
"""
|
|
752
|
+
Query class for Subscriptions entity operations.
|
|
753
|
+
"""
|
|
754
|
+
|
|
755
|
+
def __init__(self, connector: OrbConnector):
|
|
756
|
+
"""Initialize query with connector reference."""
|
|
757
|
+
self._connector = connector
|
|
758
|
+
|
|
759
|
+
async def list(
|
|
760
|
+
self,
|
|
761
|
+
limit: int | None = None,
|
|
762
|
+
cursor: str | None = None,
|
|
763
|
+
customer_id: str | None = None,
|
|
764
|
+
external_customer_id: str | None = None,
|
|
765
|
+
status: str | None = None,
|
|
766
|
+
**kwargs
|
|
767
|
+
) -> SubscriptionsListResult:
|
|
768
|
+
"""
|
|
769
|
+
Returns a paginated list of subscriptions
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
limit: Number of items to return per page
|
|
773
|
+
cursor: Cursor for pagination
|
|
774
|
+
customer_id: Filter subscriptions by customer ID
|
|
775
|
+
external_customer_id: Filter subscriptions by external customer ID
|
|
776
|
+
status: Filter subscriptions by status
|
|
777
|
+
**kwargs: Additional parameters
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
SubscriptionsListResult
|
|
781
|
+
"""
|
|
782
|
+
params = {k: v for k, v in {
|
|
783
|
+
"limit": limit,
|
|
784
|
+
"cursor": cursor,
|
|
785
|
+
"customer_id": customer_id,
|
|
786
|
+
"external_customer_id": external_customer_id,
|
|
787
|
+
"status": status,
|
|
788
|
+
**kwargs
|
|
789
|
+
}.items() if v is not None}
|
|
790
|
+
|
|
791
|
+
result = await self._connector.execute("subscriptions", "list", params)
|
|
792
|
+
# Cast generic envelope to concrete typed result
|
|
793
|
+
return SubscriptionsListResult(
|
|
794
|
+
data=result.data,
|
|
795
|
+
meta=result.meta
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
async def get(
|
|
801
|
+
self,
|
|
802
|
+
subscription_id: str,
|
|
803
|
+
**kwargs
|
|
804
|
+
) -> Subscription:
|
|
805
|
+
"""
|
|
806
|
+
Get a single subscription by ID
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
subscription_id: Subscription ID
|
|
810
|
+
**kwargs: Additional parameters
|
|
811
|
+
|
|
812
|
+
Returns:
|
|
813
|
+
Subscription
|
|
814
|
+
"""
|
|
815
|
+
params = {k: v for k, v in {
|
|
816
|
+
"subscription_id": subscription_id,
|
|
817
|
+
**kwargs
|
|
818
|
+
}.items() if v is not None}
|
|
819
|
+
|
|
820
|
+
result = await self._connector.execute("subscriptions", "get", params)
|
|
821
|
+
return result
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
async def search(
|
|
826
|
+
self,
|
|
827
|
+
query: SubscriptionsSearchQuery,
|
|
828
|
+
limit: int | None = None,
|
|
829
|
+
cursor: str | None = None,
|
|
830
|
+
fields: list[list[str]] | None = None,
|
|
831
|
+
) -> SubscriptionsSearchResult:
|
|
832
|
+
"""
|
|
833
|
+
Search subscriptions records from Airbyte cache.
|
|
834
|
+
|
|
835
|
+
This operation searches cached data from Airbyte syncs.
|
|
836
|
+
Only available in hosted execution mode.
|
|
837
|
+
|
|
838
|
+
Available filter fields (SubscriptionsSearchFilter):
|
|
839
|
+
- id: The unique identifier of the subscription
|
|
840
|
+
- created_at: The date and time when the subscription was created
|
|
841
|
+
- start_date: The date and time when the subscription starts
|
|
842
|
+
- end_date: The date and time when the subscription ends
|
|
843
|
+
- status: The current status of the subscription
|
|
844
|
+
- customer: The customer associated with the subscription
|
|
845
|
+
- plan: The plan associated with the subscription
|
|
846
|
+
- current_billing_period_start_date: The start date of the current billing period
|
|
847
|
+
- current_billing_period_end_date: The end date of the current billing period
|
|
848
|
+
- auto_collection: Whether auto collection is enabled
|
|
849
|
+
- net_terms: The net terms for the subscription
|
|
850
|
+
- metadata: Additional metadata for the subscription
|
|
851
|
+
|
|
852
|
+
Args:
|
|
853
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
854
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
855
|
+
limit: Maximum results to return (default 1000)
|
|
856
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
857
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
858
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
SubscriptionsSearchResult with hits (list of AirbyteSearchHit[SubscriptionsSearchData]) and pagination info
|
|
862
|
+
|
|
863
|
+
Raises:
|
|
864
|
+
NotImplementedError: If called in local execution mode
|
|
865
|
+
"""
|
|
866
|
+
params: dict[str, Any] = {"query": query}
|
|
867
|
+
if limit is not None:
|
|
868
|
+
params["limit"] = limit
|
|
869
|
+
if cursor is not None:
|
|
870
|
+
params["cursor"] = cursor
|
|
871
|
+
if fields is not None:
|
|
872
|
+
params["fields"] = fields
|
|
873
|
+
|
|
874
|
+
result = await self._connector.execute("subscriptions", "search", params)
|
|
875
|
+
|
|
876
|
+
# Parse response into typed result
|
|
877
|
+
return SubscriptionsSearchResult(
|
|
878
|
+
hits=[
|
|
879
|
+
AirbyteSearchHit[SubscriptionsSearchData](
|
|
880
|
+
id=hit.get("id"),
|
|
881
|
+
score=hit.get("score"),
|
|
882
|
+
data=SubscriptionsSearchData(**hit.get("data", {}))
|
|
883
|
+
)
|
|
884
|
+
for hit in result.get("hits", [])
|
|
885
|
+
],
|
|
886
|
+
next_cursor=result.get("next_cursor"),
|
|
887
|
+
took_ms=result.get("took_ms")
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
class PlansQuery:
|
|
891
|
+
"""
|
|
892
|
+
Query class for Plans entity operations.
|
|
893
|
+
"""
|
|
894
|
+
|
|
895
|
+
def __init__(self, connector: OrbConnector):
|
|
896
|
+
"""Initialize query with connector reference."""
|
|
897
|
+
self._connector = connector
|
|
898
|
+
|
|
899
|
+
async def list(
|
|
900
|
+
self,
|
|
901
|
+
limit: int | None = None,
|
|
902
|
+
cursor: str | None = None,
|
|
903
|
+
**kwargs
|
|
904
|
+
) -> PlansListResult:
|
|
905
|
+
"""
|
|
906
|
+
Returns a paginated list of plans
|
|
907
|
+
|
|
908
|
+
Args:
|
|
909
|
+
limit: Number of items to return per page
|
|
910
|
+
cursor: Cursor for pagination
|
|
911
|
+
**kwargs: Additional parameters
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
PlansListResult
|
|
915
|
+
"""
|
|
916
|
+
params = {k: v for k, v in {
|
|
917
|
+
"limit": limit,
|
|
918
|
+
"cursor": cursor,
|
|
919
|
+
**kwargs
|
|
920
|
+
}.items() if v is not None}
|
|
921
|
+
|
|
922
|
+
result = await self._connector.execute("plans", "list", params)
|
|
923
|
+
# Cast generic envelope to concrete typed result
|
|
924
|
+
return PlansListResult(
|
|
925
|
+
data=result.data,
|
|
926
|
+
meta=result.meta
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
async def get(
|
|
932
|
+
self,
|
|
933
|
+
plan_id: str,
|
|
934
|
+
**kwargs
|
|
935
|
+
) -> Plan:
|
|
936
|
+
"""
|
|
937
|
+
Get a single plan by ID
|
|
938
|
+
|
|
939
|
+
Args:
|
|
940
|
+
plan_id: Plan ID
|
|
941
|
+
**kwargs: Additional parameters
|
|
942
|
+
|
|
943
|
+
Returns:
|
|
944
|
+
Plan
|
|
945
|
+
"""
|
|
946
|
+
params = {k: v for k, v in {
|
|
947
|
+
"plan_id": plan_id,
|
|
948
|
+
**kwargs
|
|
949
|
+
}.items() if v is not None}
|
|
950
|
+
|
|
951
|
+
result = await self._connector.execute("plans", "get", params)
|
|
952
|
+
return result
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
async def search(
|
|
957
|
+
self,
|
|
958
|
+
query: PlansSearchQuery,
|
|
959
|
+
limit: int | None = None,
|
|
960
|
+
cursor: str | None = None,
|
|
961
|
+
fields: list[list[str]] | None = None,
|
|
962
|
+
) -> PlansSearchResult:
|
|
963
|
+
"""
|
|
964
|
+
Search plans records from Airbyte cache.
|
|
965
|
+
|
|
966
|
+
This operation searches cached data from Airbyte syncs.
|
|
967
|
+
Only available in hosted execution mode.
|
|
968
|
+
|
|
969
|
+
Available filter fields (PlansSearchFilter):
|
|
970
|
+
- id: The unique identifier of the plan
|
|
971
|
+
- created_at: The date and time when the plan was created
|
|
972
|
+
- name: The name of the plan
|
|
973
|
+
- description: A description of the plan
|
|
974
|
+
- status: The status of the plan
|
|
975
|
+
- currency: The currency of the plan
|
|
976
|
+
- prices: The pricing options for the plan
|
|
977
|
+
- product: The product associated with the plan
|
|
978
|
+
- external_plan_id: The external plan ID
|
|
979
|
+
- metadata: Additional metadata for the plan
|
|
980
|
+
|
|
981
|
+
Args:
|
|
982
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
983
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
984
|
+
limit: Maximum results to return (default 1000)
|
|
985
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
986
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
987
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
988
|
+
|
|
989
|
+
Returns:
|
|
990
|
+
PlansSearchResult with hits (list of AirbyteSearchHit[PlansSearchData]) and pagination info
|
|
991
|
+
|
|
992
|
+
Raises:
|
|
993
|
+
NotImplementedError: If called in local execution mode
|
|
994
|
+
"""
|
|
995
|
+
params: dict[str, Any] = {"query": query}
|
|
996
|
+
if limit is not None:
|
|
997
|
+
params["limit"] = limit
|
|
998
|
+
if cursor is not None:
|
|
999
|
+
params["cursor"] = cursor
|
|
1000
|
+
if fields is not None:
|
|
1001
|
+
params["fields"] = fields
|
|
1002
|
+
|
|
1003
|
+
result = await self._connector.execute("plans", "search", params)
|
|
1004
|
+
|
|
1005
|
+
# Parse response into typed result
|
|
1006
|
+
return PlansSearchResult(
|
|
1007
|
+
hits=[
|
|
1008
|
+
AirbyteSearchHit[PlansSearchData](
|
|
1009
|
+
id=hit.get("id"),
|
|
1010
|
+
score=hit.get("score"),
|
|
1011
|
+
data=PlansSearchData(**hit.get("data", {}))
|
|
1012
|
+
)
|
|
1013
|
+
for hit in result.get("hits", [])
|
|
1014
|
+
],
|
|
1015
|
+
next_cursor=result.get("next_cursor"),
|
|
1016
|
+
took_ms=result.get("took_ms")
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
class InvoicesQuery:
|
|
1020
|
+
"""
|
|
1021
|
+
Query class for Invoices entity operations.
|
|
1022
|
+
"""
|
|
1023
|
+
|
|
1024
|
+
def __init__(self, connector: OrbConnector):
|
|
1025
|
+
"""Initialize query with connector reference."""
|
|
1026
|
+
self._connector = connector
|
|
1027
|
+
|
|
1028
|
+
async def list(
|
|
1029
|
+
self,
|
|
1030
|
+
limit: int | None = None,
|
|
1031
|
+
cursor: str | None = None,
|
|
1032
|
+
customer_id: str | None = None,
|
|
1033
|
+
external_customer_id: str | None = None,
|
|
1034
|
+
subscription_id: str | None = None,
|
|
1035
|
+
invoice_date_gt: str | None = None,
|
|
1036
|
+
invoice_date_gte: str | None = None,
|
|
1037
|
+
invoice_date_lt: str | None = None,
|
|
1038
|
+
invoice_date_lte: str | None = None,
|
|
1039
|
+
status: str | None = None,
|
|
1040
|
+
**kwargs
|
|
1041
|
+
) -> InvoicesListResult:
|
|
1042
|
+
"""
|
|
1043
|
+
Returns a paginated list of invoices
|
|
1044
|
+
|
|
1045
|
+
Args:
|
|
1046
|
+
limit: Number of items to return per page
|
|
1047
|
+
cursor: Cursor for pagination
|
|
1048
|
+
customer_id: Filter invoices by customer ID
|
|
1049
|
+
external_customer_id: Filter invoices by external customer ID
|
|
1050
|
+
subscription_id: Filter invoices by subscription ID
|
|
1051
|
+
invoice_date_gt: Filter invoices with invoice date greater than this value (ISO 8601 format)
|
|
1052
|
+
invoice_date_gte: Filter invoices with invoice date greater than or equal to this value (ISO 8601 format)
|
|
1053
|
+
invoice_date_lt: Filter invoices with invoice date less than this value (ISO 8601 format)
|
|
1054
|
+
invoice_date_lte: Filter invoices with invoice date less than or equal to this value (ISO 8601 format)
|
|
1055
|
+
status: Filter invoices by status
|
|
1056
|
+
**kwargs: Additional parameters
|
|
1057
|
+
|
|
1058
|
+
Returns:
|
|
1059
|
+
InvoicesListResult
|
|
1060
|
+
"""
|
|
1061
|
+
params = {k: v for k, v in {
|
|
1062
|
+
"limit": limit,
|
|
1063
|
+
"cursor": cursor,
|
|
1064
|
+
"customer_id": customer_id,
|
|
1065
|
+
"external_customer_id": external_customer_id,
|
|
1066
|
+
"subscription_id": subscription_id,
|
|
1067
|
+
"invoice_date_gt": invoice_date_gt,
|
|
1068
|
+
"invoice_date_gte": invoice_date_gte,
|
|
1069
|
+
"invoice_date_lt": invoice_date_lt,
|
|
1070
|
+
"invoice_date_lte": invoice_date_lte,
|
|
1071
|
+
"status": status,
|
|
1072
|
+
**kwargs
|
|
1073
|
+
}.items() if v is not None}
|
|
1074
|
+
|
|
1075
|
+
result = await self._connector.execute("invoices", "list", params)
|
|
1076
|
+
# Cast generic envelope to concrete typed result
|
|
1077
|
+
return InvoicesListResult(
|
|
1078
|
+
data=result.data,
|
|
1079
|
+
meta=result.meta
|
|
1080
|
+
)
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
async def get(
|
|
1085
|
+
self,
|
|
1086
|
+
invoice_id: str,
|
|
1087
|
+
**kwargs
|
|
1088
|
+
) -> Invoice:
|
|
1089
|
+
"""
|
|
1090
|
+
Get a single invoice by ID
|
|
1091
|
+
|
|
1092
|
+
Args:
|
|
1093
|
+
invoice_id: Invoice ID
|
|
1094
|
+
**kwargs: Additional parameters
|
|
1095
|
+
|
|
1096
|
+
Returns:
|
|
1097
|
+
Invoice
|
|
1098
|
+
"""
|
|
1099
|
+
params = {k: v for k, v in {
|
|
1100
|
+
"invoice_id": invoice_id,
|
|
1101
|
+
**kwargs
|
|
1102
|
+
}.items() if v is not None}
|
|
1103
|
+
|
|
1104
|
+
result = await self._connector.execute("invoices", "get", params)
|
|
1105
|
+
return result
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
async def search(
|
|
1110
|
+
self,
|
|
1111
|
+
query: InvoicesSearchQuery,
|
|
1112
|
+
limit: int | None = None,
|
|
1113
|
+
cursor: str | None = None,
|
|
1114
|
+
fields: list[list[str]] | None = None,
|
|
1115
|
+
) -> InvoicesSearchResult:
|
|
1116
|
+
"""
|
|
1117
|
+
Search invoices records from Airbyte cache.
|
|
1118
|
+
|
|
1119
|
+
This operation searches cached data from Airbyte syncs.
|
|
1120
|
+
Only available in hosted execution mode.
|
|
1121
|
+
|
|
1122
|
+
Available filter fields (InvoicesSearchFilter):
|
|
1123
|
+
- id: The unique identifier of the invoice
|
|
1124
|
+
- created_at: The date and time when the invoice was created
|
|
1125
|
+
- invoice_date: The date of the invoice
|
|
1126
|
+
- due_date: The due date for the invoice
|
|
1127
|
+
- invoice_pdf: The URL to download the PDF version of the invoice
|
|
1128
|
+
- subtotal: The subtotal amount of the invoice
|
|
1129
|
+
- total: The total amount of the invoice
|
|
1130
|
+
- amount_due: The amount due on the invoice
|
|
1131
|
+
- status: The current status of the invoice
|
|
1132
|
+
- memo: Any additional notes or comments on the invoice
|
|
1133
|
+
- paid_at: The date and time when the invoice was paid
|
|
1134
|
+
- issued_at: The date and time when the invoice was issued
|
|
1135
|
+
- hosted_invoice_url: The URL to view the hosted invoice
|
|
1136
|
+
- line_items: The line items on the invoice
|
|
1137
|
+
- subscription: The subscription associated with the invoice
|
|
1138
|
+
- customer: The customer associated with the invoice
|
|
1139
|
+
- currency: The currency of the invoice
|
|
1140
|
+
- invoice_number: The invoice number
|
|
1141
|
+
- metadata: Additional metadata for the invoice
|
|
1142
|
+
|
|
1143
|
+
Args:
|
|
1144
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
1145
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
1146
|
+
limit: Maximum results to return (default 1000)
|
|
1147
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
1148
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
1149
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
1150
|
+
|
|
1151
|
+
Returns:
|
|
1152
|
+
InvoicesSearchResult with hits (list of AirbyteSearchHit[InvoicesSearchData]) and pagination info
|
|
1153
|
+
|
|
1154
|
+
Raises:
|
|
1155
|
+
NotImplementedError: If called in local execution mode
|
|
1156
|
+
"""
|
|
1157
|
+
params: dict[str, Any] = {"query": query}
|
|
1158
|
+
if limit is not None:
|
|
1159
|
+
params["limit"] = limit
|
|
1160
|
+
if cursor is not None:
|
|
1161
|
+
params["cursor"] = cursor
|
|
1162
|
+
if fields is not None:
|
|
1163
|
+
params["fields"] = fields
|
|
1164
|
+
|
|
1165
|
+
result = await self._connector.execute("invoices", "search", params)
|
|
1166
|
+
|
|
1167
|
+
# Parse response into typed result
|
|
1168
|
+
return InvoicesSearchResult(
|
|
1169
|
+
hits=[
|
|
1170
|
+
AirbyteSearchHit[InvoicesSearchData](
|
|
1171
|
+
id=hit.get("id"),
|
|
1172
|
+
score=hit.get("score"),
|
|
1173
|
+
data=InvoicesSearchData(**hit.get("data", {}))
|
|
1174
|
+
)
|
|
1175
|
+
for hit in result.get("hits", [])
|
|
1176
|
+
],
|
|
1177
|
+
next_cursor=result.get("next_cursor"),
|
|
1178
|
+
took_ms=result.get("took_ms")
|
|
1179
|
+
)
|