airbyte-agent-zendesk-support 0.18.18__py3-none-any.whl → 0.18.51__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_zendesk_support/__init__.py +239 -18
- airbyte_agent_zendesk_support/_vendored/connector_sdk/auth_strategies.py +2 -5
- airbyte_agent_zendesk_support/_vendored/connector_sdk/auth_template.py +1 -1
- airbyte_agent_zendesk_support/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_zendesk_support/_vendored/connector_sdk/cloud_utils/client.py +213 -0
- airbyte_agent_zendesk_support/_vendored/connector_sdk/connector_model_loader.py +35 -8
- airbyte_agent_zendesk_support/_vendored/connector_sdk/constants.py +1 -1
- airbyte_agent_zendesk_support/_vendored/connector_sdk/executor/hosted_executor.py +92 -84
- airbyte_agent_zendesk_support/_vendored/connector_sdk/executor/local_executor.py +274 -54
- airbyte_agent_zendesk_support/_vendored/connector_sdk/extensions.py +43 -5
- airbyte_agent_zendesk_support/_vendored/connector_sdk/http/response.py +2 -0
- airbyte_agent_zendesk_support/_vendored/connector_sdk/http_client.py +63 -49
- airbyte_agent_zendesk_support/_vendored/connector_sdk/introspection.py +262 -0
- airbyte_agent_zendesk_support/_vendored/connector_sdk/logging/logger.py +19 -10
- airbyte_agent_zendesk_support/_vendored/connector_sdk/logging/types.py +11 -10
- airbyte_agent_zendesk_support/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_zendesk_support/_vendored/connector_sdk/observability/models.py +6 -6
- airbyte_agent_zendesk_support/_vendored/connector_sdk/observability/session.py +41 -32
- airbyte_agent_zendesk_support/_vendored/connector_sdk/performance/metrics.py +3 -3
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/base.py +22 -18
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/components.py +59 -58
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/connector.py +22 -33
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/extensions.py +131 -10
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/operations.py +32 -32
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/security.py +44 -34
- airbyte_agent_zendesk_support/_vendored/connector_sdk/secrets.py +2 -2
- airbyte_agent_zendesk_support/_vendored/connector_sdk/telemetry/events.py +9 -8
- airbyte_agent_zendesk_support/_vendored/connector_sdk/telemetry/tracker.py +9 -5
- airbyte_agent_zendesk_support/_vendored/connector_sdk/types.py +9 -3
- airbyte_agent_zendesk_support/_vendored/connector_sdk/validation.py +12 -6
- airbyte_agent_zendesk_support/connector.py +1170 -171
- airbyte_agent_zendesk_support/connector_model.py +7 -1
- airbyte_agent_zendesk_support/models.py +628 -69
- airbyte_agent_zendesk_support/types.py +3717 -1
- {airbyte_agent_zendesk_support-0.18.18.dist-info → airbyte_agent_zendesk_support-0.18.51.dist-info}/METADATA +55 -31
- {airbyte_agent_zendesk_support-0.18.18.dist-info → airbyte_agent_zendesk_support-0.18.51.dist-info}/RECORD +37 -33
- {airbyte_agent_zendesk_support-0.18.18.dist-info → airbyte_agent_zendesk_support-0.18.51.dist-info}/WHEEL +0 -0
|
@@ -53,42 +53,52 @@ from .models import (
|
|
|
53
53
|
ZendeskSupportExecuteResult,
|
|
54
54
|
ZendeskSupportExecuteResultWithMeta,
|
|
55
55
|
TicketsListResult,
|
|
56
|
-
TicketsGetResult,
|
|
57
56
|
UsersListResult,
|
|
58
|
-
UsersGetResult,
|
|
59
57
|
OrganizationsListResult,
|
|
60
|
-
OrganizationsGetResult,
|
|
61
58
|
GroupsListResult,
|
|
62
|
-
GroupsGetResult,
|
|
63
59
|
TicketCommentsListResult,
|
|
64
|
-
AttachmentsGetResult,
|
|
65
60
|
TicketAuditsListResult,
|
|
66
61
|
TicketMetricsListResult,
|
|
67
62
|
TicketFieldsListResult,
|
|
68
|
-
TicketFieldsGetResult,
|
|
69
63
|
BrandsListResult,
|
|
70
|
-
BrandsGetResult,
|
|
71
64
|
ViewsListResult,
|
|
72
|
-
ViewsGetResult,
|
|
73
65
|
MacrosListResult,
|
|
74
|
-
MacrosGetResult,
|
|
75
66
|
TriggersListResult,
|
|
76
|
-
TriggersGetResult,
|
|
77
67
|
AutomationsListResult,
|
|
78
|
-
AutomationsGetResult,
|
|
79
68
|
TagsListResult,
|
|
80
69
|
SatisfactionRatingsListResult,
|
|
81
|
-
SatisfactionRatingsGetResult,
|
|
82
70
|
GroupMembershipsListResult,
|
|
83
71
|
OrganizationMembershipsListResult,
|
|
84
72
|
SlaPoliciesListResult,
|
|
85
|
-
SlaPoliciesGetResult,
|
|
86
73
|
TicketFormsListResult,
|
|
87
|
-
TicketFormsGetResult,
|
|
88
74
|
ArticlesListResult,
|
|
89
|
-
ArticlesGetResult,
|
|
90
75
|
ArticleAttachmentsListResult,
|
|
91
|
-
|
|
76
|
+
AirbyteSearchHit,
|
|
77
|
+
AirbyteSearchResult,
|
|
78
|
+
BrandsSearchData,
|
|
79
|
+
BrandsSearchResult,
|
|
80
|
+
GroupsSearchData,
|
|
81
|
+
GroupsSearchResult,
|
|
82
|
+
OrganizationsSearchData,
|
|
83
|
+
OrganizationsSearchResult,
|
|
84
|
+
SatisfactionRatingsSearchData,
|
|
85
|
+
SatisfactionRatingsSearchResult,
|
|
86
|
+
TagsSearchData,
|
|
87
|
+
TagsSearchResult,
|
|
88
|
+
TicketAuditsSearchData,
|
|
89
|
+
TicketAuditsSearchResult,
|
|
90
|
+
TicketCommentsSearchData,
|
|
91
|
+
TicketCommentsSearchResult,
|
|
92
|
+
TicketFieldsSearchData,
|
|
93
|
+
TicketFieldsSearchResult,
|
|
94
|
+
TicketFormsSearchData,
|
|
95
|
+
TicketFormsSearchResult,
|
|
96
|
+
TicketMetricsSearchData,
|
|
97
|
+
TicketMetricsSearchResult,
|
|
98
|
+
TicketsSearchData,
|
|
99
|
+
TicketsSearchResult,
|
|
100
|
+
UsersSearchData,
|
|
101
|
+
UsersSearchResult
|
|
92
102
|
)
|
|
93
103
|
from .types import (
|
|
94
104
|
TicketsListParams,
|
|
@@ -130,7 +140,218 @@ from .types import (
|
|
|
130
140
|
ArticlesGetParams,
|
|
131
141
|
ArticleAttachmentsListParams,
|
|
132
142
|
ArticleAttachmentsGetParams,
|
|
133
|
-
ArticleAttachmentsDownloadParams
|
|
143
|
+
ArticleAttachmentsDownloadParams,
|
|
144
|
+
AirbyteSearchParams,
|
|
145
|
+
AirbyteSortOrder,
|
|
146
|
+
BrandsSearchFilter,
|
|
147
|
+
BrandsSearchQuery,
|
|
148
|
+
BrandsCondition,
|
|
149
|
+
GroupsSearchFilter,
|
|
150
|
+
GroupsSearchQuery,
|
|
151
|
+
GroupsCondition,
|
|
152
|
+
OrganizationsSearchFilter,
|
|
153
|
+
OrganizationsSearchQuery,
|
|
154
|
+
OrganizationsCondition,
|
|
155
|
+
SatisfactionRatingsSearchFilter,
|
|
156
|
+
SatisfactionRatingsSearchQuery,
|
|
157
|
+
SatisfactionRatingsCondition,
|
|
158
|
+
TagsSearchFilter,
|
|
159
|
+
TagsSearchQuery,
|
|
160
|
+
TagsCondition,
|
|
161
|
+
TicketAuditsSearchFilter,
|
|
162
|
+
TicketAuditsSearchQuery,
|
|
163
|
+
TicketAuditsCondition,
|
|
164
|
+
TicketCommentsSearchFilter,
|
|
165
|
+
TicketCommentsSearchQuery,
|
|
166
|
+
TicketCommentsCondition,
|
|
167
|
+
TicketFieldsSearchFilter,
|
|
168
|
+
TicketFieldsSearchQuery,
|
|
169
|
+
TicketFieldsCondition,
|
|
170
|
+
TicketFormsSearchFilter,
|
|
171
|
+
TicketFormsSearchQuery,
|
|
172
|
+
TicketFormsCondition,
|
|
173
|
+
TicketMetricsSearchFilter,
|
|
174
|
+
TicketMetricsSearchQuery,
|
|
175
|
+
TicketMetricsCondition,
|
|
176
|
+
TicketsSearchFilter,
|
|
177
|
+
TicketsSearchQuery,
|
|
178
|
+
TicketsCondition,
|
|
179
|
+
UsersSearchFilter,
|
|
180
|
+
UsersSearchQuery,
|
|
181
|
+
UsersCondition
|
|
134
182
|
)
|
|
135
183
|
|
|
136
|
-
__all__ = [
|
|
184
|
+
__all__ = [
|
|
185
|
+
"ZendeskSupportConnector",
|
|
186
|
+
"ZendeskSupportAuthConfig",
|
|
187
|
+
"Ticket",
|
|
188
|
+
"User",
|
|
189
|
+
"Organization",
|
|
190
|
+
"Group",
|
|
191
|
+
"TicketComment",
|
|
192
|
+
"Attachment",
|
|
193
|
+
"TicketAudit",
|
|
194
|
+
"TicketMetric",
|
|
195
|
+
"TicketField",
|
|
196
|
+
"Brand",
|
|
197
|
+
"View",
|
|
198
|
+
"Macro",
|
|
199
|
+
"Trigger",
|
|
200
|
+
"Automation",
|
|
201
|
+
"Tag",
|
|
202
|
+
"SatisfactionRating",
|
|
203
|
+
"GroupMembership",
|
|
204
|
+
"OrganizationMembership",
|
|
205
|
+
"SLAPolicy",
|
|
206
|
+
"TicketForm",
|
|
207
|
+
"Article",
|
|
208
|
+
"ArticleAttachment",
|
|
209
|
+
"TicketsListResultMeta",
|
|
210
|
+
"UsersListResultMeta",
|
|
211
|
+
"OrganizationsListResultMeta",
|
|
212
|
+
"GroupsListResultMeta",
|
|
213
|
+
"TicketCommentsListResultMeta",
|
|
214
|
+
"TicketAuditsListResultMeta",
|
|
215
|
+
"TicketMetricsListResultMeta",
|
|
216
|
+
"TicketFieldsListResultMeta",
|
|
217
|
+
"BrandsListResultMeta",
|
|
218
|
+
"ViewsListResultMeta",
|
|
219
|
+
"MacrosListResultMeta",
|
|
220
|
+
"TriggersListResultMeta",
|
|
221
|
+
"AutomationsListResultMeta",
|
|
222
|
+
"TagsListResultMeta",
|
|
223
|
+
"SatisfactionRatingsListResultMeta",
|
|
224
|
+
"GroupMembershipsListResultMeta",
|
|
225
|
+
"OrganizationMembershipsListResultMeta",
|
|
226
|
+
"SlaPoliciesListResultMeta",
|
|
227
|
+
"TicketFormsListResultMeta",
|
|
228
|
+
"ArticlesListResultMeta",
|
|
229
|
+
"ArticleAttachmentsListResultMeta",
|
|
230
|
+
"ZendeskSupportExecuteResult",
|
|
231
|
+
"ZendeskSupportExecuteResultWithMeta",
|
|
232
|
+
"TicketsListResult",
|
|
233
|
+
"UsersListResult",
|
|
234
|
+
"OrganizationsListResult",
|
|
235
|
+
"GroupsListResult",
|
|
236
|
+
"TicketCommentsListResult",
|
|
237
|
+
"TicketAuditsListResult",
|
|
238
|
+
"TicketMetricsListResult",
|
|
239
|
+
"TicketFieldsListResult",
|
|
240
|
+
"BrandsListResult",
|
|
241
|
+
"ViewsListResult",
|
|
242
|
+
"MacrosListResult",
|
|
243
|
+
"TriggersListResult",
|
|
244
|
+
"AutomationsListResult",
|
|
245
|
+
"TagsListResult",
|
|
246
|
+
"SatisfactionRatingsListResult",
|
|
247
|
+
"GroupMembershipsListResult",
|
|
248
|
+
"OrganizationMembershipsListResult",
|
|
249
|
+
"SlaPoliciesListResult",
|
|
250
|
+
"TicketFormsListResult",
|
|
251
|
+
"ArticlesListResult",
|
|
252
|
+
"ArticleAttachmentsListResult",
|
|
253
|
+
"AirbyteSearchHit",
|
|
254
|
+
"AirbyteSearchResult",
|
|
255
|
+
"BrandsSearchData",
|
|
256
|
+
"BrandsSearchResult",
|
|
257
|
+
"GroupsSearchData",
|
|
258
|
+
"GroupsSearchResult",
|
|
259
|
+
"OrganizationsSearchData",
|
|
260
|
+
"OrganizationsSearchResult",
|
|
261
|
+
"SatisfactionRatingsSearchData",
|
|
262
|
+
"SatisfactionRatingsSearchResult",
|
|
263
|
+
"TagsSearchData",
|
|
264
|
+
"TagsSearchResult",
|
|
265
|
+
"TicketAuditsSearchData",
|
|
266
|
+
"TicketAuditsSearchResult",
|
|
267
|
+
"TicketCommentsSearchData",
|
|
268
|
+
"TicketCommentsSearchResult",
|
|
269
|
+
"TicketFieldsSearchData",
|
|
270
|
+
"TicketFieldsSearchResult",
|
|
271
|
+
"TicketFormsSearchData",
|
|
272
|
+
"TicketFormsSearchResult",
|
|
273
|
+
"TicketMetricsSearchData",
|
|
274
|
+
"TicketMetricsSearchResult",
|
|
275
|
+
"TicketsSearchData",
|
|
276
|
+
"TicketsSearchResult",
|
|
277
|
+
"UsersSearchData",
|
|
278
|
+
"UsersSearchResult",
|
|
279
|
+
"TicketsListParams",
|
|
280
|
+
"TicketsGetParams",
|
|
281
|
+
"UsersListParams",
|
|
282
|
+
"UsersGetParams",
|
|
283
|
+
"OrganizationsListParams",
|
|
284
|
+
"OrganizationsGetParams",
|
|
285
|
+
"GroupsListParams",
|
|
286
|
+
"GroupsGetParams",
|
|
287
|
+
"TicketCommentsListParams",
|
|
288
|
+
"AttachmentsGetParams",
|
|
289
|
+
"AttachmentsDownloadParams",
|
|
290
|
+
"TicketAuditsListParams",
|
|
291
|
+
"TicketAuditsListParams",
|
|
292
|
+
"TicketMetricsListParams",
|
|
293
|
+
"TicketFieldsListParams",
|
|
294
|
+
"TicketFieldsGetParams",
|
|
295
|
+
"BrandsListParams",
|
|
296
|
+
"BrandsGetParams",
|
|
297
|
+
"ViewsListParams",
|
|
298
|
+
"ViewsGetParams",
|
|
299
|
+
"MacrosListParams",
|
|
300
|
+
"MacrosGetParams",
|
|
301
|
+
"TriggersListParams",
|
|
302
|
+
"TriggersGetParams",
|
|
303
|
+
"AutomationsListParams",
|
|
304
|
+
"AutomationsGetParams",
|
|
305
|
+
"TagsListParams",
|
|
306
|
+
"SatisfactionRatingsListParams",
|
|
307
|
+
"SatisfactionRatingsGetParams",
|
|
308
|
+
"GroupMembershipsListParams",
|
|
309
|
+
"OrganizationMembershipsListParams",
|
|
310
|
+
"SlaPoliciesListParams",
|
|
311
|
+
"SlaPoliciesGetParams",
|
|
312
|
+
"TicketFormsListParams",
|
|
313
|
+
"TicketFormsGetParams",
|
|
314
|
+
"ArticlesListParams",
|
|
315
|
+
"ArticlesGetParams",
|
|
316
|
+
"ArticleAttachmentsListParams",
|
|
317
|
+
"ArticleAttachmentsGetParams",
|
|
318
|
+
"ArticleAttachmentsDownloadParams",
|
|
319
|
+
"AirbyteSearchParams",
|
|
320
|
+
"AirbyteSortOrder",
|
|
321
|
+
"BrandsSearchFilter",
|
|
322
|
+
"BrandsSearchQuery",
|
|
323
|
+
"BrandsCondition",
|
|
324
|
+
"GroupsSearchFilter",
|
|
325
|
+
"GroupsSearchQuery",
|
|
326
|
+
"GroupsCondition",
|
|
327
|
+
"OrganizationsSearchFilter",
|
|
328
|
+
"OrganizationsSearchQuery",
|
|
329
|
+
"OrganizationsCondition",
|
|
330
|
+
"SatisfactionRatingsSearchFilter",
|
|
331
|
+
"SatisfactionRatingsSearchQuery",
|
|
332
|
+
"SatisfactionRatingsCondition",
|
|
333
|
+
"TagsSearchFilter",
|
|
334
|
+
"TagsSearchQuery",
|
|
335
|
+
"TagsCondition",
|
|
336
|
+
"TicketAuditsSearchFilter",
|
|
337
|
+
"TicketAuditsSearchQuery",
|
|
338
|
+
"TicketAuditsCondition",
|
|
339
|
+
"TicketCommentsSearchFilter",
|
|
340
|
+
"TicketCommentsSearchQuery",
|
|
341
|
+
"TicketCommentsCondition",
|
|
342
|
+
"TicketFieldsSearchFilter",
|
|
343
|
+
"TicketFieldsSearchQuery",
|
|
344
|
+
"TicketFieldsCondition",
|
|
345
|
+
"TicketFormsSearchFilter",
|
|
346
|
+
"TicketFormsSearchQuery",
|
|
347
|
+
"TicketFormsCondition",
|
|
348
|
+
"TicketMetricsSearchFilter",
|
|
349
|
+
"TicketMetricsSearchQuery",
|
|
350
|
+
"TicketMetricsCondition",
|
|
351
|
+
"TicketsSearchFilter",
|
|
352
|
+
"TicketsSearchQuery",
|
|
353
|
+
"TicketsCondition",
|
|
354
|
+
"UsersSearchFilter",
|
|
355
|
+
"UsersSearchQuery",
|
|
356
|
+
"UsersCondition",
|
|
357
|
+
]
|
|
@@ -610,9 +610,7 @@ class OAuth2AuthStrategy(AuthStrategy):
|
|
|
610
610
|
has_refresh_token = bool(secrets.get("refresh_token"))
|
|
611
611
|
|
|
612
612
|
if not has_access_token and not has_refresh_token:
|
|
613
|
-
raise AuthenticationError(
|
|
614
|
-
"Missing OAuth2 credentials. Provide either 'access_token' " "or 'refresh_token' (for refresh-token-only mode)."
|
|
615
|
-
)
|
|
613
|
+
raise AuthenticationError("Missing OAuth2 credentials. Provide either 'access_token' or 'refresh_token' (for refresh-token-only mode).")
|
|
616
614
|
|
|
617
615
|
def can_refresh(self, secrets: OAuth2RefreshSecrets) -> bool:
|
|
618
616
|
"""Check if token refresh is possible.
|
|
@@ -1106,8 +1104,7 @@ class AuthStrategyFactory:
|
|
|
1106
1104
|
strategy = cls._strategies.get(auth_type)
|
|
1107
1105
|
if strategy is None:
|
|
1108
1106
|
raise AuthenticationError(
|
|
1109
|
-
f"Authentication type '{auth_type.value}' is not implemented. "
|
|
1110
|
-
f"Supported types: {', '.join(s.value for s in cls._strategies.keys())}"
|
|
1107
|
+
f"Authentication type '{auth_type.value}' is not implemented. Supported types: {', '.join(s.value for s in cls._strategies.keys())}"
|
|
1111
1108
|
)
|
|
1112
1109
|
return strategy
|
|
1113
1110
|
|
|
@@ -17,7 +17,7 @@ class MissingVariableError(ValueError):
|
|
|
17
17
|
def __init__(self, var_name: str, available_fields: list):
|
|
18
18
|
self.var_name = var_name
|
|
19
19
|
self.available_fields = available_fields
|
|
20
|
-
super().__init__(f"Template variable '${{{var_name}}}' not found in config.
|
|
20
|
+
super().__init__(f"Template variable '${{{var_name}}}' not found in config. Available fields: {available_fields}")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def apply_template(template: str, values: Dict[str, str]) -> str:
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""AirbyteCloudClient for Airbyte Platform API integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AirbyteCloudClient:
|
|
12
|
+
"""Client for interacting with Airbyte Platform APIs.
|
|
13
|
+
|
|
14
|
+
Handles authentication, token caching, and API calls to:
|
|
15
|
+
- Get bearer tokens for authentication
|
|
16
|
+
- Look up connectors for users
|
|
17
|
+
- Execute connectors via the cloud API
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
client = AirbyteCloudClient(
|
|
21
|
+
client_id="your-client-id",
|
|
22
|
+
client_secret="your-client-secret"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Get a connector ID
|
|
26
|
+
connector_id = await client.get_connector_id(
|
|
27
|
+
external_user_id="user-123",
|
|
28
|
+
connector_definition_id="550e8400-e29b-41d4-a716-446655440000"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Execute the connector
|
|
32
|
+
result = await client.execute_connector(
|
|
33
|
+
connector_id=connector_id,
|
|
34
|
+
entity="customers",
|
|
35
|
+
action="list",
|
|
36
|
+
params={"limit": 10}
|
|
37
|
+
)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
AUTH_BASE_URL = "https://cloud.airbyte.com" # For token endpoint
|
|
41
|
+
API_BASE_URL = "https://api.airbyte.ai" # For instance lookup & execution
|
|
42
|
+
|
|
43
|
+
def __init__(self, client_id: str, client_secret: str):
|
|
44
|
+
"""Initialize AirbyteCloudClient.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
client_id: Airbyte client ID for authentication
|
|
48
|
+
client_secret: Airbyte client secret for authentication
|
|
49
|
+
"""
|
|
50
|
+
self._client_id = client_id
|
|
51
|
+
self._client_secret = client_secret
|
|
52
|
+
|
|
53
|
+
# Token cache (instance-level)
|
|
54
|
+
self._cached_token: str | None = None
|
|
55
|
+
self._token_expires_at: datetime | None = None
|
|
56
|
+
self._http_client = httpx.AsyncClient(
|
|
57
|
+
timeout=httpx.Timeout(300.0), # 5 minute timeout
|
|
58
|
+
follow_redirects=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async def get_bearer_token(self) -> str:
|
|
62
|
+
"""Get bearer token for API authentication.
|
|
63
|
+
|
|
64
|
+
Caches the token and only requests a new one when the cached token
|
|
65
|
+
is expired or missing. Adds a 60-second buffer before expiration
|
|
66
|
+
to avoid edge cases.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Bearer token string
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
httpx.HTTPStatusError: If the token request fails with 4xx/5xx
|
|
73
|
+
httpx.RequestError: If the network request fails
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
token = await client.get_bearer_token()
|
|
77
|
+
# Use token in Authorization header: f"Bearer {token}"
|
|
78
|
+
"""
|
|
79
|
+
# Check if we have a cached token that hasn't expired
|
|
80
|
+
if self._cached_token and self._token_expires_at:
|
|
81
|
+
# Add 60 second buffer before expiration to avoid edge cases
|
|
82
|
+
now = datetime.now()
|
|
83
|
+
if now < self._token_expires_at:
|
|
84
|
+
# Token is still valid, return cached version
|
|
85
|
+
return self._cached_token
|
|
86
|
+
|
|
87
|
+
# Token is missing or expired, fetch a new one
|
|
88
|
+
url = f"{self.AUTH_BASE_URL}/api/v1/applications/token"
|
|
89
|
+
request_body = {
|
|
90
|
+
"client_id": self._client_id,
|
|
91
|
+
"client_secret": self._client_secret,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
response = await self._http_client.post(url, json=request_body)
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
|
|
97
|
+
data = response.json()
|
|
98
|
+
access_token = data["access_token"]
|
|
99
|
+
expires_in = 15 * 60 # default 15 min expiry time * 60 seconds
|
|
100
|
+
|
|
101
|
+
# Calculate expiration time with 60 second buffer
|
|
102
|
+
expires_at = datetime.now() + timedelta(seconds=expires_in - 60)
|
|
103
|
+
self._cached_token = access_token
|
|
104
|
+
self._token_expires_at = expires_at
|
|
105
|
+
|
|
106
|
+
return access_token
|
|
107
|
+
|
|
108
|
+
async def get_connector_id(
|
|
109
|
+
self,
|
|
110
|
+
external_user_id: str,
|
|
111
|
+
connector_definition_id: str,
|
|
112
|
+
) -> str:
|
|
113
|
+
"""Get connector ID for a user.
|
|
114
|
+
|
|
115
|
+
Looks up the connector that belongs to the specified user
|
|
116
|
+
and connector definition. Validates that exactly one connector exists.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
external_user_id: User identifier in the Airbyte system
|
|
120
|
+
connector_definition_id: UUID of the connector definition
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Connector ID (UUID string)
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValueError: If 0 or more than 1 connector is found
|
|
127
|
+
httpx.HTTPStatusError: If API returns 4xx/5xx status code
|
|
128
|
+
httpx.RequestError: If network request fails
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
connector_id = await client.get_connector_id(
|
|
132
|
+
external_user_id="user-123",
|
|
133
|
+
connector_definition_id="550e8400-e29b-41d4-a716-446655440000"
|
|
134
|
+
)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
token = await self.get_bearer_token()
|
|
138
|
+
url = f"{self.API_BASE_URL}/api/v1/connectors/connectors_for_user"
|
|
139
|
+
params = {
|
|
140
|
+
"external_user_id": external_user_id,
|
|
141
|
+
"definition_id": connector_definition_id,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
145
|
+
response = await self._http_client.get(url, params=params, headers=headers)
|
|
146
|
+
response.raise_for_status()
|
|
147
|
+
|
|
148
|
+
data = response.json()
|
|
149
|
+
connectors = data["connectors"]
|
|
150
|
+
|
|
151
|
+
if len(connectors) == 0:
|
|
152
|
+
raise ValueError(f"No connector found for user '{external_user_id}' and connector definition '{connector_definition_id}'")
|
|
153
|
+
|
|
154
|
+
if len(connectors) > 1:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"Multiple connectors found for user '{external_user_id}' "
|
|
157
|
+
f"and connector definition '{connector_definition_id}'. Expected exactly 1, "
|
|
158
|
+
f"found {len(connectors)}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
connector_id = connectors[0]["id"]
|
|
162
|
+
return connector_id
|
|
163
|
+
|
|
164
|
+
async def execute_connector(
|
|
165
|
+
self,
|
|
166
|
+
connector_id: str,
|
|
167
|
+
entity: str,
|
|
168
|
+
action: str,
|
|
169
|
+
params: dict[str, Any] | None,
|
|
170
|
+
) -> dict[str, Any]:
|
|
171
|
+
"""Execute a connector operation.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
connector_id: Connector UUID (source ID)
|
|
175
|
+
entity: Entity name (e.g., "customers", "invoices")
|
|
176
|
+
action: Operation action (e.g., "list", "get", "create")
|
|
177
|
+
params: Optional parameters for the operation
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Raw JSON response dict from the API
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
httpx.HTTPStatusError: If API returns 4xx/5xx status code
|
|
184
|
+
httpx.RequestError: If network request fails
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
result = await client.execute_connector(
|
|
188
|
+
connector_id="550e8400-e29b-41d4-a716-446655440000",
|
|
189
|
+
entity="customers",
|
|
190
|
+
action="list",
|
|
191
|
+
params={"limit": 10}
|
|
192
|
+
)
|
|
193
|
+
"""
|
|
194
|
+
token = await self.get_bearer_token()
|
|
195
|
+
url = f"{self.API_BASE_URL}/api/v1/connectors/sources/{connector_id}/execute"
|
|
196
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
197
|
+
request_body = {
|
|
198
|
+
"entity": entity,
|
|
199
|
+
"action": action,
|
|
200
|
+
"params": params,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
response = await self._http_client.post(url, json=request_body, headers=headers)
|
|
204
|
+
response.raise_for_status()
|
|
205
|
+
|
|
206
|
+
return response.json()
|
|
207
|
+
|
|
208
|
+
async def close(self):
|
|
209
|
+
"""Close the HTTP client.
|
|
210
|
+
|
|
211
|
+
Call this when you're done using the client to clean up resources.
|
|
212
|
+
"""
|
|
213
|
+
await self._http_client.aclose()
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
import re
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any
|
|
9
|
+
from uuid import UUID
|
|
8
10
|
|
|
9
11
|
import jsonref
|
|
10
12
|
import yaml
|
|
@@ -105,7 +107,7 @@ def resolve_schema_refs(schema: Any, spec_dict: dict) -> dict[str, Any]:
|
|
|
105
107
|
|
|
106
108
|
try:
|
|
107
109
|
# Resolve all references
|
|
108
|
-
resolved_spec = jsonref.replace_refs(
|
|
110
|
+
resolved_spec = jsonref.replace_refs( # type: ignore[union-attr]
|
|
109
111
|
temp_spec,
|
|
110
112
|
base_uri="",
|
|
111
113
|
jsonschema=True, # Use JSONSchema draft 7 semantics
|
|
@@ -117,9 +119,11 @@ def resolve_schema_refs(schema: Any, spec_dict: dict) -> dict[str, Any]:
|
|
|
117
119
|
|
|
118
120
|
# Remove any remaining jsonref proxy objects by converting to plain dict
|
|
119
121
|
return _deproxy_schema(resolved_schema)
|
|
120
|
-
except (
|
|
122
|
+
except (AttributeError, KeyError, RecursionError, Exception):
|
|
121
123
|
# If resolution fails, return the original schema
|
|
122
124
|
# This allows the system to continue even with malformed $refs
|
|
125
|
+
# AttributeError covers the case where jsonref might be None
|
|
126
|
+
# Exception catches jsonref.JsonRefError and other jsonref exceptions
|
|
123
127
|
return schema_dict
|
|
124
128
|
|
|
125
129
|
|
|
@@ -390,23 +394,35 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
390
394
|
for entity_name, endpoints_dict in entities_map.items():
|
|
391
395
|
actions = list(endpoints_dict.keys())
|
|
392
396
|
|
|
393
|
-
# Get schema from components if available
|
|
397
|
+
# Get schema and stream_name from components if available
|
|
394
398
|
schema = None
|
|
399
|
+
entity_stream_name = None
|
|
395
400
|
if spec.components:
|
|
396
401
|
# Look for a schema matching the entity name
|
|
397
402
|
for schema_name, schema_def in spec.components.schemas.items():
|
|
398
403
|
if schema_def.x_airbyte_entity_name == entity_name or schema_name.lower() == entity_name.lower():
|
|
399
404
|
schema = schema_def.model_dump(by_alias=True)
|
|
405
|
+
entity_stream_name = schema_def.x_airbyte_stream_name
|
|
400
406
|
break
|
|
401
407
|
|
|
402
|
-
entity = EntityDefinition(
|
|
408
|
+
entity = EntityDefinition(
|
|
409
|
+
name=entity_name,
|
|
410
|
+
stream_name=entity_stream_name,
|
|
411
|
+
actions=actions,
|
|
412
|
+
endpoints=endpoints_dict,
|
|
413
|
+
schema=schema,
|
|
414
|
+
)
|
|
403
415
|
entities.append(entity)
|
|
404
416
|
|
|
405
417
|
# Extract retry config from x-airbyte-retry-config extension
|
|
406
418
|
retry_config = spec.info.x_airbyte_retry_config
|
|
419
|
+
connector_id = spec.info.x_airbyte_connector_id
|
|
420
|
+
if not connector_id:
|
|
421
|
+
raise InvalidOpenAPIError("Missing required x-airbyte-connector-id field")
|
|
407
422
|
|
|
408
423
|
# Create ConnectorModel
|
|
409
424
|
model = ConnectorModel(
|
|
425
|
+
id=connector_id,
|
|
410
426
|
name=name,
|
|
411
427
|
version=version,
|
|
412
428
|
base_url=base_url,
|
|
@@ -503,13 +519,14 @@ def _parse_oauth2_config(scheme: Any) -> dict[str, str]:
|
|
|
503
519
|
config["refresh_url"] = refresh_url
|
|
504
520
|
|
|
505
521
|
# Extract custom refresh configuration from x-airbyte-token-refresh extension
|
|
522
|
+
# Note: x_token_refresh is a Dict[str, Any], not a Pydantic model, so use .get()
|
|
506
523
|
x_token_refresh = getattr(scheme, "x_token_refresh", None)
|
|
507
524
|
if x_token_refresh:
|
|
508
|
-
auth_style =
|
|
525
|
+
auth_style = x_token_refresh.get("auth_style")
|
|
509
526
|
if auth_style:
|
|
510
527
|
config["auth_style"] = auth_style
|
|
511
528
|
|
|
512
|
-
body_format =
|
|
529
|
+
body_format = x_token_refresh.get("body_format")
|
|
513
530
|
if body_format:
|
|
514
531
|
config["body_format"] = body_format
|
|
515
532
|
|
|
@@ -753,8 +770,6 @@ def _parse_auth_from_openapi(spec: OpenAPIConnector) -> AuthConfig:
|
|
|
753
770
|
options.append(auth_option)
|
|
754
771
|
except Exception as e:
|
|
755
772
|
# Log warning but continue - skip invalid schemes
|
|
756
|
-
import logging
|
|
757
|
-
|
|
758
773
|
logger = logging.getLogger(__name__)
|
|
759
774
|
logger.warning(f"Skipping invalid security scheme '{scheme_name}': {e}")
|
|
760
775
|
continue
|
|
@@ -926,8 +941,20 @@ def load_connector_model(definition_path: str | Path) -> ConnectorModel:
|
|
|
926
941
|
)
|
|
927
942
|
entities.append(entity)
|
|
928
943
|
|
|
944
|
+
# Get connector ID
|
|
945
|
+
connector_id_value = connector_meta.get("id")
|
|
946
|
+
if connector_id_value:
|
|
947
|
+
# Try to parse as UUID (handles string UUIDs)
|
|
948
|
+
if isinstance(connector_id_value, str):
|
|
949
|
+
connector_id = UUID(connector_id_value)
|
|
950
|
+
else:
|
|
951
|
+
connector_id = connector_id_value
|
|
952
|
+
else:
|
|
953
|
+
raise ValueError
|
|
954
|
+
|
|
929
955
|
# Build ConnectorModel
|
|
930
956
|
model = ConnectorModel(
|
|
957
|
+
id=connector_id,
|
|
931
958
|
name=connector_meta["name"],
|
|
932
959
|
version=connector_meta.get("version", OPENAPI_DEFAULT_VERSION),
|
|
933
960
|
base_url=raw_definition.get("base_url", connector_meta.get("base_url", "")),
|