airbyte-agent-mcp 0.1.53__py3-none-any.whl → 0.1.60__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 (28) hide show
  1. airbyte_agent_mcp/_vendored/connector_sdk/auth_strategies.py +2 -5
  2. airbyte_agent_mcp/_vendored/connector_sdk/auth_template.py +1 -1
  3. airbyte_agent_mcp/_vendored/connector_sdk/cloud_utils/client.py +26 -26
  4. airbyte_agent_mcp/_vendored/connector_sdk/connector_model_loader.py +1 -2
  5. airbyte_agent_mcp/_vendored/connector_sdk/executor/hosted_executor.py +10 -11
  6. airbyte_agent_mcp/_vendored/connector_sdk/executor/local_executor.py +72 -13
  7. airbyte_agent_mcp/_vendored/connector_sdk/extensions.py +1 -2
  8. airbyte_agent_mcp/_vendored/connector_sdk/http/response.py +2 -0
  9. airbyte_agent_mcp/_vendored/connector_sdk/logging/logger.py +9 -9
  10. airbyte_agent_mcp/_vendored/connector_sdk/logging/types.py +10 -10
  11. airbyte_agent_mcp/_vendored/connector_sdk/observability/config.py +2 -2
  12. airbyte_agent_mcp/_vendored/connector_sdk/observability/models.py +6 -6
  13. airbyte_agent_mcp/_vendored/connector_sdk/observability/session.py +7 -5
  14. airbyte_agent_mcp/_vendored/connector_sdk/performance/metrics.py +3 -3
  15. airbyte_agent_mcp/_vendored/connector_sdk/schema/base.py +20 -18
  16. airbyte_agent_mcp/_vendored/connector_sdk/schema/components.py +58 -58
  17. airbyte_agent_mcp/_vendored/connector_sdk/schema/connector.py +22 -33
  18. airbyte_agent_mcp/_vendored/connector_sdk/schema/extensions.py +102 -9
  19. airbyte_agent_mcp/_vendored/connector_sdk/schema/operations.py +31 -31
  20. airbyte_agent_mcp/_vendored/connector_sdk/schema/security.py +36 -36
  21. airbyte_agent_mcp/_vendored/connector_sdk/secrets.py +2 -2
  22. airbyte_agent_mcp/_vendored/connector_sdk/telemetry/events.py +7 -7
  23. airbyte_agent_mcp/_vendored/connector_sdk/telemetry/tracker.py +6 -5
  24. airbyte_agent_mcp/_vendored/connector_sdk/types.py +2 -2
  25. airbyte_agent_mcp/server.py +34 -1
  26. {airbyte_agent_mcp-0.1.53.dist-info → airbyte_agent_mcp-0.1.60.dist-info}/METADATA +1 -1
  27. {airbyte_agent_mcp-0.1.53.dist-info → airbyte_agent_mcp-0.1.60.dist-info}/RECORD +28 -28
  28. {airbyte_agent_mcp-0.1.53.dist-info → airbyte_agent_mcp-0.1.60.dist-info}/WHEEL +0 -0
