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