airbyte-agent-mailchimp 0.1.4__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_mailchimp/__init__.py +217 -0
- airbyte_agent_mailchimp/_vendored/__init__.py +1 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/__init__.py +82 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/auth_strategies.py +1120 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/auth_template.py +135 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/cloud_utils/client.py +213 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/connector_model_loader.py +965 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/constants.py +78 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/exceptions.py +23 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/executor/__init__.py +31 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/executor/hosted_executor.py +196 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/executor/local_executor.py +1641 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/executor/models.py +190 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/extensions.py +693 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/__init__.py +37 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/config.py +98 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/exceptions.py +119 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/protocols.py +114 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http/response.py +104 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/http_client.py +686 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/introspection.py +262 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/logging/__init__.py +11 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/logging/logger.py +264 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/logging/types.py +92 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/observability/__init__.py +11 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/observability/models.py +19 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/observability/redactor.py +81 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/observability/session.py +103 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/performance/__init__.py +6 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/performance/instrumentation.py +57 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/performance/metrics.py +93 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/__init__.py +75 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/base.py +164 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/components.py +239 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/connector.py +120 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/extensions.py +230 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/operations.py +146 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/schema/security.py +223 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/secrets.py +182 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/telemetry/__init__.py +10 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/telemetry/config.py +32 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/telemetry/events.py +59 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/telemetry/tracker.py +155 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/types.py +245 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/utils.py +60 -0
- airbyte_agent_mailchimp/_vendored/connector_sdk/validation.py +822 -0
- airbyte_agent_mailchimp/connector.py +1378 -0
- airbyte_agent_mailchimp/connector_model.py +4749 -0
- airbyte_agent_mailchimp/models.py +956 -0
- airbyte_agent_mailchimp/types.py +164 -0
- airbyte_agent_mailchimp-0.1.4.dist-info/METADATA +119 -0
- airbyte_agent_mailchimp-0.1.4.dist-info/RECORD +57 -0
- airbyte_agent_mailchimp-0.1.4.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Extension models for future features.
|
|
3
|
+
|
|
4
|
+
These models are defined but NOT yet added to the main schema models.
|
|
5
|
+
They serve as:
|
|
6
|
+
1. Type hints for future use
|
|
7
|
+
2. Documentation of planned extensions
|
|
8
|
+
3. Ready-to-use structures when features are implemented
|
|
9
|
+
|
|
10
|
+
NOTE: These are not currently active in the schema. They will be added
|
|
11
|
+
to Operation, Schema, or other models when their respective features
|
|
12
|
+
are implemented.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PaginationConfig(BaseModel):
|
|
21
|
+
"""
|
|
22
|
+
Configuration for pagination support.
|
|
23
|
+
|
|
24
|
+
NOT YET USED - Defined for future implementation.
|
|
25
|
+
|
|
26
|
+
When active, will be added to Operation model as:
|
|
27
|
+
x_pagination: Optional[PaginationConfig] = Field(None, alias="x-pagination")
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
31
|
+
|
|
32
|
+
style: Literal["cursor", "offset", "page", "link"]
|
|
33
|
+
limit_param: str = "limit"
|
|
34
|
+
|
|
35
|
+
# Cursor-based pagination
|
|
36
|
+
cursor_param: str | None = None
|
|
37
|
+
cursor_source: Literal["body", "headers"] | None = "body"
|
|
38
|
+
cursor_path: str | None = None
|
|
39
|
+
|
|
40
|
+
# Offset-based pagination
|
|
41
|
+
offset_param: str | None = None
|
|
42
|
+
|
|
43
|
+
# Page-based pagination
|
|
44
|
+
page_param: str | None = None
|
|
45
|
+
|
|
46
|
+
# Response parsing
|
|
47
|
+
data_path: str = "data"
|
|
48
|
+
has_more_path: str | None = None
|
|
49
|
+
|
|
50
|
+
# Limits
|
|
51
|
+
max_page_size: int | None = None
|
|
52
|
+
default_page_size: int = 100
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RateLimitConfig(BaseModel):
|
|
56
|
+
"""
|
|
57
|
+
Configuration for rate limiting.
|
|
58
|
+
|
|
59
|
+
NOT YET USED - Defined for future implementation.
|
|
60
|
+
|
|
61
|
+
When active, might be added to Server or root OpenAPIConnector as:
|
|
62
|
+
x_rate_limit: Optional[RateLimitConfig] = Field(None, alias="x-rate-limit")
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
66
|
+
|
|
67
|
+
max_requests: int
|
|
68
|
+
time_window_seconds: int
|
|
69
|
+
retry_after_header: str | None = "Retry-After"
|
|
70
|
+
respect_retry_after: bool = True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RetryConfig(BaseModel):
|
|
74
|
+
"""
|
|
75
|
+
Configuration for retry strategy with exponential backoff.
|
|
76
|
+
|
|
77
|
+
Used to configure automatic retries for transient errors (429, 5xx, timeouts, network errors).
|
|
78
|
+
Can be specified at the connector level via x-airbyte-retry-config in the OpenAPI spec's info section.
|
|
79
|
+
|
|
80
|
+
By default, retries are enabled with max_attempts=3. To disable retries, set max_attempts=1
|
|
81
|
+
in your connector's x-airbyte-retry-config.
|
|
82
|
+
|
|
83
|
+
Example YAML usage:
|
|
84
|
+
info:
|
|
85
|
+
title: My API
|
|
86
|
+
x-airbyte-retry-config:
|
|
87
|
+
max_attempts: 5
|
|
88
|
+
initial_delay_seconds: 2.0
|
|
89
|
+
retry_after_header: "X-RateLimit-Reset"
|
|
90
|
+
retry_after_format: "unix_timestamp"
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
94
|
+
|
|
95
|
+
# Core retry settings (max_attempts=3 enables retries by default)
|
|
96
|
+
max_attempts: int = 3
|
|
97
|
+
initial_delay_seconds: float = 1.0
|
|
98
|
+
max_delay_seconds: float = 60.0
|
|
99
|
+
exponential_base: float = 2.0
|
|
100
|
+
jitter: bool = True
|
|
101
|
+
|
|
102
|
+
# Which errors to retry
|
|
103
|
+
retry_on_status_codes: list[int] = [429, 500, 502, 503, 504]
|
|
104
|
+
retry_on_timeout: bool = True
|
|
105
|
+
retry_on_network_error: bool = True
|
|
106
|
+
|
|
107
|
+
# Header-based delay extraction
|
|
108
|
+
retry_after_header: str = "Retry-After"
|
|
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
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operation and PathItem models for OpenAPI 3.1.
|
|
3
|
+
|
|
4
|
+
References:
|
|
5
|
+
- https://spec.openapis.org/oas/v3.1.0#operation-object
|
|
6
|
+
- https://spec.openapis.org/oas/v3.1.0#path-item-object
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
12
|
+
|
|
13
|
+
from ..extensions import ActionTypeLiteral
|
|
14
|
+
from .components import Parameter, PathOverrideConfig, RequestBody, Response
|
|
15
|
+
from .security import SecurityRequirement
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Operation(BaseModel):
|
|
19
|
+
"""
|
|
20
|
+
Single API operation (GET, POST, PUT, PATCH, DELETE, etc.).
|
|
21
|
+
|
|
22
|
+
OpenAPI Reference: https://spec.openapis.org/oas/v3.1.0#operation-object
|
|
23
|
+
|
|
24
|
+
Extensions:
|
|
25
|
+
- x-airbyte-entity: Entity name (Airbyte extension)
|
|
26
|
+
- x-airbyte-action: Semantic action (Airbyte extension)
|
|
27
|
+
- x-airbyte-path-override: Path override (Airbyte extension)
|
|
28
|
+
- x-airbyte-record-extractor: JSONPath to extract records from response (Airbyte extension)
|
|
29
|
+
|
|
30
|
+
Future extensions (not yet active):
|
|
31
|
+
- x-airbyte-pagination: Pagination configuration for list operations
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
35
|
+
|
|
36
|
+
# Standard OpenAPI fields
|
|
37
|
+
tags: List[str] | None = None
|
|
38
|
+
summary: str | None = None
|
|
39
|
+
description: str | None = None
|
|
40
|
+
external_docs: Dict[str, Any] | None = Field(None, alias="externalDocs")
|
|
41
|
+
operation_id: str | None = Field(None, alias="operationId")
|
|
42
|
+
parameters: List[Parameter] | None = None
|
|
43
|
+
request_body: RequestBody | None = Field(None, alias="requestBody")
|
|
44
|
+
responses: Dict[str, Response] = Field(default_factory=dict)
|
|
45
|
+
callbacks: Dict[str, Any] | None = None
|
|
46
|
+
deprecated: bool | None = None
|
|
47
|
+
security: List[SecurityRequirement] | None = None
|
|
48
|
+
servers: List[Any] | None = None # Can override root servers
|
|
49
|
+
|
|
50
|
+
# Airbyte extensions
|
|
51
|
+
x_airbyte_entity: str = Field(..., alias="x-airbyte-entity")
|
|
52
|
+
x_airbyte_action: ActionTypeLiteral = Field(..., alias="x-airbyte-action")
|
|
53
|
+
x_airbyte_path_override: PathOverrideConfig | None = Field(
|
|
54
|
+
None,
|
|
55
|
+
alias="x-airbyte-path-override",
|
|
56
|
+
description=("Override path for HTTP requests when OpenAPI path differs from actual endpoint"),
|
|
57
|
+
)
|
|
58
|
+
x_airbyte_record_extractor: str | None = Field(
|
|
59
|
+
None,
|
|
60
|
+
alias="x-airbyte-record-extractor",
|
|
61
|
+
description=(
|
|
62
|
+
"JSONPath expression to extract records from API response envelopes. "
|
|
63
|
+
"When specified, executor extracts data at this path instead of returning "
|
|
64
|
+
"full response. Returns array for list/api_search actions, single record for "
|
|
65
|
+
"get/create/update/delete actions."
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
x_airbyte_meta_extractor: Dict[str, str] | None = Field(
|
|
69
|
+
None,
|
|
70
|
+
alias="x-airbyte-meta-extractor",
|
|
71
|
+
description=(
|
|
72
|
+
"Dictionary mapping field names to JSONPath expressions for extracting "
|
|
73
|
+
"metadata (pagination info, request IDs, etc.) from API response envelopes. "
|
|
74
|
+
"Each key becomes a field in ExecutionResult.meta with the value extracted "
|
|
75
|
+
"using the corresponding JSONPath expression. "
|
|
76
|
+
"Example: {'pagination': '$.pagination', 'request_id': '$.requestId'}"
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
x_airbyte_file_url: str | None = Field(None, alias="x-airbyte-file-url")
|
|
80
|
+
x_airbyte_untested: bool | None = Field(
|
|
81
|
+
None,
|
|
82
|
+
alias="x-airbyte-untested",
|
|
83
|
+
description=(
|
|
84
|
+
"Mark operation as untested to skip cassette validation in readiness checks. "
|
|
85
|
+
"Use this for operations that cannot be recorded (e.g., webhooks, real-time streams). "
|
|
86
|
+
"Validation will generate a warning instead of an error when cassettes are missing."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Future extensions (commented out, defined for future use)
|
|
91
|
+
# from .extensions import PaginationConfig
|
|
92
|
+
# x_pagination: Optional[PaginationConfig] = Field(None, alias="x-airbyte-pagination")
|
|
93
|
+
|
|
94
|
+
@model_validator(mode="after")
|
|
95
|
+
def validate_download_action_requirements(self) -> "Operation":
|
|
96
|
+
"""
|
|
97
|
+
Validate download operation requirements.
|
|
98
|
+
|
|
99
|
+
Rules:
|
|
100
|
+
- If x-airbyte-action is "download":
|
|
101
|
+
- x-airbyte-file-url must be non-empty if provided
|
|
102
|
+
- If x-airbyte-action is not "download":
|
|
103
|
+
- x-airbyte-file-url must not be present
|
|
104
|
+
"""
|
|
105
|
+
action = self.x_airbyte_action
|
|
106
|
+
file_url = self.x_airbyte_file_url
|
|
107
|
+
|
|
108
|
+
if action == "download":
|
|
109
|
+
# If file_url is provided, it must be non-empty
|
|
110
|
+
if file_url is not None and not file_url.strip():
|
|
111
|
+
raise ValueError("x-airbyte-file-url must be non-empty when provided for download operations")
|
|
112
|
+
else:
|
|
113
|
+
# Non-download actions cannot have file_url
|
|
114
|
+
if file_url is not None:
|
|
115
|
+
raise ValueError(f"x-airbyte-file-url can only be used with x-airbyte-action: download, but action is '{action}'")
|
|
116
|
+
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class PathItem(BaseModel):
|
|
121
|
+
"""
|
|
122
|
+
Path item containing operations for different HTTP methods.
|
|
123
|
+
|
|
124
|
+
OpenAPI Reference: https://spec.openapis.org/oas/v3.1.0#path-item-object
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
128
|
+
|
|
129
|
+
# Common fields for all operations
|
|
130
|
+
summary: str | None = None
|
|
131
|
+
description: str | None = None
|
|
132
|
+
servers: List[Any] | None = None
|
|
133
|
+
parameters: List[Parameter] | None = None
|
|
134
|
+
|
|
135
|
+
# HTTP methods (all optional)
|
|
136
|
+
get: Operation | None = None
|
|
137
|
+
put: Operation | None = None
|
|
138
|
+
post: Operation | None = None
|
|
139
|
+
delete: Operation | None = None
|
|
140
|
+
options: Operation | None = None
|
|
141
|
+
head: Operation | None = None
|
|
142
|
+
patch: Operation | None = None
|
|
143
|
+
trace: Operation | None = None
|
|
144
|
+
|
|
145
|
+
# Reference support
|
|
146
|
+
ref: str | None = Field(None, alias="$ref")
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Security scheme models for OpenAPI 3.1.
|
|
3
|
+
|
|
4
|
+
References:
|
|
5
|
+
- https://spec.openapis.org/oas/v3.1.0#security-scheme-object
|
|
6
|
+
- https://spec.openapis.org/oas/v3.1.0#oauth-flows-object
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OAuth2Flow(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
OAuth 2.0 flow configuration.
|
|
17
|
+
|
|
18
|
+
OpenAPI Reference: https://spec.openapis.org/oas/v3.1.0#oauth-flow-object
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
22
|
+
|
|
23
|
+
authorization_url: str | None = Field(None, alias="authorizationUrl")
|
|
24
|
+
token_url: str | None = Field(None, alias="tokenUrl")
|
|
25
|
+
refresh_url: str | None = Field(None, alias="refreshUrl")
|
|
26
|
+
scopes: Dict[str, str] = Field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OAuth2Flows(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
Collection of OAuth 2.0 flows.
|
|
32
|
+
|
|
33
|
+
OpenAPI Reference: https://spec.openapis.org/oas/v3.1.0#oauth-flows-object
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
37
|
+
|
|
38
|
+
implicit: OAuth2Flow | None = None
|
|
39
|
+
password: OAuth2Flow | None = None
|
|
40
|
+
client_credentials: OAuth2Flow | None = Field(None, alias="clientCredentials")
|
|
41
|
+
authorization_code: OAuth2Flow | None = Field(None, alias="authorizationCode")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AuthConfigFieldSpec(BaseModel):
|
|
45
|
+
"""
|
|
46
|
+
Specification for a user-facing authentication config field.
|
|
47
|
+
|
|
48
|
+
This defines a single input field that users provide for authentication.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
52
|
+
|
|
53
|
+
type: Literal["string", "integer", "boolean", "number"] = "string"
|
|
54
|
+
title: str | None = None
|
|
55
|
+
description: str | None = None
|
|
56
|
+
format: str | None = None # e.g., "email", "uri"
|
|
57
|
+
pattern: str | None = None # Regex validation
|
|
58
|
+
airbyte_secret: bool = Field(False, alias="airbyte_secret")
|
|
59
|
+
default: Any | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AuthConfigOption(BaseModel):
|
|
63
|
+
"""
|
|
64
|
+
A single authentication configuration option.
|
|
65
|
+
|
|
66
|
+
Defines user-facing fields and how they map to auth parameters.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
70
|
+
|
|
71
|
+
title: str | None = None
|
|
72
|
+
description: str | None = None
|
|
73
|
+
type: Literal["object"] = "object"
|
|
74
|
+
required: List[str] = Field(default_factory=list)
|
|
75
|
+
properties: Dict[str, AuthConfigFieldSpec] = Field(default_factory=dict)
|
|
76
|
+
auth_mapping: Dict[str, str] = Field(
|
|
77
|
+
default_factory=dict,
|
|
78
|
+
description="Mapping from auth parameters (e.g., 'username', 'password', 'token') to template strings using ${field} syntax",
|
|
79
|
+
)
|
|
80
|
+
replication_auth_key_mapping: Dict[str, str] | None = Field(
|
|
81
|
+
None,
|
|
82
|
+
description="Mapping from source config paths (e.g., 'credentials.api_key') to auth config keys for direct connectors",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class AirbyteAuthConfig(BaseModel):
|
|
87
|
+
"""
|
|
88
|
+
Airbyte auth configuration extension (x-airbyte-auth-config).
|
|
89
|
+
|
|
90
|
+
Defines user-facing authentication configuration and how it maps to
|
|
91
|
+
the underlying OpenAPI security scheme.
|
|
92
|
+
|
|
93
|
+
Either a single auth option or multiple options via oneOf.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
97
|
+
|
|
98
|
+
# Single option fields
|
|
99
|
+
title: str | None = None
|
|
100
|
+
description: str | None = None
|
|
101
|
+
type: Literal["object"] | None = None
|
|
102
|
+
required: List[str] | None = None
|
|
103
|
+
properties: Dict[str, AuthConfigFieldSpec] | None = None
|
|
104
|
+
auth_mapping: Dict[str, str] | None = None
|
|
105
|
+
|
|
106
|
+
# Replication connector auth mapping
|
|
107
|
+
replication_auth_key_mapping: Dict[str, str] | None = Field(
|
|
108
|
+
None,
|
|
109
|
+
description="Mapping from source config paths (e.g., 'credentials.api_key') to auth config keys for direct connectors",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Multiple options (oneOf)
|
|
113
|
+
one_of: List[AuthConfigOption] | None = Field(None, alias="oneOf")
|
|
114
|
+
|
|
115
|
+
@model_validator(mode="after")
|
|
116
|
+
def validate_config_structure(self) -> "AirbyteAuthConfig":
|
|
117
|
+
"""Validate that either single option or oneOf is provided, not both."""
|
|
118
|
+
has_single = self.type is not None or self.properties is not None or self.auth_mapping is not None
|
|
119
|
+
has_one_of = self.one_of is not None and len(self.one_of) > 0
|
|
120
|
+
|
|
121
|
+
if not has_single and not has_one_of:
|
|
122
|
+
raise ValueError("Either single auth option (type/properties/auth_mapping) or oneOf must be provided")
|
|
123
|
+
|
|
124
|
+
if has_single and has_one_of:
|
|
125
|
+
raise ValueError("Cannot have both single auth option and oneOf")
|
|
126
|
+
|
|
127
|
+
if has_single:
|
|
128
|
+
# Validate single option has required fields
|
|
129
|
+
if self.type != "object":
|
|
130
|
+
raise ValueError("Single auth option must have type='object'")
|
|
131
|
+
if not self.properties:
|
|
132
|
+
raise ValueError("Single auth option must have properties")
|
|
133
|
+
if not self.auth_mapping:
|
|
134
|
+
raise ValueError("Single auth option must have auth_mapping")
|
|
135
|
+
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SecurityScheme(BaseModel):
|
|
140
|
+
"""
|
|
141
|
+
Security scheme definition.
|
|
142
|
+
|
|
143
|
+
OpenAPI Reference: https://spec.openapis.org/oas/v3.1.0#security-scheme-object
|
|
144
|
+
|
|
145
|
+
Supported Types:
|
|
146
|
+
- apiKey: API key in header/query/cookie
|
|
147
|
+
- http: HTTP authentication (basic, bearer, digest, etc.)
|
|
148
|
+
- oauth2: OAuth 2.0 flows
|
|
149
|
+
|
|
150
|
+
Extensions:
|
|
151
|
+
- x-airbyte-token-path: JSON path to extract token from auth response (Airbyte extension)
|
|
152
|
+
- x-airbyte-token-refresh: OAuth2 token refresh configuration (dict with auth_style, body_format)
|
|
153
|
+
- x-airbyte-auth-config: User-facing authentication configuration (Airbyte extension)
|
|
154
|
+
|
|
155
|
+
Future extensions (not yet active):
|
|
156
|
+
- x-grant-type: OAuth grant type for refresh tokens
|
|
157
|
+
- x-refresh-endpoint: Custom refresh endpoint URL
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
161
|
+
|
|
162
|
+
# Standard OpenAPI fields
|
|
163
|
+
type: Literal["apiKey", "http", "oauth2", "openIdConnect"]
|
|
164
|
+
description: str | None = None
|
|
165
|
+
|
|
166
|
+
# apiKey specific
|
|
167
|
+
name: str | None = None
|
|
168
|
+
in_: Literal["query", "header", "cookie"] | None = Field(None, alias="in")
|
|
169
|
+
|
|
170
|
+
# http specific
|
|
171
|
+
scheme: str | None = None # e.g., "basic", "bearer", "digest"
|
|
172
|
+
bearer_format: str | None = Field(None, alias="bearerFormat")
|
|
173
|
+
|
|
174
|
+
# oauth2 specific
|
|
175
|
+
flows: OAuth2Flows | None = None
|
|
176
|
+
|
|
177
|
+
# openIdConnect specific
|
|
178
|
+
open_id_connect_url: str | None = Field(None, alias="openIdConnectUrl")
|
|
179
|
+
|
|
180
|
+
# Airbyte extensions
|
|
181
|
+
x_token_path: str | None = Field(None, alias="x-airbyte-token-path")
|
|
182
|
+
x_token_refresh: Dict[str, Any] | None = Field(None, alias="x-airbyte-token-refresh")
|
|
183
|
+
x_airbyte_auth_config: AirbyteAuthConfig | None = Field(None, alias="x-airbyte-auth-config")
|
|
184
|
+
x_airbyte_token_extract: List[str] | None = Field(
|
|
185
|
+
None,
|
|
186
|
+
alias="x-airbyte-token-extract",
|
|
187
|
+
description="List of fields to extract from OAuth2 token responses and use as server variables",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@field_validator("x_airbyte_token_extract", mode="after")
|
|
191
|
+
@classmethod
|
|
192
|
+
def validate_token_extract(cls, v: List[str] | None) -> List[str] | None:
|
|
193
|
+
"""Validate x-airbyte-token-extract has no duplicates."""
|
|
194
|
+
if v is not None:
|
|
195
|
+
if len(v) != len(set(v)):
|
|
196
|
+
duplicates = [x for x in v if v.count(x) > 1]
|
|
197
|
+
raise ValueError(f"x-airbyte-token-extract contains duplicate fields: {set(duplicates)}")
|
|
198
|
+
return v
|
|
199
|
+
|
|
200
|
+
# Future extensions (commented out, defined for future use)
|
|
201
|
+
# x_grant_type: Optional[Literal["refresh_token", "client_credentials"]] = Field(None, alias="x-grant-type")
|
|
202
|
+
# x_refresh_endpoint: Optional[str] = Field(None, alias="x-refresh-endpoint")
|
|
203
|
+
|
|
204
|
+
@model_validator(mode="after")
|
|
205
|
+
def validate_security_scheme(self) -> "SecurityScheme":
|
|
206
|
+
"""Validate that required fields are present based on security type."""
|
|
207
|
+
if self.type == "apiKey":
|
|
208
|
+
if not self.name or not self.in_:
|
|
209
|
+
raise ValueError("apiKey type requires 'name' and 'in' fields")
|
|
210
|
+
elif self.type == "http":
|
|
211
|
+
if not self.scheme:
|
|
212
|
+
raise ValueError("http type requires 'scheme' field")
|
|
213
|
+
elif self.type == "oauth2":
|
|
214
|
+
if not self.flows:
|
|
215
|
+
raise ValueError("oauth2 type requires 'flows' field")
|
|
216
|
+
elif self.type == "openIdConnect":
|
|
217
|
+
if not self.open_id_connect_url:
|
|
218
|
+
raise ValueError("openIdConnect type requires 'openIdConnectUrl' field")
|
|
219
|
+
return self
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# SecurityRequirement is a dict mapping security scheme name to list of scopes
|
|
223
|
+
SecurityRequirement = Dict[str, List[str]]
|