airbyte-agent-zendesk-support 0.18.39__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/connector_model_loader.py +3 -2
- airbyte_agent_zendesk_support/_vendored/connector_sdk/executor/local_executor.py +181 -30
- airbyte_agent_zendesk_support/_vendored/connector_sdk/http_client.py +13 -6
- airbyte_agent_zendesk_support/_vendored/connector_sdk/logging/logger.py +10 -1
- airbyte_agent_zendesk_support/_vendored/connector_sdk/logging/types.py +1 -0
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/base.py +4 -1
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/connector.py +22 -33
- airbyte_agent_zendesk_support/_vendored/connector_sdk/schema/extensions.py +122 -1
- airbyte_agent_zendesk_support/_vendored/connector_sdk/validation.py +12 -6
- airbyte_agent_zendesk_support/connector.py +1073 -157
- airbyte_agent_zendesk_support/connector_model.py +3 -3
- airbyte_agent_zendesk_support/models.py +628 -69
- airbyte_agent_zendesk_support/types.py +3716 -0
- {airbyte_agent_zendesk_support-0.18.39.dist-info → airbyte_agent_zendesk_support-0.18.51.dist-info}/METADATA +12 -9
- {airbyte_agent_zendesk_support-0.18.39.dist-info → airbyte_agent_zendesk_support-0.18.51.dist-info}/RECORD +17 -17
- {airbyte_agent_zendesk_support-0.18.39.dist-info → airbyte_agent_zendesk_support-0.18.51.dist-info}/WHEEL +0 -0
|
@@ -421,10 +421,14 @@ class HTTPClient:
|
|
|
421
421
|
headers: dict[str, str] | None = None,
|
|
422
422
|
*,
|
|
423
423
|
stream: bool = False,
|
|
424
|
-
):
|
|
424
|
+
) -> tuple[dict[str, Any], dict[str, str]]:
|
|
425
425
|
"""Execute a single HTTP request attempt (no retries).
|
|
426
426
|
|
|
427
427
|
This is the core request logic, separated from retry handling.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Tuple of (response_data, response_headers) for non-streaming requests.
|
|
431
|
+
For streaming requests, returns (response_object, response_headers).
|
|
428
432
|
"""
|
|
429
433
|
# Ensure auth credentials are initialized (proactive refresh if needed)
|
|
430
434
|
await self._ensure_auth_initialized()
|
|
@@ -474,8 +478,9 @@ class HTTPClient:
|
|
|
474
478
|
request_id=request_id,
|
|
475
479
|
status_code=status_code,
|
|
476
480
|
response_body=f"<binary content, {response.headers.get('content-length', 'unknown')} bytes>",
|
|
481
|
+
response_headers=dict(response.headers),
|
|
477
482
|
)
|
|
478
|
-
return response
|
|
483
|
+
return response, dict(response.headers)
|
|
479
484
|
|
|
480
485
|
# Parse response - handle non-JSON responses gracefully
|
|
481
486
|
content_type = response.headers.get("content-type", "")
|
|
@@ -500,8 +505,9 @@ class HTTPClient:
|
|
|
500
505
|
request_id=request_id,
|
|
501
506
|
status_code=status_code,
|
|
502
507
|
response_body=response_data,
|
|
508
|
+
response_headers=dict(response.headers),
|
|
503
509
|
)
|
|
504
|
-
return response_data
|
|
510
|
+
return response_data, dict(response.headers)
|
|
505
511
|
|
|
506
512
|
except AuthenticationError as e:
|
|
507
513
|
# Auth error (401, 403) - handle token refresh
|
|
@@ -631,7 +637,7 @@ class HTTPClient:
|
|
|
631
637
|
*,
|
|
632
638
|
stream: bool = False,
|
|
633
639
|
_auth_retry_attempted: bool = False,
|
|
634
|
-
):
|
|
640
|
+
) -> tuple[dict[str, Any], dict[str, str]]:
|
|
635
641
|
"""Make an async HTTP request with optional streaming and automatic retries.
|
|
636
642
|
|
|
637
643
|
Args:
|
|
@@ -644,8 +650,9 @@ class HTTPClient:
|
|
|
644
650
|
stream: If True, do not eagerly read the body (useful for downloads)
|
|
645
651
|
|
|
646
652
|
Returns:
|
|
647
|
-
|
|
648
|
-
- If stream=
|
|
653
|
+
Tuple of (response_data, response_headers):
|
|
654
|
+
- If stream=False: (parsed JSON dict or empty dict, response headers dict)
|
|
655
|
+
- If stream=True: (response object suitable for streaming, response headers dict)
|
|
649
656
|
|
|
650
657
|
Raises:
|
|
651
658
|
HTTPStatusError: If request fails with 4xx/5xx status after all retries
|
|
@@ -134,6 +134,7 @@ class RequestLogger:
|
|
|
134
134
|
request_id: str,
|
|
135
135
|
status_code: int,
|
|
136
136
|
response_body: Any | None = None,
|
|
137
|
+
response_headers: Dict[str, str] | None = None,
|
|
137
138
|
) -> None:
|
|
138
139
|
"""
|
|
139
140
|
Log a successful HTTP response.
|
|
@@ -142,6 +143,7 @@ class RequestLogger:
|
|
|
142
143
|
request_id: ID returned from log_request
|
|
143
144
|
status_code: HTTP status code
|
|
144
145
|
response_body: Response body
|
|
146
|
+
response_headers: Response headers
|
|
145
147
|
"""
|
|
146
148
|
if request_id not in self._active_requests:
|
|
147
149
|
return
|
|
@@ -166,6 +168,7 @@ class RequestLogger:
|
|
|
166
168
|
body=request_data["body"],
|
|
167
169
|
response_status=status_code,
|
|
168
170
|
response_body=serializable_body,
|
|
171
|
+
response_headers=response_headers or {},
|
|
169
172
|
timing_ms=timing_ms,
|
|
170
173
|
)
|
|
171
174
|
|
|
@@ -243,7 +246,13 @@ class NullLogger:
|
|
|
243
246
|
"""No-op log_request."""
|
|
244
247
|
return ""
|
|
245
248
|
|
|
246
|
-
def log_response(
|
|
249
|
+
def log_response(
|
|
250
|
+
self,
|
|
251
|
+
request_id: str,
|
|
252
|
+
status_code: int,
|
|
253
|
+
response_body: Any | None = None,
|
|
254
|
+
response_headers: Dict[str, str] | None = None,
|
|
255
|
+
) -> None:
|
|
247
256
|
"""No-op log_response."""
|
|
248
257
|
pass
|
|
249
258
|
|
|
@@ -13,7 +13,7 @@ from uuid import UUID
|
|
|
13
13
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
14
14
|
from pydantic_core import Url
|
|
15
15
|
|
|
16
|
-
from .extensions import RetryConfig
|
|
16
|
+
from .extensions import CacheConfig, RetryConfig
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ExampleQuestions(BaseModel):
|
|
@@ -105,6 +105,7 @@ class Info(BaseModel):
|
|
|
105
105
|
- x-airbyte-external-documentation-urls: List of external documentation URLs (Airbyte extension)
|
|
106
106
|
- x-airbyte-retry-config: Retry configuration for transient errors (Airbyte extension)
|
|
107
107
|
- x-airbyte-example-questions: Example questions for AI connector README (Airbyte extension)
|
|
108
|
+
- x-airbyte-cache: Cache configuration for field mapping between API and cache schemas (Airbyte extension)
|
|
108
109
|
"""
|
|
109
110
|
|
|
110
111
|
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
@@ -122,6 +123,7 @@ class Info(BaseModel):
|
|
|
122
123
|
x_airbyte_external_documentation_urls: list[DocUrl] = Field(..., alias="x-airbyte-external-documentation-urls")
|
|
123
124
|
x_airbyte_retry_config: RetryConfig | None = Field(None, alias="x-airbyte-retry-config")
|
|
124
125
|
x_airbyte_example_questions: ExampleQuestions | None = Field(None, alias="x-airbyte-example-questions")
|
|
126
|
+
x_airbyte_cache: CacheConfig | None = Field(None, alias="x-airbyte-cache")
|
|
125
127
|
|
|
126
128
|
|
|
127
129
|
class ServerVariable(BaseModel):
|
|
@@ -150,6 +152,7 @@ class Server(BaseModel):
|
|
|
150
152
|
url: str
|
|
151
153
|
description: str | None = None
|
|
152
154
|
variables: Dict[str, ServerVariable] = Field(default_factory=dict)
|
|
155
|
+
x_airbyte_replication_user_config_mapping: Dict[str, str] | None = Field(default=None, alias="x-airbyte-replication-user-config-mapping")
|
|
153
156
|
|
|
154
157
|
@field_validator("url")
|
|
155
158
|
@classmethod
|
|
@@ -7,6 +7,7 @@ References:
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
from collections.abc import Iterator
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
@@ -15,7 +16,7 @@ from ..constants import OPENAPI_VERSION_PREFIX
|
|
|
15
16
|
|
|
16
17
|
from .base import Info, Server
|
|
17
18
|
from .components import Components
|
|
18
|
-
from .operations import PathItem
|
|
19
|
+
from .operations import Operation, PathItem
|
|
19
20
|
from .security import SecurityRequirement
|
|
20
21
|
|
|
21
22
|
|
|
@@ -33,6 +34,10 @@ class Tag(BaseModel):
|
|
|
33
34
|
external_docs: dict[str, Any] | None = Field(None, alias="externalDocs")
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
# HTTP methods supported by OpenAPI operations
|
|
38
|
+
HTTP_METHODS = frozenset({"get", "post", "put", "patch", "delete", "options", "head", "trace"})
|
|
39
|
+
|
|
40
|
+
|
|
36
41
|
class ExternalDocs(BaseModel):
|
|
37
42
|
"""
|
|
38
43
|
External documentation reference.
|
|
@@ -79,7 +84,7 @@ class OpenAPIConnector(BaseModel):
|
|
|
79
84
|
raise ValueError(f"OpenAPI version must be {OPENAPI_VERSION_PREFIX}x, got: {v}")
|
|
80
85
|
return v
|
|
81
86
|
|
|
82
|
-
def get_entity_operations(self, entity_name: str) -> list[tuple[str, str,
|
|
87
|
+
def get_entity_operations(self, entity_name: str) -> list[tuple[str, str, Operation]]:
|
|
83
88
|
"""
|
|
84
89
|
Get all operations for a specific entity.
|
|
85
90
|
|
|
@@ -89,22 +94,7 @@ class OpenAPIConnector(BaseModel):
|
|
|
89
94
|
Returns:
|
|
90
95
|
List of tuples: (path, method, operation)
|
|
91
96
|
"""
|
|
92
|
-
|
|
93
|
-
for path, path_item in self.paths.items():
|
|
94
|
-
for method in [
|
|
95
|
-
"get",
|
|
96
|
-
"post",
|
|
97
|
-
"put",
|
|
98
|
-
"patch",
|
|
99
|
-
"delete",
|
|
100
|
-
"options",
|
|
101
|
-
"head",
|
|
102
|
-
"trace",
|
|
103
|
-
]:
|
|
104
|
-
operation = getattr(path_item, method, None)
|
|
105
|
-
if operation and operation.x_airbyte_entity == entity_name:
|
|
106
|
-
results.append((path, method, operation))
|
|
107
|
-
return results
|
|
97
|
+
return [(path, method, op) for path, method, op in self._iter_operations() if op.x_airbyte_entity == entity_name]
|
|
108
98
|
|
|
109
99
|
def list_entities(self) -> list[str]:
|
|
110
100
|
"""
|
|
@@ -113,19 +103,18 @@ class OpenAPIConnector(BaseModel):
|
|
|
113
103
|
Returns:
|
|
114
104
|
Sorted list of unique entity names
|
|
115
105
|
"""
|
|
116
|
-
entities =
|
|
117
|
-
for path_item in self.paths.values():
|
|
118
|
-
for method in [
|
|
119
|
-
"get",
|
|
120
|
-
"post",
|
|
121
|
-
"put",
|
|
122
|
-
"patch",
|
|
123
|
-
"delete",
|
|
124
|
-
"options",
|
|
125
|
-
"head",
|
|
126
|
-
"trace",
|
|
127
|
-
]:
|
|
128
|
-
operation = getattr(path_item, method, None)
|
|
129
|
-
if operation and operation.x_airbyte_entity:
|
|
130
|
-
entities.add(operation.x_airbyte_entity)
|
|
106
|
+
entities = {op.x_airbyte_entity for _, _, op in self._iter_operations() if op.x_airbyte_entity}
|
|
131
107
|
return sorted(entities)
|
|
108
|
+
|
|
109
|
+
def _iter_operations(self) -> Iterator[tuple[str, str, Operation]]:
|
|
110
|
+
"""
|
|
111
|
+
Iterate over all operations in the spec.
|
|
112
|
+
|
|
113
|
+
Yields:
|
|
114
|
+
Tuples of (path, method, operation) for each defined operation
|
|
115
|
+
"""
|
|
116
|
+
for path, path_item in self.paths.items():
|
|
117
|
+
for method in HTTP_METHODS:
|
|
118
|
+
operation = getattr(path_item, method, None)
|
|
119
|
+
if operation:
|
|
120
|
+
yield path, method, operation
|
|
@@ -14,7 +14,7 @@ are implemented.
|
|
|
14
14
|
|
|
15
15
|
from typing import Literal
|
|
16
16
|
|
|
17
|
-
from pydantic import BaseModel, ConfigDict
|
|
17
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class PaginationConfig(BaseModel):
|
|
@@ -107,3 +107,124 @@ class RetryConfig(BaseModel):
|
|
|
107
107
|
# Header-based delay extraction
|
|
108
108
|
retry_after_header: str = "Retry-After"
|
|
109
109
|
retry_after_format: Literal["seconds", "milliseconds", "unix_timestamp"] = "seconds"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CacheFieldProperty(BaseModel):
|
|
113
|
+
"""
|
|
114
|
+
Nested property definition for object-type cache fields.
|
|
115
|
+
|
|
116
|
+
Supports recursive nesting to represent complex nested schemas in cache field definitions.
|
|
117
|
+
Used when a cache field has type 'object' and needs to define its internal structure.
|
|
118
|
+
|
|
119
|
+
Example YAML usage:
|
|
120
|
+
- name: collaboration
|
|
121
|
+
type: ['null', 'object']
|
|
122
|
+
description: "Collaboration data"
|
|
123
|
+
properties:
|
|
124
|
+
brief:
|
|
125
|
+
type: ['null', 'string']
|
|
126
|
+
comments:
|
|
127
|
+
type: ['null', 'array']
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
131
|
+
|
|
132
|
+
type: str | list[str]
|
|
133
|
+
properties: dict[str, "CacheFieldProperty"] | None = None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class CacheFieldConfig(BaseModel):
|
|
137
|
+
"""
|
|
138
|
+
Field configuration for cache mapping.
|
|
139
|
+
|
|
140
|
+
Defines a single field in a cache entity, with optional name aliasing
|
|
141
|
+
to map between user-facing field names and cache storage names.
|
|
142
|
+
|
|
143
|
+
For object-type fields, supports nested properties to define the internal structure
|
|
144
|
+
of complex nested schemas.
|
|
145
|
+
|
|
146
|
+
Used in x-airbyte-cache extension for api_search operations.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
150
|
+
|
|
151
|
+
name: str
|
|
152
|
+
x_airbyte_name: str | None = Field(default=None, alias="x-airbyte-name")
|
|
153
|
+
type: str | list[str]
|
|
154
|
+
description: str
|
|
155
|
+
properties: dict[str, CacheFieldProperty] | None = None
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def cache_name(self) -> str:
|
|
159
|
+
"""Return cache name, falling back to name if alias not specified."""
|
|
160
|
+
return self.x_airbyte_name or self.name
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class CacheEntityConfig(BaseModel):
|
|
164
|
+
"""
|
|
165
|
+
Entity configuration for cache mapping.
|
|
166
|
+
|
|
167
|
+
Defines a cache-enabled entity with its fields and optional name aliasing
|
|
168
|
+
to map between user-facing entity names and cache storage names.
|
|
169
|
+
|
|
170
|
+
Used in x-airbyte-cache extension for api_search operations.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
174
|
+
|
|
175
|
+
entity: str
|
|
176
|
+
x_airbyte_name: str | None = Field(default=None, alias="x-airbyte-name")
|
|
177
|
+
fields: list[CacheFieldConfig]
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def cache_name(self) -> str:
|
|
181
|
+
"""Return cache entity name, falling back to entity if alias not specified."""
|
|
182
|
+
return self.x_airbyte_name or self.entity
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class CacheConfig(BaseModel):
|
|
186
|
+
"""
|
|
187
|
+
Cache configuration extension (x-airbyte-cache).
|
|
188
|
+
|
|
189
|
+
Defines cache-enabled entities and their field mappings for api_search operations.
|
|
190
|
+
Supports optional name aliasing via x-airbyte-name for both entities and fields,
|
|
191
|
+
enabling bidirectional mapping between user-facing names and cache storage names.
|
|
192
|
+
|
|
193
|
+
This extension is added to the Info model and provides field-level mapping for
|
|
194
|
+
search operations that use cached data.
|
|
195
|
+
|
|
196
|
+
Example YAML usage:
|
|
197
|
+
info:
|
|
198
|
+
title: Stripe API
|
|
199
|
+
x-airbyte-cache:
|
|
200
|
+
entities:
|
|
201
|
+
- entity: customers
|
|
202
|
+
stream: customers
|
|
203
|
+
fields:
|
|
204
|
+
- name: email
|
|
205
|
+
type: ["null", "string"]
|
|
206
|
+
description: "Customer email address"
|
|
207
|
+
- name: customer_name
|
|
208
|
+
x-airbyte-name: name
|
|
209
|
+
type: ["null", "string"]
|
|
210
|
+
description: "Customer full name"
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
214
|
+
|
|
215
|
+
entities: list[CacheEntityConfig]
|
|
216
|
+
|
|
217
|
+
def get_entity_mapping(self, user_entity: str) -> CacheEntityConfig | None:
|
|
218
|
+
"""
|
|
219
|
+
Get entity config by user-facing name.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
user_entity: User-facing entity name to look up
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
CacheEntityConfig if found, None otherwise
|
|
226
|
+
"""
|
|
227
|
+
for entity in self.entities:
|
|
228
|
+
if entity.entity == user_entity:
|
|
229
|
+
return entity
|
|
230
|
+
return None
|
|
@@ -486,30 +486,36 @@ def validate_meta_extractor_fields(
|
|
|
486
486
|
response_body = spec.captured_response.body
|
|
487
487
|
|
|
488
488
|
# Validate each meta extractor field
|
|
489
|
-
for field_name,
|
|
489
|
+
for field_name, extractor_expr in endpoint.meta_extractor.items():
|
|
490
|
+
# Skip header-based extractors - they extract from headers, not response body
|
|
491
|
+
# @link.next extracts from RFC 5988 Link header
|
|
492
|
+
# @header.X-Name extracts raw header value
|
|
493
|
+
if extractor_expr.startswith("@link.") or extractor_expr.startswith("@header."):
|
|
494
|
+
continue
|
|
495
|
+
|
|
490
496
|
# Check 1: Does the JSONPath find data in the actual response?
|
|
491
497
|
try:
|
|
492
|
-
parsed_expr = parse_jsonpath(
|
|
498
|
+
parsed_expr = parse_jsonpath(extractor_expr)
|
|
493
499
|
matches = [match.value for match in parsed_expr.find(response_body)]
|
|
494
500
|
|
|
495
501
|
if not matches:
|
|
496
502
|
warnings.append(
|
|
497
503
|
f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' "
|
|
498
|
-
f"with JSONPath '{
|
|
504
|
+
f"with JSONPath '{extractor_expr}' found no matches in cassette response"
|
|
499
505
|
)
|
|
500
506
|
except Exception as e:
|
|
501
507
|
warnings.append(
|
|
502
|
-
f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' has invalid JSONPath '{
|
|
508
|
+
f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' has invalid JSONPath '{extractor_expr}': {str(e)}"
|
|
503
509
|
)
|
|
504
510
|
|
|
505
511
|
# Check 2: Is this field path declared in the response schema?
|
|
506
512
|
if endpoint.response_schema:
|
|
507
|
-
field_in_schema = _check_field_in_schema(
|
|
513
|
+
field_in_schema = _check_field_in_schema(extractor_expr, endpoint.response_schema)
|
|
508
514
|
|
|
509
515
|
if not field_in_schema:
|
|
510
516
|
warnings.append(
|
|
511
517
|
f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' "
|
|
512
|
-
f"extracts from '{
|
|
518
|
+
f"extracts from '{extractor_expr}' but this path is not declared in response schema"
|
|
513
519
|
)
|
|
514
520
|
|
|
515
521
|
except Exception as e:
|