airbyte-source-google-ads 4.2.2__tar.gz → 4.2.3__tar.gz

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 (16) hide show
  1. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/PKG-INFO +1 -1
  2. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/pyproject.toml +1 -1
  3. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/components.py +55 -1
  4. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/manifest.yaml +2 -0
  5. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/README.md +0 -0
  6. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/__init__.py +0 -0
  7. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/config_migrations.py +0 -0
  8. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/google_ads.py +0 -0
  9. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/models.py +0 -0
  10. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/run.py +0 -0
  11. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/schemas/customer_client.json +0 -0
  12. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/schemas/service_accounts.json +0 -0
  13. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/source.py +0 -0
  14. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/spec.json +0 -0
  15. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/streams.py +0 -0
  16. {airbyte_source_google_ads-4.2.2 → airbyte_source_google_ads-4.2.3}/source_google_ads/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-source-google-ads
3
- Version: 4.2.2
3
+ Version: 4.2.3
4
4
  Summary: Source implementation for Google Ads.
5
5
  Home-page: https://airbyte.com
6
6
  License: Elv2
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
3
3
  build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
- version = "4.2.2"
6
+ version = "4.2.3"
7
7
  name = "airbyte-source-google-ads"
8
8
  description = "Source implementation for Google Ads."
9
9
  authors = [ "Airbyte <contact@airbyte.io>",]
@@ -10,7 +10,7 @@ import threading
10
10
  from dataclasses import dataclass, field
11
11
  from datetime import datetime, timedelta
12
12
  from itertools import groupby
13
- from typing import Any, Callable, Dict, Generator, Iterable, List, Mapping, MutableMapping, Optional, Tuple, Union
13
+ from typing import Any, Callable, ClassVar, Dict, Generator, Iterable, List, Mapping, MutableMapping, Optional, Set, Tuple, Union
14
14
 
15
15
  import anyascii
16
16
  import requests
@@ -147,6 +147,55 @@ class FlattenNestedDictsTransformation(RecordTransformation):
147
147
  _flatten(top_key, nested)
148
148
 
149
149
 
150
+ @dataclass
151
+ class SerializeMessageFieldsTransformation(RecordTransformation):
152
+ """
153
+ Serializes MESSAGE-type fields (nested dict values) to JSON strings before
154
+ flattening occurs. This prevents FlattenNestedDictsTransformation from
155
+ recursively flattening MESSAGE fields like change_event.old_resource and
156
+ change_event.new_resource, which would create sub-keys not present in
157
+ the schema and cause the original field values to be lost (returned as NULL).
158
+
159
+ This transformation must run AFTER KeysToSnakeCaseGoogleAdsTransformation
160
+ and BEFORE FlattenNestedDictsTransformation in the custom query stream
161
+ transformation chain.
162
+
163
+ The set of MESSAGE-type field names is populated by
164
+ CustomGAQuerySchemaLoader.get_json_schema() during stream initialization
165
+ and stored on the CustomGAQuerySchemaLoader class.
166
+ """
167
+
168
+ def transform(
169
+ self,
170
+ record: Dict[str, Any],
171
+ config: Optional[Config] = None,
172
+ stream_state: Optional[StreamState] = None,
173
+ stream_slice: Optional[StreamSlice] = None,
174
+ ) -> None:
175
+ message_fields: set = CustomGAQuerySchemaLoader._all_message_fields
176
+ if not message_fields:
177
+ return
178
+
179
+ for field_name in message_fields:
180
+ parts = field_name.split(".")
181
+ current = record
182
+ for part in parts[:-1]:
183
+ if isinstance(current, dict) and part in current:
184
+ current = current[part]
185
+ else:
186
+ current = None
187
+ break
188
+
189
+ if current is not None and isinstance(current, dict):
190
+ last_part = parts[-1]
191
+ if last_part in current:
192
+ value = current[last_part]
193
+ if isinstance(value, dict):
194
+ current[last_part] = json.dumps(value)
195
+ elif isinstance(value, list):
196
+ current[last_part] = [json.dumps(item) if isinstance(item, dict) else item for item in value]
197
+
198
+
150
199
  class DoubleQuotedDictTypeTransformer(TypeTransformer):
151
200
  """
152
201
  Convert arrays of dicts into JSON-formatted string arrays using double quotes only
@@ -928,6 +977,7 @@ class CustomGAQuerySchemaLoader(SchemaLoader):
928
977
 
929
978
  _google_ads_client: Optional[GoogleAds] = None
930
979
  _client_lock = threading.Lock()
980
+ _all_message_fields: ClassVar[Set[str]] = set()
931
981
 
932
982
  def __post_init__(self):
933
983
  self._cursor_field = InterpolatedString.create(self.cursor_field, parameters={}) if self.cursor_field else None
@@ -964,10 +1014,14 @@ class CustomGAQuerySchemaLoader(SchemaLoader):
964
1014
  fields = self._get_list_of_fields()
965
1015
  fields_metadata = self.google_ads_client(self.config).get_fields_metadata(fields)
966
1016
 
1017
+ message_fields: set = set()
967
1018
  for field, field_metadata in fields_metadata.items():
1019
+ if field_metadata.data_type.name == "MESSAGE":
1020
+ message_fields.add(field)
968
1021
  field_value = self._build_field_value(field, field_metadata)
969
1022
  local_json_schema["properties"][field] = field_value
970
1023
 
1024
+ CustomGAQuerySchemaLoader._all_message_fields = CustomGAQuerySchemaLoader._all_message_fields | message_fields
971
1025
  return local_json_schema
972
1026
 
973
1027
  def _build_field_value(self, field: str, field_metadata) -> Any:
@@ -425,6 +425,8 @@ definitions:
425
425
  transformations:
426
426
  - type: CustomTransformation
427
427
  class_name: "source_google_ads.components.KeysToSnakeCaseGoogleAdsTransformation"
428
+ - type: CustomTransformation
429
+ class_name: "source_google_ads.components.SerializeMessageFieldsTransformation"
428
430
  - type: CustomTransformation
429
431
  class_name: "source_google_ads.components.FlattenNestedDictsTransformation"
430
432
  - type: AddFields