@@ -7,13 +7,13 @@ References:
7
7
  """
8
8
 
9
9
  from enum import StrEnum
10
- from typing import Dict, Optional
10
+ from typing import Dict
11
11
  from uuid import UUID
12
12
 
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):
@@ -45,9 +45,9 @@ class Contact(BaseModel):
45
45
 
46
46
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
47
47
 
48
- name: Optional[str] = None
49
- url: Optional[str] = None
50
- email: Optional[str] = None
48
+ name: str | None = None
49
+ url: str | None = None
50
+ email: str | None = None
51
51
 
52
52
 
53
53
  class License(BaseModel):
@@ -60,7 +60,7 @@ class License(BaseModel):
60
60
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
61
61
 
62
62
  name: str
63
- url: Optional[str] = None
63
+ url: str | None = None
64
64
 
65
65
 
66
66
  class DocUrlType(StrEnum):
@@ -85,7 +85,7 @@ class DocUrl(BaseModel):
85
85
 
86
86
  url: str
87
87
  type: DocUrlType
88
- title: Optional[str] = None
88
+ title: str | None = None
89
89
 
90
90
  @field_validator("url")
91
91
  def validate_url(cls, v):
@@ -105,23 +105,25 @@ 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")
111
112
 
112
113
  title: str
113
114
  version: str
114
- description: Optional[str] = None
115
- terms_of_service: Optional[str] = Field(None, alias="termsOfService")
116
- contact: Optional[Contact] = None
117
- license: Optional[License] = None
115
+ description: str | None = None
116
+ terms_of_service: str | None = Field(None, alias="termsOfService")
117
+ contact: Contact | None = None
118
+ license: License | None = None
118
119
 
119
120
  # Airbyte extension
120
- x_airbyte_connector_name: Optional[str] = Field(None, alias="x-airbyte-connector-name")
121
- x_airbyte_connector_id: Optional[UUID] = Field(None, alias="x-airbyte-connector-id")
121
+ x_airbyte_connector_name: str | None = Field(None, alias="x-airbyte-connector-name")
122
+ x_airbyte_connector_id: UUID | None = Field(None, alias="x-airbyte-connector-id")
122
123
  x_airbyte_external_documentation_urls: list[DocUrl] = Field(..., alias="x-airbyte-external-documentation-urls")
123
- x_airbyte_retry_config: Optional[RetryConfig] = Field(None, alias="x-airbyte-retry-config")
124
- x_airbyte_example_questions: Optional[ExampleQuestions] = Field(None, alias="x-airbyte-example-questions")
124
+ x_airbyte_retry_config: RetryConfig | None = Field(None, alias="x-airbyte-retry-config")
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):
@@ -133,9 +135,9 @@ class ServerVariable(BaseModel):
133
135
 
134
136
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
135
137
 
136
- enum: Optional[list[str]] = None
138
+ enum: list[str] | None = None
137
139
  default: str
138
- description: Optional[str] = None
140
+ description: str | None = None
139
141
 
140
142
 
141
143
  class Server(BaseModel):
@@ -148,7 +150,7 @@ class Server(BaseModel):
148
150
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
149
151
 
150
152
  url: str
151
- description: Optional[str] = None
153
+ description: str | None = None
152
154
  variables: Dict[str, ServerVariable] = Field(default_factory=dict)
153
155
 
154
156
  @field_validator("url")
@@ -7,7 +7,7 @@ References:
7
7
  - https://spec.openapis.org/oas/v3.1.0#parameter-object
8
8
  """
9
9
 
10
- from typing import Any, Dict, List, Literal, Optional, Union
10
+ from typing import Any, Dict, List, Literal, Union
11
11
 
12
12
  from pydantic import BaseModel, ConfigDict, Field
13
13
 
@@ -30,44 +30,44 @@ class Schema(BaseModel):
30
30
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
31
31
 
32
32
  # Core JSON Schema fields
33
- type: Optional[str] = None
34
- format: Optional[str] = None
35
- title: Optional[str] = None
36
- description: Optional[str] = None
37
- default: Optional[Any] = None
38
- example: Optional[Any] = None
33
+ type: str | None = None
34
+ format: str | None = None
35
+ title: str | None = None
36
+ description: str | None = None
37
+ default: Any | None = None
38
+ example: Any | None = None
39
39
 
40
40
  # Object properties
41
41
  properties: Dict[str, Any] = Field(default_factory=dict) # May contain $ref
42
42
  required: List[str] = Field(default_factory=list)
43
- additional_properties: Optional[Any] = Field(None, alias="additionalProperties")
43
+ additional_properties: Any | None = Field(None, alias="additionalProperties")
44
44
 
45
45
  # Array properties
46
- items: Optional[Any] = None # May be Schema or $ref
46
+ items: Any | None = None # May be Schema or $ref
47
47
 
48
48
  # Validation
49
- enum: Optional[List[Any]] = None
50
- min_length: Optional[int] = Field(None, alias="minLength")
51
- max_length: Optional[int] = Field(None, alias="maxLength")
52
- minimum: Optional[float] = None
53
- maximum: Optional[float] = None
54
- pattern: Optional[str] = None
49
+ enum: List[Any] | None = None
50
+ min_length: int | None = Field(None, alias="minLength")
51
+ max_length: int | None = Field(None, alias="maxLength")
52
+ minimum: float | None = None
53
+ maximum: float | None = None
54
+ pattern: str | None = None
55
55
 
56
56
  # Composition
