airbyte-source-google-ads 4.1.0rc7.dev202510171337__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-source-google-ads
3
- Version: 4.1.0rc7.dev202510171337
3
+ Version: 4.1.0rc7.dev202510212244
4
4
  Summary: Source implementation for Google Ads.
5
5
  Home-page: https://airbyte.com
6
6
  License: Elv2
@@ -1,8 +1,8 @@
1
1
  source_google_ads/__init__.py,sha256=Nlo5H6LlaSgg7tx_LyqMIy3MXiAagfk3izZ9o44VvSE,1201
2
- source_google_ads/components.py,sha256=7L__n98oQmBGyyqO-rhnK28--2aj1CKtqoxqcwpBjo4,34017
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=hzCtWC4cTAlTF-GpAn0Z5EwkqiUMrJDOC8a7LbrW1eI,218086
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.dev202510171337.dist-info/METADATA,sha256=MW61eHrQNbtLBXK8HpM2_-EfEJfsGbWKmaaT9mqHJhM,5379
15
- airbyte_source_google_ads-4.1.0rc7.dev202510171337.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
16
- airbyte_source_google_ads-4.1.0rc7.dev202510171337.dist-info/entry_points.txt,sha256=pP4Llir93XGkHFDZfXXxK7qOWo9_U1ssCJToyxEUB4w,63
17
- airbyte_source_google_ads-4.1.0rc7.dev202510171337.dist-info/RECORD,,
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,,
@@ -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
+
@@ -5,7 +5,7 @@ type: DeclarativeSource
5
5
  check:
6
6
  type: CheckStream
7
7
  stream_names:
8
- - campaign
8
+ - customer
9
9
 
10
10
  definitions:
11
11
  # Authenticator Definitions
@@ -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:search"
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