airbyte-agent-hubspot 0.15.25__py3-none-any.whl → 0.15.43__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_hubspot/__init__.py +100 -25
- airbyte_agent_hubspot/_vendored/connector_sdk/auth_strategies.py +2 -5
- airbyte_agent_hubspot/_vendored/connector_sdk/auth_template.py +1 -1
- airbyte_agent_hubspot/_vendored/connector_sdk/cloud_utils/client.py +26 -26
- airbyte_agent_hubspot/_vendored/connector_sdk/connector_model_loader.py +11 -4
- airbyte_agent_hubspot/_vendored/connector_sdk/constants.py +1 -1
- airbyte_agent_hubspot/_vendored/connector_sdk/executor/hosted_executor.py +10 -11
- airbyte_agent_hubspot/_vendored/connector_sdk/executor/local_executor.py +163 -34
- airbyte_agent_hubspot/_vendored/connector_sdk/extensions.py +43 -5
- airbyte_agent_hubspot/_vendored/connector_sdk/http/response.py +2 -0
- airbyte_agent_hubspot/_vendored/connector_sdk/introspection.py +262 -0
- airbyte_agent_hubspot/_vendored/connector_sdk/logging/logger.py +9 -9
- airbyte_agent_hubspot/_vendored/connector_sdk/logging/types.py +10 -10
- airbyte_agent_hubspot/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_hubspot/_vendored/connector_sdk/observability/models.py +6 -6
- airbyte_agent_hubspot/_vendored/connector_sdk/observability/session.py +41 -32
- airbyte_agent_hubspot/_vendored/connector_sdk/performance/metrics.py +3 -3
- airbyte_agent_hubspot/_vendored/connector_sdk/schema/base.py +20 -18
- airbyte_agent_hubspot/_vendored/connector_sdk/schema/components.py +59 -58
- airbyte_agent_hubspot/_vendored/connector_sdk/schema/connector.py +22 -33
- airbyte_agent_hubspot/_vendored/connector_sdk/schema/extensions.py +103 -10
- airbyte_agent_hubspot/_vendored/connector_sdk/schema/operations.py +32 -32
- airbyte_agent_hubspot/_vendored/connector_sdk/schema/security.py +44 -34
- airbyte_agent_hubspot/_vendored/connector_sdk/secrets.py +2 -2
- airbyte_agent_hubspot/_vendored/connector_sdk/telemetry/events.py +9 -8
- airbyte_agent_hubspot/_vendored/connector_sdk/telemetry/tracker.py +9 -5
- airbyte_agent_hubspot/_vendored/connector_sdk/types.py +7 -3
- airbyte_agent_hubspot/connector.py +182 -87
- airbyte_agent_hubspot/connector_model.py +17 -12
- airbyte_agent_hubspot/models.py +21 -21
- airbyte_agent_hubspot/types.py +45 -45
- {airbyte_agent_hubspot-0.15.25.dist-info → airbyte_agent_hubspot-0.15.43.dist-info}/METADATA +25 -22
- {airbyte_agent_hubspot-0.15.25.dist-info → airbyte_agent_hubspot-0.15.43.dist-info}/RECORD +34 -32
- {airbyte_agent_hubspot-0.15.25.dist-info → airbyte_agent_hubspot-0.15.43.dist-info}/WHEEL +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import inspect
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
8
9
|
import re
|
|
@@ -11,6 +12,7 @@ from collections.abc import AsyncIterator
|
|
|
11
12
|
from typing import Any, Protocol
|
|
12
13
|
from urllib.parse import quote
|
|
13
14
|
|
|
15
|
+
from jinja2 import Environment, StrictUndefined
|
|
14
16
|
from jsonpath_ng import parse as parse_jsonpath
|
|
15
17
|
from opentelemetry import trace
|
|
16
18
|
|
|
@@ -506,8 +508,6 @@ class LocalExecutor:
|
|
|
506
508
|
result = handler.execute_operation(config.entity, action, params)
|
|
507
509
|
|
|
508
510
|
# Check if it's an async generator (download) or awaitable (standard)
|
|
509
|
-
import inspect
|
|
510
|
-
|
|
511
511
|
if inspect.isasyncgen(result):
|
|
512
512
|
# Download operation: return generator directly
|
|
513
513
|
return ExecutionResult(
|
|
@@ -674,16 +674,16 @@ class LocalExecutor:
|
|
|
674
674
|
return {key: value for key, value in params.items() if key in allowed_params}
|
|
675
675
|
|
|
676
676
|
def _extract_body(self, allowed_fields: list[str], params: dict[str, Any]) -> dict[str, Any]:
|
|
677
|
-
"""Extract body fields from params.
|
|
677
|
+
"""Extract body fields from params, filtering out None values.
|
|
678
678
|
|
|
679
679
|
Args:
|
|
680
680
|
allowed_fields: List of allowed body field names
|
|
681
681
|
params: All parameters
|
|
682
682
|
|
|
683
683
|
Returns:
|
|
684
|
-
Dictionary of body fields
|
|
684
|
+
Dictionary of body fields with None values filtered out
|
|
685
685
|
"""
|
|
686
|
-
return {key: value for key, value in params.items() if key in allowed_fields}
|
|
686
|
+
return {key: value for key, value in params.items() if key in allowed_fields and value is not None}
|
|
687
687
|
|
|
688
688
|
def _serialize_deep_object_params(self, params: dict[str, Any], deep_object_param_names: list[str]) -> dict[str, Any]:
|
|
689
689
|
"""Serialize deepObject parameters to bracket notation format.
|
|
@@ -814,7 +814,6 @@ class LocalExecutor:
|
|
|
814
814
|
>>> _substitute_file_field_params("attachments[{attachment_index}].url", {"attachment_index": 0})
|
|
815
815
|
"attachments[0].url"
|
|
816
816
|
"""
|
|
817
|
-
from jinja2 import Environment, StrictUndefined
|
|
818
817
|
|
|
819
818
|
# Use custom delimiters to match OpenAPI path parameter syntax {var}
|
|
820
819
|
# StrictUndefined raises clear error if a template variable is missing
|
|
@@ -837,15 +836,65 @@ class LocalExecutor:
|
|
|
837
836
|
Request body dict or None if no body needed
|
|
838
837
|
"""
|
|
839
838
|
if endpoint.graphql_body:
|
|
840
|
-
|
|
839
|
+
# Extract defaults from query_params_schema for GraphQL variable interpolation
|
|
840
|
+
param_defaults = {name: schema.get("default") for name, schema in endpoint.query_params_schema.items() if "default" in schema}
|
|
841
|
+
return self._build_graphql_body(endpoint.graphql_body, params, param_defaults)
|
|
841
842
|
elif endpoint.body_fields:
|
|
842
843
|
return self._extract_body(endpoint.body_fields, params)
|
|
843
844
|
return None
|
|
844
845
|
|
|
846
|
+
def _flatten_form_data(self, data: dict[str, Any], parent_key: str = "") -> dict[str, Any]:
|
|
847
|
+
"""Flatten nested dict/list structures into bracket notation for form encoding.
|
|
848
|
+
|
|
849
|
+
Stripe and similar APIs require nested arrays/objects to be encoded using bracket
|
|
850
|
+
notation when using application/x-www-form-urlencoded content type.
|
|
851
|
+
|
|
852
|
+
Args:
|
|
853
|
+
data: Nested dict with arrays/objects to flatten
|
|
854
|
+
parent_key: Parent key for nested structures (used in recursion)
|
|
855
|
+
|
|
856
|
+
Returns:
|
|
857
|
+
Flattened dict with bracket notation keys
|
|
858
|
+
|
|
859
|
+
Examples:
|
|
860
|
+
>>> _flatten_form_data({"items": [{"price": "p1", "qty": 1}]})
|
|
861
|
+
{"items[0][price]": "p1", "items[0][qty]": 1}
|
|
862
|
+
|
|
863
|
+
>>> _flatten_form_data({"customer": "cus_123", "metadata": {"key": "value"}})
|
|
864
|
+
{"customer": "cus_123", "metadata[key]": "value"}
|
|
865
|
+
"""
|
|
866
|
+
flattened = {}
|
|
867
|
+
|
|
868
|
+
for key, value in data.items():
|
|
869
|
+
new_key = f"{parent_key}[{key}]" if parent_key else key
|
|
870
|
+
|
|
871
|
+
if isinstance(value, dict):
|
|
872
|
+
# Recursively flatten nested dicts
|
|
873
|
+
flattened.update(self._flatten_form_data(value, new_key))
|
|
874
|
+
elif isinstance(value, list):
|
|
875
|
+
# Flatten arrays with indexed bracket notation
|
|
876
|
+
for i, item in enumerate(value):
|
|
877
|
+
indexed_key = f"{new_key}[{i}]"
|
|
878
|
+
if isinstance(item, dict):
|
|
879
|
+
# Nested dict in array - recurse
|
|
880
|
+
flattened.update(self._flatten_form_data(item, indexed_key))
|
|
881
|
+
elif isinstance(item, list):
|
|
882
|
+
# Nested list in array - recurse
|
|
883
|
+
flattened.update(self._flatten_form_data({str(i): item}, new_key))
|
|
884
|
+
else:
|
|
885
|
+
# Primitive value in array
|
|
886
|
+
flattened[indexed_key] = item
|
|
887
|
+
else:
|
|
888
|
+
# Primitive value - add directly
|
|
889
|
+
flattened[new_key] = value
|
|
890
|
+
|
|
891
|
+
return flattened
|
|
892
|
+
|
|
845
893
|
def _determine_request_format(self, endpoint: EndpointDefinition, body: dict[str, Any] | None) -> dict[str, Any]:
|
|
846
894
|
"""Determine json/data parameters for HTTP request.
|
|
847
895
|
|
|
848
896
|
GraphQL always uses JSON, regardless of content_type setting.
|
|
897
|
+
For form-encoded requests, nested structures are flattened into bracket notation.
|
|
849
898
|
|
|
850
899
|
Args:
|
|
851
900
|
endpoint: Endpoint definition
|
|
@@ -862,7 +911,9 @@ class LocalExecutor:
|
|
|
862
911
|
if is_graphql or endpoint.content_type.value == "application/json":
|
|
863
912
|
return {"json": body}
|
|
864
913
|
elif endpoint.content_type.value == "application/x-www-form-urlencoded":
|
|
865
|
-
|
|
914
|
+
# Flatten nested structures for form encoding
|
|
915
|
+
flattened_body = self._flatten_form_data(body)
|
|
916
|
+
return {"data": flattened_body}
|
|
866
917
|
|
|
867
918
|
return {}
|
|
868
919
|
|
|
@@ -903,12 +954,18 @@ class LocalExecutor:
|
|
|
903
954
|
|
|
904
955
|
return query
|
|
905
956
|
|
|
906
|
-
def _build_graphql_body(
|
|
957
|
+
def _build_graphql_body(
|
|
958
|
+
self,
|
|
959
|
+
graphql_config: dict[str, Any],
|
|
960
|
+
params: dict[str, Any],
|
|
961
|
+
param_defaults: dict[str, Any] | None = None,
|
|
962
|
+
) -> dict[str, Any]:
|
|
907
963
|
"""Build GraphQL request body with variable substitution and field selection.
|
|
908
964
|
|
|
909
965
|
Args:
|
|
910
966
|
graphql_config: GraphQL configuration from x-airbyte-body-type extension
|
|
911
967
|
params: Parameters from execute() call
|
|
968
|
+
param_defaults: Default values for params from query_params_schema
|
|
912
969
|
|
|
913
970
|
Returns:
|
|
914
971
|
GraphQL request body: {"query": "...", "variables": {...}}
|
|
@@ -922,7 +979,7 @@ class LocalExecutor:
|
|
|
922
979
|
|
|
923
980
|
# Substitute variables from params
|
|
924
981
|
if "variables" in graphql_config and graphql_config["variables"]:
|
|
925
|
-
body["variables"] = self._interpolate_variables(graphql_config["variables"], params)
|
|
982
|
+
body["variables"] = self._interpolate_variables(graphql_config["variables"], params, param_defaults)
|
|
926
983
|
|
|
927
984
|
# Add operation name if specified
|
|
928
985
|
if "operationName" in graphql_config:
|
|
@@ -981,7 +1038,12 @@ class LocalExecutor:
|
|
|
981
1038
|
fields_str = " ".join(graphql_fields)
|
|
982
1039
|
return query.replace("{{ fields }}", fields_str)
|
|
983
1040
|
|
|
984
|
-
def _interpolate_variables(
|
|
1041
|
+
def _interpolate_variables(
|
|
1042
|
+
self,
|
|
1043
|
+
variables: dict[str, Any],
|
|
1044
|
+
params: dict[str, Any],
|
|
1045
|
+
param_defaults: dict[str, Any] | None = None,
|
|
1046
|
+
) -> dict[str, Any]:
|
|
985
1047
|
"""Recursively interpolate variables using params.
|
|
986
1048
|
|
|
987
1049
|
Preserves types (doesn't stringify everything).
|
|
@@ -990,15 +1052,18 @@ class LocalExecutor:
|
|
|
990
1052
|
- Direct replacement: "{{ owner }}" → params["owner"] (preserves type)
|
|
991
1053
|
- Nested objects: {"input": {"name": "{{ name }}"}}
|
|
992
1054
|
- Arrays: [{"id": "{{ id }}"}]
|
|
993
|
-
-
|
|
1055
|
+
- Default values: "{{ per_page }}" → param_defaults["per_page"] if not in params
|
|
1056
|
+
- Unsubstituted placeholders: "{{ states }}" → None (for optional params without defaults)
|
|
994
1057
|
|
|
995
1058
|
Args:
|
|
996
1059
|
variables: Variables dict with template placeholders
|
|
997
1060
|
params: Parameters to substitute
|
|
1061
|
+
param_defaults: Default values for params from query_params_schema
|
|
998
1062
|
|
|
999
1063
|
Returns:
|
|
1000
1064
|
Interpolated variables dict with types preserved
|
|
1001
1065
|
"""
|
|
1066
|
+
defaults = param_defaults or {}
|
|
1002
1067
|
|
|
1003
1068
|
def interpolate_value(value: Any) -> Any:
|
|
1004
1069
|
if isinstance(value, str):
|
|
@@ -1012,8 +1077,15 @@ class LocalExecutor:
|
|
|
1012
1077
|
value = value.replace(placeholder, str(param_value))
|
|
1013
1078
|
|
|
1014
1079
|
# Check if any unsubstituted placeholders remain
|
|
1015
|
-
# If so, return None (treats as "not provided" for optional params)
|
|
1016
1080
|
if re.search(r"\{\{\s*\w+\s*\}\}", value):
|
|
1081
|
+
# Extract placeholder name and check for default value
|
|
1082
|
+
match = re.search(r"\{\{\s*(\w+)\s*\}\}", value)
|
|
1083
|
+
if match:
|
|
1084
|
+
param_name = match.group(1)
|
|
1085
|
+
if param_name in defaults:
|
|
1086
|
+
# Use default value (preserves type)
|
|
1087
|
+
return defaults[param_name]
|
|
1088
|
+
# No default found - return None (for optional params)
|
|
1017
1089
|
return None
|
|
1018
1090
|
|
|
1019
1091
|
return value
|
|
@@ -1026,37 +1098,95 @@ class LocalExecutor:
|
|
|
1026
1098
|
|
|
1027
1099
|
return interpolate_value(variables)
|
|
1028
1100
|
|
|
1101
|
+
def _wrap_primitives(self, data: Any) -> dict[str, Any] | list[dict[str, Any]] | None:
|
|
1102
|
+
"""Wrap primitive values in dict format for consistent response structure.
|
|
1103
|
+
|
|
1104
|
+
Transforms primitive API responses into dict format so downstream code
|
|
1105
|
+
can always expect dict-based data structures.
|
|
1106
|
+
|
|
1107
|
+
Args:
|
|
1108
|
+
data: Response data (could be primitive, list, dict, or None)
|
|
1109
|
+
|
|
1110
|
+
Returns:
|
|
1111
|
+
- If data is a primitive (str, int, float, bool): {"value": data}
|
|
1112
|
+
- If data is a list: wraps all non-dict elements as {"value": item}
|
|
1113
|
+
- If data is already a dict or list of dicts: unchanged
|
|
1114
|
+
- If data is None: None
|
|
1115
|
+
|
|
1116
|
+
Examples:
|
|
1117
|
+
>>> executor._wrap_primitives(42)
|
|
1118
|
+
{"value": 42}
|
|
1119
|
+
>>> executor._wrap_primitives([1, 2, 3])
|
|
1120
|
+
[{"value": 1}, {"value": 2}, {"value": 3}]
|
|
1121
|
+
>>> executor._wrap_primitives([1, {"id": 2}, 3])
|
|
1122
|
+
[{"value": 1}, {"id": 2}, {"value": 3}]
|
|
1123
|
+
>>> executor._wrap_primitives([[1, 2], 3])
|
|
1124
|
+
[{"value": [1, 2]}, {"value": 3}]
|
|
1125
|
+
>>> executor._wrap_primitives({"id": 1})
|
|
1126
|
+
{"id": 1} # unchanged
|
|
1127
|
+
"""
|
|
1128
|
+
if data is None:
|
|
1129
|
+
return None
|
|
1130
|
+
|
|
1131
|
+
# Handle primitive scalars
|
|
1132
|
+
if isinstance(data, (bool, str, int, float)):
|
|
1133
|
+
return {"value": data}
|
|
1134
|
+
|
|
1135
|
+
# Handle lists - wrap non-dict elements
|
|
1136
|
+
if isinstance(data, list):
|
|
1137
|
+
if not data:
|
|
1138
|
+
return [] # Empty list unchanged
|
|
1139
|
+
|
|
1140
|
+
wrapped = []
|
|
1141
|
+
for item in data:
|
|
1142
|
+
if isinstance(item, dict):
|
|
1143
|
+
wrapped.append(item)
|
|
1144
|
+
else:
|
|
1145
|
+
wrapped.append({"value": item})
|
|
1146
|
+
return wrapped
|
|
1147
|
+
|
|
1148
|
+
# Dict - return unchanged
|
|
1149
|
+
if isinstance(data, dict):
|
|
1150
|
+
return data
|
|
1151
|
+
|
|
1152
|
+
# Unknown type - wrap for safety
|
|
1153
|
+
return {"value": data}
|
|
1154
|
+
|
|
1029
1155
|
def _extract_records(
|
|
1030
1156
|
self,
|
|
1031
|
-
response_data:
|
|
1157
|
+
response_data: Any,
|
|
1032
1158
|
endpoint: EndpointDefinition,
|
|
1033
|
-
) -> dict[str, Any] | list[Any] | None:
|
|
1159
|
+
) -> dict[str, Any] | list[dict[str, Any]] | None:
|
|
1034
1160
|
"""Extract records from response using record extractor.
|
|
1035
1161
|
|
|
1036
1162
|
Type inference based on action:
|
|
1037
1163
|
- list, search: Returns array ([] if not found)
|
|
1038
1164
|
- get, create, update, delete: Returns single record (None if not found)
|
|
1039
1165
|
|
|
1166
|
+
Automatically wraps primitive values (int, str, float, bool) in {"value": primitive}
|
|
1167
|
+
format to ensure consistent dict-based responses for downstream code.
|
|
1168
|
+
|
|
1040
1169
|
Args:
|
|
1041
|
-
response_data: Full API response
|
|
1170
|
+
response_data: Full API response (can be dict, list, primitive, or None)
|
|
1042
1171
|
endpoint: Endpoint with optional record extractor and action
|
|
1043
1172
|
|
|
1044
1173
|
Returns:
|
|
1045
1174
|
- Extracted data if extractor configured and path found
|
|
1046
1175
|
- [] or None if path not found (based on action)
|
|
1047
1176
|
- Original response if no extractor configured or on error
|
|
1177
|
+
- Primitives are wrapped as {"value": primitive}
|
|
1048
1178
|
"""
|
|
1049
1179
|
# Check if endpoint has record extractor
|
|
1050
1180
|
extractor = endpoint.record_extractor
|
|
1051
1181
|
if not extractor:
|
|
1052
|
-
return response_data
|
|
1182
|
+
return self._wrap_primitives(response_data)
|
|
1053
1183
|
|
|
1054
1184
|
# Determine if this action returns array or single record
|
|
1055
1185
|
action = endpoint.action
|
|
1056
1186
|
if not action:
|
|
1057
|
-
return response_data
|
|
1187
|
+
return self._wrap_primitives(response_data)
|
|
1058
1188
|
|
|
1059
|
-
is_array_action = action in (Action.LIST, Action.
|
|
1189
|
+
is_array_action = action in (Action.LIST, Action.API_SEARCH)
|
|
1060
1190
|
|
|
1061
1191
|
try:
|
|
1062
1192
|
# Parse and apply JSONPath expression
|
|
@@ -1067,17 +1197,19 @@ class LocalExecutor:
|
|
|
1067
1197
|
# Path not found - return empty based on action
|
|
1068
1198
|
return [] if is_array_action else None
|
|
1069
1199
|
|
|
1070
|
-
# Return extracted data
|
|
1200
|
+
# Return extracted data with primitive wrapping
|
|
1071
1201
|
if is_array_action:
|
|
1072
1202
|
# For array actions, return the array (or list of matches)
|
|
1073
|
-
|
|
1203
|
+
result = matches[0] if len(matches) == 1 else matches
|
|
1074
1204
|
else:
|
|
1075
1205
|
# For single record actions, return first match
|
|
1076
|
-
|
|
1206
|
+
result = matches[0]
|
|
1207
|
+
|
|
1208
|
+
return self._wrap_primitives(result)
|
|
1077
1209
|
|
|
1078
1210
|
except Exception as e:
|
|
1079
1211
|
logging.warning(f"Failed to apply record extractor '{extractor}': {e}. Returning original response.")
|
|
1080
|
-
return response_data
|
|
1212
|
+
return self._wrap_primitives(response_data)
|
|
1081
1213
|
|
|
1082
1214
|
def _extract_metadata(
|
|
1083
1215
|
self,
|
|
@@ -1151,17 +1283,14 @@ class LocalExecutor:
|
|
|
1151
1283
|
if action not in (Action.CREATE, Action.UPDATE):
|
|
1152
1284
|
return
|
|
1153
1285
|
|
|
1154
|
-
#
|
|
1155
|
-
|
|
1286
|
+
# Get the request schema to find truly required fields
|
|
1287
|
+
request_schema = endpoint.request_schema
|
|
1288
|
+
if not request_schema:
|
|
1156
1289
|
return
|
|
1157
1290
|
|
|
1158
|
-
#
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
missing_fields = []
|
|
1162
|
-
for field in endpoint.body_fields:
|
|
1163
|
-
if field not in params:
|
|
1164
|
-
missing_fields.append(field)
|
|
1291
|
+
# Only validate fields explicitly marked as required in the schema
|
|
1292
|
+
required_fields = request_schema.get("required", [])
|
|
1293
|
+
missing_fields = [field for field in required_fields if field not in params]
|
|
1165
1294
|
|
|
1166
1295
|
if missing_fields:
|
|
1167
1296
|
raise MissingParameterError(
|
|
@@ -1189,7 +1318,7 @@ class LocalExecutor:
|
|
|
1189
1318
|
|
|
1190
1319
|
|
|
1191
1320
|
class _StandardOperationHandler:
|
|
1192
|
-
"""Handler for standard REST operations (GET, LIST, CREATE, UPDATE, DELETE,
|
|
1321
|
+
"""Handler for standard REST operations (GET, LIST, CREATE, UPDATE, DELETE, API_SEARCH, AUTHORIZE)."""
|
|
1193
1322
|
|
|
1194
1323
|
def __init__(self, context: _OperationContext):
|
|
1195
1324
|
self.ctx = context
|
|
@@ -1202,7 +1331,7 @@ class _StandardOperationHandler:
|
|
|
1202
1331
|
Action.CREATE,
|
|
1203
1332
|
Action.UPDATE,
|
|
1204
1333
|
Action.DELETE,
|
|
1205
|
-
Action.
|
|
1334
|
+
Action.API_SEARCH,
|
|
1206
1335
|
Action.AUTHORIZE,
|
|
1207
1336
|
}
|
|
1208
1337
|
|
|
@@ -159,6 +159,38 @@ Example:
|
|
|
159
159
|
```
|
|
160
160
|
"""
|
|
161
161
|
|
|
162
|
+
AIRBYTE_STREAM_NAME = "x-airbyte-stream-name"
|
|
163
|
+
"""
|
|
164
|
+
Extension: x-airbyte-stream-name
|
|
165
|
+
Location: Schema object (in components.schemas)
|
|
166
|
+
Type: string
|
|
167
|
+
Required: No
|
|
168
|
+
|
|
169
|
+
Description:
|
|
170
|
+
Specifies the Airbyte stream name for cache lookup purposes. This maps the entity
|
|
171
|
+
to the corresponding Airbyte stream, enabling cache-based data retrieval. When
|
|
172
|
+
specified, the EntityDefinition.stream_name field will be populated with this value.
|
|
173
|
+
|
|
174
|
+
This extension is placed on Schema objects alongside x-airbyte-entity-name, following
|
|
175
|
+
the same pattern. The stream name is an entity-level property (not operation-level)
|
|
176
|
+
since an entity maps to exactly one Airbyte stream.
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
```yaml
|
|
180
|
+
components:
|
|
181
|
+
schemas:
|
|
182
|
+
Customer:
|
|
183
|
+
type: object
|
|
184
|
+
x-airbyte-entity-name: customers
|
|
185
|
+
x-airbyte-stream-name: customers
|
|
186
|
+
properties:
|
|
187
|
+
id:
|
|
188
|
+
type: string
|
|
189
|
+
name:
|
|
190
|
+
type: string
|
|
191
|
+
```
|
|
192
|
+
"""
|
|
193
|
+
|
|
162
194
|
AIRBYTE_TOKEN_PATH = "x-airbyte-token-path"
|
|
163
195
|
"""
|
|
164
196
|
Extension: x-airbyte-token-path
|
|
@@ -495,8 +527,8 @@ class ActionType(str, Enum):
|
|
|
495
527
|
DELETE = "delete"
|
|
496
528
|
"""Delete a record"""
|
|
497
529
|
|
|
498
|
-
|
|
499
|
-
"""Search for records matching specific query criteria"""
|
|
530
|
+
API_SEARCH = "api_search"
|
|
531
|
+
"""Search for records matching specific query criteria via API"""
|
|
500
532
|
|
|
501
533
|
DOWNLOAD = "download"
|
|
502
534
|
"""Download file content from a URL specified in the metadata response"""
|
|
@@ -514,7 +546,7 @@ class BodyType(str, Enum):
|
|
|
514
546
|
|
|
515
547
|
|
|
516
548
|
# Type alias for use in Pydantic models
|
|
517
|
-
ActionTypeLiteral = Literal["get", "list", "create", "update", "delete", "
|
|
549
|
+
ActionTypeLiteral = Literal["get", "list", "create", "update", "delete", "api_search", "download"]
|
|
518
550
|
|
|
519
551
|
|
|
520
552
|
# =============================================================================
|
|
@@ -548,6 +580,7 @@ def get_all_extension_names() -> list[str]:
|
|
|
548
580
|
AIRBYTE_ENTITY,
|
|
549
581
|
AIRBYTE_ACTION,
|
|
550
582
|
AIRBYTE_ENTITY_NAME,
|
|
583
|
+
AIRBYTE_STREAM_NAME,
|
|
551
584
|
AIRBYTE_TOKEN_PATH,
|
|
552
585
|
AIRBYTE_BODY_TYPE,
|
|
553
586
|
AIRBYTE_PATH_OVERRIDE,
|
|
@@ -594,6 +627,12 @@ EXTENSION_REGISTRY = {
|
|
|
594
627
|
"required": False,
|
|
595
628
|
"description": "Links schema to an entity/stream",
|
|
596
629
|
},
|
|
630
|
+
AIRBYTE_STREAM_NAME: {
|
|
631
|
+
"location": "schema",
|
|
632
|
+
"type": "string",
|
|
633
|
+
"required": False,
|
|
634
|
+
"description": "Maps entity to Airbyte stream for cache lookup",
|
|
635
|
+
},
|
|
597
636
|
AIRBYTE_TOKEN_PATH: {
|
|
598
637
|
"location": "securityScheme",
|
|
599
638
|
"type": "string",
|
|
@@ -627,8 +666,7 @@ EXTENSION_REGISTRY = {
|
|
|
627
666
|
"type": "dict[str, str]",
|
|
628
667
|
"required": False,
|
|
629
668
|
"description": (
|
|
630
|
-
"Dictionary mapping field names to JSONPath expressions for extracting metadata "
|
|
631
|
-
"(pagination, request IDs, etc.) from response envelopes"
|
|
669
|
+
"Dictionary mapping field names to JSONPath expressions for extracting metadata (pagination, request IDs, etc.) from response envelopes"
|
|
632
670
|
),
|
|
633
671
|
},
|
|
634
672
|
AIRBYTE_FILE_URL: {
|
|
@@ -80,6 +80,8 @@ class HTTPResponse:
|
|
|
80
80
|
HTTPStatusError: For 4xx or 5xx status codes.
|
|
81
81
|
"""
|
|
82
82
|
if 400 <= self._status_code < 600:
|
|
83
|
+
# NOTE: Import here intentionally to avoid circular import.
|
|
84
|
+
# exceptions.py imports HTTPResponse for type hints.
|
|
83
85
|
from .exceptions import HTTPStatusError
|
|
84
86
|
|
|
85
87
|
raise HTTPStatusError(
|