57
- all_of: Optional[List[Any]] = Field(None, alias="allOf")
58
- any_of: Optional[List[Any]] = Field(None, alias="anyOf")
59
- one_of: Optional[List[Any]] = Field(None, alias="oneOf")
60
- not_: Optional[Any] = Field(None, alias="not")
57
+ all_of: List[Any] | None = Field(None, alias="allOf")
58
+ any_of: List[Any] | None = Field(None, alias="anyOf")
59
+ one_of: List[Any] | None = Field(None, alias="oneOf")
60
+ not_: Any | None = Field(None, alias="not")
61
61
 
62
62
  # Metadata
63
- nullable: Optional[bool] = Field(None, deprecated="Use type union with null instead (OpenAPI 3.1)")
64
- read_only: Optional[bool] = Field(None, alias="readOnly")
65
- write_only: Optional[bool] = Field(None, alias="writeOnly")
66
- deprecated: Optional[bool] = None
63
+ nullable: bool | None = Field(None, deprecated="Use type union with null instead (OpenAPI 3.1)")
64
+ read_only: bool | None = Field(None, alias="readOnly")
65
+ write_only: bool | None = Field(None, alias="writeOnly")
66
+ deprecated: bool | None = None
67
67
 
68
68
  # Airbyte extensions
69
- x_airbyte_entity_name: Optional[str] = Field(None, alias="x-airbyte-entity-name")
70
- x_airbyte_stream_name: Optional[str] = Field(None, alias="x-airbyte-stream-name")
69
+ x_airbyte_entity_name: str | None = Field(None, alias="x-airbyte-entity-name")
70
+ x_airbyte_stream_name: str | None = Field(None, alias="x-airbyte-stream-name")
71
71
 
72
72
 
73
73
  class Parameter(BaseModel):
@@ -81,19 +81,19 @@ class Parameter(BaseModel):
81
81
 
82
82
  name: str
83
83
  in_: Literal["query", "header", "path", "cookie"] = Field(alias="in")
84
- description: Optional[str] = None
85
- required: Optional[bool] = None
86
- deprecated: Optional[bool] = None
87
- allow_empty_value: Optional[bool] = Field(None, alias="allowEmptyValue")
84
+ description: str | None = None
85
+ required: bool | None = None
86
+ deprecated: bool | None = None
87
+ allow_empty_value: bool | None = Field(None, alias="allowEmptyValue")
88
88
 
89
89
  # Schema can be inline or reference
90
- schema_: Optional[Dict[str, Any]] = Field(None, alias="schema")
90
+ schema_: Dict[str, Any] | None = Field(None, alias="schema")
91
91
 
92
92
  # Style and examples
93
- style: Optional[str] = None
94
- explode: Optional[bool] = None
95
- example: Optional[Any] = None
96
- examples: Optional[Dict[str, Any]] = None
93
+ style: str | None = None
94
+ explode: bool | None = None
95
+ example: Any | None = None
96
+ examples: Dict[str, Any] | None = None
97
97
 
98
98
 
99
99
  class MediaType(BaseModel):
@@ -105,10 +105,10 @@ class MediaType(BaseModel):
105
105
 
106
106
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
107
107
 
108
- schema_: Optional[Dict[str, Any]] = Field(None, alias="schema")
109
- example: Optional[Any] = None
110
- examples: Optional[Dict[str, Any]] = None
111
- encoding: Optional[Dict[str, Any]] = None
108
+ schema_: Dict[str, Any] | None = Field(None, alias="schema")
109
+ example: Any | None = None
110
+ examples: Dict[str, Any] | None = None
111
+ encoding: Dict[str, Any] | None = None
112
112
 
113
113
 
114
114
  class GraphQLBodyConfig(BaseModel):
@@ -125,12 +125,12 @@ class GraphQLBodyConfig(BaseModel):
125
125
  ...,
126
126
  description="GraphQL query or mutation string with optional template placeholders (e.g., {{ variable }})",
127
127
  )
