airbyte-source-google-ads 4.1.0rc7.dev202510171521__py3-none-any.whl → 4.1.0rc7.dev202510212244__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_source_google_ads-4.1.0rc7.dev202510171521.dist-info → airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info}/METADATA +1 -1
- {airbyte_source_google_ads-4.1.0rc7.dev202510171521.dist-info → airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info}/RECORD +6 -6
- source_google_ads/components.py +153 -2
- source_google_ads/manifest.yaml +14 -2
- {airbyte_source_google_ads-4.1.0rc7.dev202510171521.dist-info → airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info}/WHEEL +0 -0
- {airbyte_source_google_ads-4.1.0rc7.dev202510171521.dist-info → airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info}/entry_points.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
source_google_ads/__init__.py,sha256=Nlo5H6LlaSgg7tx_LyqMIy3MXiAagfk3izZ9o44VvSE,1201
|
|
2
|
-
source_google_ads/components.py,sha256=
|
|
2
|
+
source_google_ads/components.py,sha256=bp2uW_dxo5hT8NXnBAWWDNi3saVCEbh7uU7XHa2nSyI,40037
|
|
3
3
|
source_google_ads/config_migrations.py,sha256=oBi_qNqBpLS8GNCaIOBo0stNdYuyqVl6lkrhdXRwMX8,4405
|
|
4
4
|
source_google_ads/google_ads.py,sha256=cxS18tz0fFJjmIhlhFQ3Zvu2K8bhDtmsl1kFeO7nNhk,11595
|
|
5
|
-
source_google_ads/manifest.yaml,sha256=
|
|
5
|
+
source_google_ads/manifest.yaml,sha256=u0qwByoWFe8GLuxqYqRqkUGV2FOB5n9rioZhHx4cqTc,218452
|
|
6
6
|
source_google_ads/models.py,sha256=ZmdS3z_2roaEQgV2Mx1CDm33MztpQ66SfHDzP8XwZog,1658
|
|
7
7
|
source_google_ads/run.py,sha256=ydIyq_vSNV5Z4mJYnsO5GyNDsLDd0qibBsq6wnvuFAo,2002
|
|
8
8
|
source_google_ads/schemas/customer_client.json,sha256=oThcyUDO1yWpxtWPWdoAFqTXEIweF8N4q6mRI73Q6yU,984
|
|
@@ -11,7 +11,7 @@ source_google_ads/source.py,sha256=hz5ep6stMWHNvD73PIF_7bjnee49sY9YHHjaYNAPnOQ,1
|
|
|
11
11
|
source_google_ads/spec.json,sha256=8hbc7smbaffIkYCkX2BYJLB9kgaH8vYKCg-H0y1FvUs,7810
|
|
12
12
|
source_google_ads/streams.py,sha256=FB-DNJlXhjQADptT-wrv3iGWoliyRuvDuHGeqiN9HsY,13349
|
|
13
13
|
source_google_ads/utils.py,sha256=-KpgGv2W8WueXvGRC3xbVreDl5-5-vU9OwzC5SZDKVc,21409
|
|
14
|
-
airbyte_source_google_ads-4.1.0rc7.
|
|
15
|
-
airbyte_source_google_ads-4.1.0rc7.
|
|
16
|
-
airbyte_source_google_ads-4.1.0rc7.
|
|
17
|
-
airbyte_source_google_ads-4.1.0rc7.
|
|
14
|
+
airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info/METADATA,sha256=jWTT3CuzoqUYd--izNeWytf-dHqGyaorZfu_ephGHec,5379
|
|
15
|
+
airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
16
|
+
airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info/entry_points.txt,sha256=pP4Llir93XGkHFDZfXXxK7qOWo9_U1ssCJToyxEUB4w,63
|
|
17
|
+
airbyte_source_google_ads-4.1.0rc7.dev202510212244.dist-info/RECORD,,
|
source_google_ads/components.py
CHANGED
|
@@ -6,9 +6,9 @@ import json
|
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
8
|
import threading
|
|
9
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import InitVar, dataclass
|
|
10
10
|
from itertools import groupby
|
|
11
|
-
from typing import Any, Callable, Dict, Iterable, List, Mapping, MutableMapping, Optional, Tuple, Union
|
|
11
|
+
from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, MutableMapping, Optional, Tuple, Union
|
|
12
12
|
|
|
13
13
|
import anyascii
|
|
14
14
|
import requests
|
|
@@ -25,6 +25,8 @@ from airbyte_cdk.sources.declarative.transformations import RecordTransformation
|
|
|
25
25
|
from airbyte_cdk.sources.streams.concurrent.default_stream import DefaultStream
|
|
26
26
|
from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
|
|
27
27
|
from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer
|
|
28
|
+
from airbyte_cdk.sources.declarative.decoders.decoder import Decoder
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
from .google_ads import GoogleAds
|
|
30
32
|
|
|
@@ -285,6 +287,10 @@ class GoogleAdsHttpRequester(HttpRequester):
|
|
|
285
287
|
|
|
286
288
|
schema_loader: InlineSchemaLoader = None
|
|
287
289
|
|
|
290
|
+
def __post_init__(self, parameters: Mapping[str, Any]) -> None:
|
|
291
|
+
super().__post_init__(parameters)
|
|
292
|
+
self.stream_response = True
|
|
293
|
+
|
|
288
294
|
def get_request_body_json(
|
|
289
295
|
self,
|
|
290
296
|
*,
|
|
@@ -889,3 +895,148 @@ class CustomGAQuerySchemaLoader(SchemaLoader):
|
|
|
889
895
|
internal_message=f"The provided query is invalid: {query}. Please refer to the Google Ads API documentation for the correct syntax: https://developers.google.com/google-ads/api/fields/v20/overview and test validate your query using the Google Ads Query Builder: https://developers.google.com/google-ads/api/fields/v20/query_validator",
|
|
890
896
|
message=f"The provided query is invalid: {query}. Please refer to the Google Ads API documentation for the correct syntax: https://developers.google.com/google-ads/api/fields/v20/overview and test validate your query using the Google Ads Query Builder: https://developers.google.com/google-ads/api/fields/v20/query_validator",
|
|
891
897
|
)
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
@dataclass
|
|
901
|
+
class RowsStreamingDecoder(Decoder):
|
|
902
|
+
parameters: InitVar[Mapping[str, Any]]
|
|
903
|
+
|
|
904
|
+
def is_stream_response(self) -> bool:
|
|
905
|
+
return True
|
|
906
|
+
|
|
907
|
+
def decode(
|
|
908
|
+
self, response: requests.Response
|
|
909
|
+
) -> Generator[MutableMapping[str, Any], None, None]:
|
|
910
|
+
for row in self._iter_rows_from_bytes(response.iter_content(chunk_size=65536)):
|
|
911
|
+
yield {"results": [row]}
|
|
912
|
+
|
|
913
|
+
def _iter_rows_from_bytes(self, byte_iter: Iterable[bytes], encoding: str = "utf-8") -> Generator[Dict[str, Any], None, None]:
|
|
914
|
+
"""
|
|
915
|
+
Incrementally scan the searchStream response and yield each object from the
|
|
916
|
+
top-level "results" array as soon as that object is complete, without waiting
|
|
917
|
+
for the enclosing message object to finish.
|
|
918
|
+
|
|
919
|
+
This is a character-level state machine:
|
|
920
|
+
- Handles split chunks and concatenated JSON objects
|
|
921
|
+
- Tracks strings/escapes so braces inside strings don't confuse depth
|
|
922
|
+
- Detects the `"results": [` array and streams its items one-by-one
|
|
923
|
+
"""
|
|
924
|
+
# Global scanning state
|
|
925
|
+
depth = 0
|
|
926
|
+
in_str = False
|
|
927
|
+
esc = False
|
|
928
|
+
|
|
929
|
+
# Detect the "results" array
|
|
930
|
+
last_string = None # last completed JSON string token
|
|
931
|
+
awaiting_results_array = False
|
|
932
|
+
results_array_depth = None # the depth level of the '[' that starts the array
|
|
933
|
+
|
|
934
|
+
# Per-item buffering state
|
|
935
|
+
collecting_item = False
|
|
936
|
+
item_buf = [] # characters of the current item
|
|
937
|
+
item_depth = 0 # nesting within the item (starts at 1 when we see '{')
|
|
938
|
+
|
|
939
|
+
# Temp buffer for current string token
|
|
940
|
+
str_buf = []
|
|
941
|
+
|
|
942
|
+
def finish_item():
|
|
943
|
+
nonlocal item_buf, collecting_item, item_depth
|
|
944
|
+
obj_text = "".join(item_buf).strip()
|
|
945
|
+
item_buf = []
|
|
946
|
+
collecting_item = False
|
|
947
|
+
item_depth = 0
|
|
948
|
+
if obj_text:
|
|
949
|
+
return json.loads(obj_text)
|
|
950
|
+
|
|
951
|
+
for chunk in byte_iter:
|
|
952
|
+
text = chunk.decode(encoding, errors="replace")
|
|
953
|
+
for ch in text:
|
|
954
|
+
|
|
955
|
+
# Always feed characters to item buffer if we're inside an item
|
|
956
|
+
if collecting_item:
|
|
957
|
+
item_buf.append(ch)
|
|
958
|
+
|
|
959
|
+
# --- String handling (so braces inside strings are ignored) ---
|
|
960
|
+
if in_str:
|
|
961
|
+
if esc:
|
|
962
|
+
esc = False
|
|
963
|
+
continue
|
|
964
|
+
if ch == "\\":
|
|
965
|
+
esc = True
|
|
966
|
+
continue
|
|
967
|
+
if ch == '"':
|
|
968
|
+
# string ended
|
|
969
|
+
in_str = False
|
|
970
|
+
last_string = "".join(str_buf)
|
|
971
|
+
str_buf = []
|
|
972
|
+
else:
|
|
973
|
+
str_buf.append(ch)
|
|
974
|
+
continue
|
|
975
|
+
|
|
976
|
+
if ch == '"':
|
|
977
|
+
in_str = True
|
|
978
|
+
str_buf = []
|
|
979
|
+
# If we are collecting an item, we already appended the quote to item_buf above
|
|
980
|
+
continue
|
|
981
|
+
|
|
982
|
+
# --- Structural characters outside strings ---
|
|
983
|
+
if ch in "{[":
|
|
984
|
+
depth += 1
|
|
985
|
+
|
|
986
|
+
# Detect the start of the "results" array: we just saw '[' after key "results": ...
|
|
987
|
+
if ch == "[" and awaiting_results_array and results_array_depth is None:
|
|
988
|
+
results_array_depth = depth # this '[' depth
|
|
989
|
+
awaiting_results_array = False
|
|
990
|
+
|
|
991
|
+
# Detect the start of an item object directly inside "results"
|
|
992
|
+
if (
|
|
993
|
+
ch == "{"
|
|
994
|
+
and results_array_depth is not None
|
|
995
|
+
and not collecting_item
|
|
996
|
+
and depth == results_array_depth + 1
|
|
997
|
+
):
|
|
998
|
+
collecting_item = True
|
|
999
|
+
item_buf = ["{"] # start buffer anew
|
|
1000
|
+
item_depth = 1
|
|
1001
|
+
elif collecting_item and ch in "{[":
|
|
1002
|
+
# Nested structure inside item
|
|
1003
|
+
item_depth += 1
|
|
1004
|
+
|
|
1005
|
+
continue
|
|
1006
|
+
|
|
1007
|
+
if ch in "}]":
|
|
1008
|
+
# If we're collecting an item, adjust its own nesting counter
|
|
1009
|
+
if collecting_item:
|
|
1010
|
+
item_depth -= 1
|
|
1011
|
+
if item_depth == 0:
|
|
1012
|
+
# Item just finished -> emit it immediately
|
|
1013
|
+
item = finish_item()
|
|
1014
|
+
if item is not None:
|
|
1015
|
+
yield item
|
|
1016
|
+
# Note: we do NOT 'continue' here; we still need to update global depth below
|
|
1017
|
+
|
|
1018
|
+
depth -= 1
|
|
1019
|
+
|
|
1020
|
+
# If we closed the results array, reset array tracking
|
|
1021
|
+
if (
|
|
1022
|
+
ch == "]"
|
|
1023
|
+
and results_array_depth is not None
|
|
1024
|
+
and depth < results_array_depth
|
|
1025
|
+
):
|
|
1026
|
+
results_array_depth = None
|
|
1027
|
+
continue
|
|
1028
|
+
|
|
1029
|
+
# Detect `"results":` key just seen (outside strings)
|
|
1030
|
+
if ch == ":" and last_string == "results" and results_array_depth is None:
|
|
1031
|
+
awaiting_results_array = True
|
|
1032
|
+
# don't 'continue'; normal flow is fine
|
|
1033
|
+
|
|
1034
|
+
# Commas/whitespace are irrelevant; any other chars just pass through
|
|
1035
|
+
|
|
1036
|
+
# End of stream: if we somehow have a finished item without seeing the closing bracket
|
|
1037
|
+
# (rare, but be defensive), try to flush.
|
|
1038
|
+
if collecting_item and item_depth == 0:
|
|
1039
|
+
item = finish_item()
|
|
1040
|
+
if item is not None:
|
|
1041
|
+
yield item
|
|
1042
|
+
|
source_google_ads/manifest.yaml
CHANGED
|
@@ -38,7 +38,7 @@ definitions:
|
|
|
38
38
|
$ref: "#/schemas"
|
|
39
39
|
authenticator:
|
|
40
40
|
$ref: "#/definitions/authenticator"
|
|
41
|
-
url_base: "https://googleads.googleapis.com/v20/{{ stream_partition['customer_id'] }}/googleAds:
|
|
41
|
+
url_base: "https://googleads.googleapis.com/v20/{{ stream_partition['customer_id'] }}/googleAds:searchStream"
|
|
42
42
|
http_method: POST
|
|
43
43
|
error_handler:
|
|
44
44
|
$ref: "#/definitions/base_error_handler"
|
|
@@ -56,7 +56,7 @@ definitions:
|
|
|
56
56
|
action: IGNORE
|
|
57
57
|
http_codes:
|
|
58
58
|
- 403
|
|
59
|
-
error_message_contains: "The customer account can\\'t be accessed because it is not yet enabled or has been deactivated."
|
|
59
|
+
# error_message_contains: "The customer account can\\'t be accessed because it is not yet enabled or has been deactivated."
|
|
60
60
|
|
|
61
61
|
base_selector:
|
|
62
62
|
type: RecordSelector
|
|
@@ -98,6 +98,11 @@ definitions:
|
|
|
98
98
|
type: DeclarativeStream
|
|
99
99
|
retriever:
|
|
100
100
|
$ref: "#/definitions/base_retriever"
|
|
101
|
+
paginator:
|
|
102
|
+
type: NoPagination
|
|
103
|
+
decoder:
|
|
104
|
+
type: CustomDecoder
|
|
105
|
+
class_name: "source_google_ads.components.RowsStreamingDecoder"
|
|
101
106
|
requester:
|
|
102
107
|
$ref: "#/definitions/stream_requester"
|
|
103
108
|
record_selector:
|
|
@@ -131,6 +136,11 @@ definitions:
|
|
|
131
136
|
$ref: "#/definitions/base_retriever"
|
|
132
137
|
requester:
|
|
133
138
|
$ref: "#/definitions/stream_requester"
|
|
139
|
+
paginator:
|
|
140
|
+
type: NoPagination
|
|
141
|
+
decoder:
|
|
142
|
+
type: CustomDecoder
|
|
143
|
+
class_name: "source_google_ads.components.RowsStreamingDecoder"
|
|
134
144
|
record_selector:
|
|
135
145
|
extractor:
|
|
136
146
|
type: DpathExtractor
|
|
@@ -659,6 +669,8 @@ definitions:
|
|
|
659
669
|
$ref: "#/definitions/base_error_handler"
|
|
660
670
|
paginator:
|
|
661
671
|
$ref: "#/definitions/cursor_paginator"
|
|
672
|
+
decoder:
|
|
673
|
+
type: JsonDecoder
|
|
662
674
|
incremental_sync:
|
|
663
675
|
type: DatetimeBasedCursor
|
|
664
676
|
cursor_field: segments.date
|
|
File without changes
|