128
- variables: Optional[Dict[str, Any]] = Field(
128
+ variables: Dict[str, Any] | None = Field(
129
129
  None,
130
130
  description="Variables to substitute in the GraphQL query using template syntax (e.g., {{ param_name }})",
131
131
  )
132
- operationName: Optional[str] = Field(None, description="Operation name for queries with multiple operations")
133
- default_fields: Optional[Union[str, List[str]]] = Field(
132
+ operationName: str | None = Field(None, description="Operation name for queries with multiple operations")
133
+ default_fields: Union[str, List[str]] | None = Field(
134
134
  None,
135
135
  description="Default fields to select if not provided in request parameters. Can be a string or array of field names.",
136
136
  )
@@ -156,7 +156,7 @@ class PathOverrideConfig(BaseModel):
156
156
 
157
157
  path: str = Field(
158
158
  ...,
159
- description=("Actual HTTP path to use for requests (e.g., '/graphql'). " "Must start with '/'"),
159
+ description=("Actual HTTP path to use for requests (e.g., '/graphql'). Must start with '/'"),
160
160
  )
161
161
 
162
162
 
@@ -173,17 +173,17 @@ class RequestBody(BaseModel):
173
173
 
174
174
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
175
175
 
176
- description: Optional[str] = None
176
+ description: str | None = None
177
177
  content: Dict[str, MediaType] = Field(default_factory=dict)
178
- required: Optional[bool] = None
178
+ required: bool | None = None
179
179
 
180
180
  # Airbyte extensions for GraphQL support
181
181
  # See connector_sdk.extensions for AIRBYTE_BODY_TYPE constant
182
- x_airbyte_body_type: Optional[BodyTypeConfig] = Field(
182
+ x_airbyte_body_type: BodyTypeConfig | None = Field(
183
183
  None,
184
184
  alias="x-airbyte-body-type", # AIRBYTE_BODY_TYPE
185
185
  description=(
186
- "Body type and configuration. Contains 'type' field (e.g., 'graphql') " "and type-specific configuration (query, variables, etc.)."
186
+ "Body type and configuration. Contains 'type' field (e.g., 'graphql') and type-specific configuration (query, variables, etc.)."
187
187
  ),
188
188
  )
189
189
 
@@ -197,11 +197,11 @@ class Header(BaseModel):
197
197
 
198
198
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
199
199
 
200
- description: Optional[str] = None
201
- required: Optional[bool] = None
202
- deprecated: Optional[bool] = None
203
- schema_: Optional[Dict[str, Any]] = Field(None, alias="schema")
204
- example: Optional[Any] = None
200
+ description: str | None = None
201
+ required: bool | None = None
202
+ deprecated: bool | None = None
203
+ schema_: Dict[str, Any] | None = Field(None, alias="schema")
204
+ example: Any | None = None
205
205
 
206
206
 
207
207
  class Response(BaseModel):
@@ -214,9 +214,9 @@ class Response(BaseModel):
214
214
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
215
215
 
216
216
  description: str
217
- headers: Optional[Dict[str, Header]] = None
218
- content: Optional[Dict[str, MediaType]] = None
219
- links: Optional[Dict[str, Any]] = None
217
+ headers: Dict[str, Header] | None = None
218
+ content: Dict[str, MediaType] | None = None
219
+ links: Dict[str, Any] | None = None
220
220
 
221
221
 
222
222
  class Components(BaseModel):
@@ -231,9 +231,9 @@ class Components(BaseModel):
231
231
  schemas: Dict[str, Schema] = Field(default_factory=dict)
232
232
  responses: Dict[str, Response] = Field(default_factory=dict)
233
233
  parameters: Dict[str, Parameter] = Field(default_factory=dict)
234
- examples: Optional[Dict[str, Any]] = None
234
+ examples: Dict[str, Any] | None = None
235
235
  request_bodies: Dict[str, RequestBody] = Field(default_factory=dict, alias="requestBodies")
236
- headers: Optional[Dict[str, Header]] = None
236
+ headers: Dict[str, Header] | None = None
237
237
  security_schemes: Dict[str, SecurityScheme] = Field(default_factory=dict, alias="securitySchemes")
238
- links: Optional[Dict[str, Any]] = None
239
- callbacks: Optional[Dict[str, Any]] = None
238
+ links: Dict[str, Any] | None = None
239
+ callbacks: Dict[str, Any] | None = None
@@ -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, Any]]:
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
- results = []
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 = set()
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, Optional
16
16
 
17
- from pydantic import BaseModel, ConfigDict
17
+ from pydantic import BaseModel, ConfigDict, Field
18
18
 
19
19
 
20
20
  class PaginationConfig(BaseModel):
@@ -33,22 +33,22 @@ class PaginationConfig(BaseModel):
33
33
  limit_param: str = "limit"
34
34
 
35
35
  # Cursor-based pagination
36
- cursor_param: Optional[str] = None
37
- cursor_source: Optional[Literal["body", "headers"]] = "body"
38
- cursor_path: Optional[str] = None
36
+ cursor_param: str | None = None
37
+ cursor_source: Literal["body", "headers"] | None = "body"
38
+ cursor_path: str | None = None
39
39
 
40
40
  # Offset-based pagination
41
- offset_param: Optional[str] = None
41
+ offset_param: str | None = None
42
42
 
43
43
  # Page-based pagination
44
- page_param: Optional[str] = None
44
+ page_param: str | None = None
45
45
 
46
46
  # Response parsing
47
47
  data_path: str = "data"
48
- has_more_path: Optional[str] = None
48
+ has_more_path: str | None = None
49
49
 
50
50
  # Limits
51
- max_page_size: Optional[int] = None
51
+ max_page_size: int | None = None
52
52
  default_page_size: int = 100
53
53
 
54
54
 
@@ -66,7 +66,7 @@ class RateLimitConfig(BaseModel):
66
66
 
67
67
  max_requests: int
68
68
  time_window_seconds: int
69
- retry_after_header: Optional[str] = "Retry-After"
69
+ retry_after_header: str | None = "Retry-After"
70
70
  respect_retry_after: bool = True
71
71
 
72
72
 
@@ -107,3 +107,96 @@ 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 CacheFieldConfig(BaseModel):
113
+ """
114
+ Field configuration for cache mapping.
115
+
116
+ Defines a single field in a cache entity, with optional name aliasing
117
+ to map between user-facing field names and cache storage names.
118
+
119
+ Used in x-airbyte-cache extension for api_search operations.
120
+ """
121
+
122
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
123
+
124
+ name: str
125
+ x_airbyte_name: Optional[str] = Field(default=None, alias="x-airbyte-name")
126
+ type: str | list[str]
127
+ description: str
128
+
129
+ @property
130
+ def cache_name(self) -> str:
131
+ """Return cache name, falling back to name if alias not specified."""
132
+ return self.x_airbyte_name or self.name
133
+
134
+
135
+ class CacheEntityConfig(BaseModel):
136
+ """
137
+ Entity configuration for cache mapping.
138
+
139
+ Defines a cache-enabled entity with its fields and optional name aliasing
140
+ to map between user-facing entity names and cache storage names.
141
+
142
+ Used in x-airbyte-cache extension for api_search operations.
143
+ """
144
+
145
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
146
+
147
+ entity: str
148
+ x_airbyte_name: Optional[str] = Field(default=None, alias="x-airbyte-name")
149
+ fields: list[CacheFieldConfig]
150
+
151
+ @property
152
+ def cache_name(self) -> str:
153
+ """Return cache entity name, falling back to entity if alias not specified."""
154
+ return self.x_airbyte_name or self.entity
155
+
156
+
157
+ class CacheConfig(BaseModel):
158
+ """
159
+ Cache configuration extension (x-airbyte-cache).
160
+
161
+ Defines cache-enabled entities and their field mappings for api_search operations.
162
+ Supports optional name aliasing via x-airbyte-name for both entities and fields,
163
+ enabling bidirectional mapping between user-facing names and cache storage names.
164
+
165
+ This extension is added to the Info model and provides field-level mapping for
166
+ search operations that use cached data.
167
+
168
+ Example YAML usage:
169
+ info:
170
+ title: Stripe API
171
+ x-airbyte-cache:
172
+ entities:
173
+ - entity: customers
174
+ stream: customers
175
+ fields:
176
+ - name: email
177
+ type: ["null", "string"]
178
+ description: "Customer email address"
179
+ - name: customer_name
180
+ x-airbyte-name: name
181
+ type: ["null", "string"]
182
+ description: "Customer full name"
183
+ """
184
+
185
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
186
+
187
+ entities: list[CacheEntityConfig]
188
+
189
+ def get_entity_mapping(self, user_entity: str) -> Optional[CacheEntityConfig]:
190
+ """
191
+ Get entity config by user-facing name.
192
+
193
+ Args:
194
+ user_entity: User-facing entity name to look up
195
+
196
+ Returns:
197
+ CacheEntityConfig if found, None otherwise
198
+ """
199
+ for entity in self.entities:
200
+ if entity.entity == user_entity:
201
+ return entity
202
+ return None
@@ -6,7 +6,7 @@ References:
6
6
  - https://spec.openapis.org/oas/v3.1.0#path-item-object
7
7
  """
8
8
 
9
- from typing import Any, Dict, List, Optional
9
+ from typing import Any, Dict, List
10
10
 
11
11
  from pydantic import BaseModel, ConfigDict, Field, model_validator
12
12
 
@@ -34,28 +34,28 @@ class Operation(BaseModel):
34
34
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
35
35
 
36
36
  # Standard OpenAPI fields
37
- tags: Optional[List[str]] = None
38
- summary: Optional[str] = None
39
- description: Optional[str] = None
40
- external_docs: Optional[Dict[str, Any]] = Field(None, alias="externalDocs")
41
- operation_id: Optional[str] = Field(None, alias="operationId")
42
- parameters: Optional[List[Parameter]] = None
43
- request_body: Optional[RequestBody] = Field(None, alias="requestBody")
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
44
  responses: Dict[str, Response] = Field(default_factory=dict)
45
- callbacks: Optional[Dict[str, Any]] = None
46
- deprecated: Optional[bool] = None
47
- security: Optional[List[SecurityRequirement]] = None
48
- servers: Optional[List[Any]] = None # Can override root servers
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
49
 
50
50
  # Airbyte extensions
51
51
  x_airbyte_entity: str = Field(..., alias="x-airbyte-entity")
52
52
  x_airbyte_action: ActionTypeLiteral = Field(..., alias="x-airbyte-action")
53
- x_airbyte_path_override: Optional[PathOverrideConfig] = Field(
53
+ x_airbyte_path_override: PathOverrideConfig | None = Field(
54
54
  None,
55
55
  alias="x-airbyte-path-override",
56
- description=("Override path for HTTP requests when OpenAPI path " "differs from actual endpoint"),
56
+ description=("Override path for HTTP requests when OpenAPI path differs from actual endpoint"),
57
57
  )
58
- x_airbyte_record_extractor: Optional[str] = Field(
58
+ x_airbyte_record_extractor: str | None = Field(
59
59
  None,
60
60
  alias="x-airbyte-record-extractor",
61
61
  description=(
@@ -65,7 +65,7 @@ class Operation(BaseModel):
65
65
  "get/create/update/delete actions."
66
66
  ),
67
67
  )
68
- x_airbyte_meta_extractor: Optional[Dict[str, str]] = Field(
68
+ x_airbyte_meta_extractor: Dict[str, str] | None = Field(
69
69
  None,
70
70
  alias="x-airbyte-meta-extractor",
71
71
  description=(
@@ -76,8 +76,8 @@ class Operation(BaseModel):
76
76
  "Example: {'pagination': '$.pagination', 'request_id': '$.requestId'}"
77
77
  ),
78
78
  )
79
- x_airbyte_file_url: Optional[str] = Field(None, alias="x-airbyte-file-url")
80
- x_airbyte_untested: Optional[bool] = Field(
79
+ x_airbyte_file_url: str | None = Field(None, alias="x-airbyte-file-url")
80
+ x_airbyte_untested: bool | None = Field(
81
81
  None,
82
82
  alias="x-airbyte-untested",
83
83
  description=(
@@ -127,20 +127,20 @@ class PathItem(BaseModel):
127
127
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
128
128
 
129
129
  # Common fields for all operations
130
- summary: Optional[str] = None
131
- description: Optional[str] = None
132
- servers: Optional[List[Any]] = None
133
- parameters: Optional[List[Parameter]] = None
130
+ summary: str | None = None
131
+ description: str | None = None
132
+ servers: List[Any] | None = None
133
+ parameters: List[Parameter] | None = None
134
134
 
135
135
  # HTTP methods (all optional)
136
- get: Optional[Operation] = None
137
- put: Optional[Operation] = None
138
- post: Optional[Operation] = None
139
- delete: Optional[Operation] = None
140
- options: Optional[Operation] = None
141
- head: Optional[Operation] = None
142
- patch: Optional[Operation] = None
143
- trace: Optional[Operation] = None
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
144
 
145
145
  # Reference support
146
- ref: Optional[str] = Field(None, alias="$ref")
146
+ ref: str | None = Field(None, alias="$ref")