Content-Length: 705237 | pFad | https://github.com/apache/airflow/commit/5d2224795b3548516311025d5549094a9b168f3b

5D Google Ads Hook: Support newer versions of the google-ads library (#1… · apache/airflow@5d22247 · GitHub
Skip to content

Commit 5d22247

Browse files
authored
Google Ads Hook: Support newer versions of the google-ads library (#17160)
1 parent 966b250 commit 5d22247

File tree

5 files changed

+113
-36
lines changed

5 files changed

+113
-36
lines changed

airflow/providers/google/CHANGELOG.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ Changelog
2121
4.1.0
2222
.....
2323

24+
Breaking changes
25+
~~~~~~~~~~~~~~~~
26+
27+
* ``Updated GoogleAdsHook to support newer API versions after google deprecated v5. Google Ads v8 is the new default API. (#17111)``
28+
29+
.. warning:: The underlying google-ads library had breaking changes.
30+
31+
Previously the google ads library returned data as native protobuf messages. Now it returns data as proto-plus objects that behave more like conventional Python objects.
32+
33+
To preserve compatibility the hook's `search()` converts the data back to native protobuf before returning it. Your existing operators *should* work as before, but due to the urgency of the v5 API being deprecated it was not tested too thoroughly. Therefore you should carefully evaluate your operator and hook functionality with this new version.
34+
35+
In order to use the API's new proto-plus format, you can use the `search_proto_plus()` method.
36+
37+
For more information, please consult `google-ads migration document <https://developers.google.com/google-ads/api/docs/client-libs/python/library-version-10>`__:
38+
39+
2440
Features
2541
~~~~~~~~
2642

airflow/providers/google/ads/hooks/ads.py

Lines changed: 94 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
from functools import cached_property
2424
except ImportError:
2525
from cached_property import cached_property
26-
from google.ads.google_ads.client import GoogleAdsClient
27-
from google.ads.google_ads.errors import GoogleAdsException
28-
from google.ads.google_ads.v2.types import GoogleAdsRow
26+
from google.ads.googleads.client import GoogleAdsClient
27+
from google.ads.googleads.errors import GoogleAdsException
28+
from google.ads.googleads.v8.services.types.google_ads_service import GoogleAdsRow
2929
from google.api_core.page_iterator import GRPCIterator
3030
from google.auth.exceptions import GoogleAuthError
3131
from googleapiclient.discovery import Resource
@@ -76,7 +76,7 @@ class GoogleAdsHook(BaseHook):
7676
:rtype: list[GoogleAdsRow]
7777
"""
7878

79-
default_api_version = "v5"
79+
default_api_version = "v8"
8080

8181
def __init__(
8282
self,
@@ -90,15 +90,92 @@ def __init__(
9090
self.google_ads_conn_id = google_ads_conn_id
9191
self.google_ads_config: Dict[str, Any] = {}
9292

93+
def search(
94+
self, client_ids: List[str], query: str, page_size: int = 10000, **kwargs
95+
) -> List[GoogleAdsRow]:
96+
"""
97+
Pulls data from the Google Ads API and returns it as native protobuf
98+
message instances (those seen in versions prior to 10.0.0 of the
99+
google-ads library).
100+
101+
This method is for backwards compatibility with older versions of the
102+
google_ads_hook.
103+
104+
Check out the search_proto_plus method to get API results in the new
105+
default format of the google-ads library since v10.0.0 that behave
106+
more like conventional python object (using proto-plus-python).
107+
108+
:param client_ids: Google Ads client ID(s) to query the API for.
109+
:type client_ids: List[str]
110+
:param query: Google Ads Query Language query.
111+
:type query: str
112+
:param page_size: Number of results to return per page. Max 10000.
113+
:type page_size: int
114+
:return: Google Ads API response, converted to Google Ads Row objects
115+
:rtype: list[GoogleAdsRow]
116+
"""
117+
data_proto_plus = self._search(client_ids, query, page_size, **kwargs)
118+
data_native_pb = [row._pb for row in data_proto_plus]
119+
120+
return data_native_pb
121+
122+
def search_proto_plus(
123+
self, client_ids: List[str], query: str, page_size: int = 10000, **kwargs
124+
) -> List[GoogleAdsRow]:
125+
"""
126+
Pulls data from the Google Ads API and returns it as proto-plus-python
127+
message instances that behave more like conventional python objects.
128+
129+
:param client_ids: Google Ads client ID(s) to query the API for.
130+
:type client_ids: List[str]
131+
:param query: Google Ads Query Language query.
132+
:type query: str
133+
:param page_size: Number of results to return per page. Max 10000.
134+
:type page_size: int
135+
:return: Google Ads API response, converted to Google Ads Row objects
136+
:rtype: list[GoogleAdsRow]
137+
"""
138+
return self._search(client_ids, query, page_size, **kwargs)
139+
140+
def list_accessible_customers(self) -> List[str]:
141+
"""
142+
Returns resource names of customers directly accessible by the user authenticating the call.
143+
The resulting list of customers is based on your OAuth credentials. The request returns a list
144+
of all accounts that you are able to act upon directly given your current credentials. This will
145+
not necessarily include all accounts within the account hierarchy; rather, it will only include
146+
accounts where your authenticated user has been added with admin or other rights in the account.
147+
148+
..seealso::
149+
https://developers.google.com/google-ads/api/reference/rpc
150+
151+
:return: List of names of customers
152+
"""
153+
try:
154+
accessible_customers = self._get_customer_service.list_accessible_customers()
155+
return accessible_customers.resource_names
156+
except GoogleAdsException as ex:
157+
for error in ex.failure.errors:
158+
self.log.error('\tError with message "%s".', error.message)
159+
if error.location:
160+
for field_path_element in error.location.field_path_elements:
161+
self.log.error('\t\tOn field: %s', field_path_element.field_name)
162+
raise
163+
93164
@cached_property
94165
def _get_service(self) -> Resource:
95166
"""Connects and authenticates with the Google Ads API using a service account"""
167+
168+
client = self._get_client
169+
return client.get_service("GoogleAdsService", version=self.api_version)
170+
171+
@cached_property
172+
def _get_client(self) -> Resource:
96173
with NamedTemporaryFile("w", suffix=".json") as secrets_temp:
97174
self._get_config()
98175
self._update_config_with_secret(secrets_temp)
99176
try:
100177
client = GoogleAdsClient.load_from_dict(self.google_ads_config)
101-
return client.get_service("GoogleAdsService", version=self.api_version)
178+
return client
102179
except GoogleAuthError as e:
103180
self.log.error("Google Auth Error: %s", e)
104181
raise
@@ -140,7 +217,7 @@ def _update_config_with_secret(self, secrets_temp: IO[str]) -> None:
140217

141218
self.google_ads_config["path_to_private_key_file"] = secrets_temp.name
142219

143-
def search(
220+
def _search(
144221
self, client_ids: List[str], query: str, page_size: int = 10000, **kwargs
145222
) -> List[GoogleAdsRow]:
146223
"""
@@ -157,9 +234,17 @@ def search(
157234
:rtype: list[GoogleAdsRow]
158235
"""
159236
service = self._get_service
160-
iterators = (
161-
service.search(client_id, query=query, page_size=page_size, **kwargs) for client_id in client_ids
162-
)
237+
238+
iterators = []
239+
for client_id in client_ids:
240+
request = self._get_client.get_type("SearchGoogleAdsRequest")
241+
request.customer_id = client_id
242+
request.query = query
243+
request.page_size = 10000
244+
245+
iterator = service.search(request=request)
246+
iterators.append(iterator)
247+
163248
self.log.info("Fetched Google Ads Iterators")
164249

165250
return self._extract_rows(iterators)
@@ -189,27 +274,3 @@ def _extract_rows(self, iterators: Generator[GRPCIterator, None, None]) -> List[
189274
for field_path_element in error.location.field_path_elements:
190275
self.log.error("\t\tOn field: %s", field_path_element.field_name)
191276
raise
192-
193-
def list_accessible_customers(self) -> List[str]:
194-
"""
195-
Returns resource names of customers directly accessible by the user authenticating the call.
196-
The resulting list of customers is based on your OAuth credentials. The request returns a list
197-
of all accounts that you are able to act upon directly given your current credentials. This will
198-
not necessarily include all accounts within the account hierarchy; rather, it will only include
199-
accounts where your authenticated user has been added with admin or other rights in the account.
200-
201-
..seealso::
202-
https://developers.google.com/google-ads/api/reference/rpc
203-
204-
:return: List of names of customers
205-
"""
206-
try:
207-
accessible_customers = self._get_customer_service.list_accessible_customers()
208-
return accessible_customers.resource_names
209-
except GoogleAdsException as ex:
210-
for error in ex.failure.errors:
211-
self.log.error('\tError with message "%s".', error.message)
212-
if error.location:
213-
for field_path_element in error.location.field_path_elements:
214-
self.log.error('\t\tOn field: %s', field_path_element.field_name)
215-
raise

docs/apache-airflow-providers-google/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ PIP package Version required
9090
====================================== ===================
9191
``apache-airflow`` ``>=2.1.0``
9292
``PyOpenSSL``
93-
``google-ads`` ``>=4.0.0,<8.0.0``
93+
``google-ads`` ``>=12.0.0``
9494
``google-api-core`` ``>=1.25.1,<2.0.0``
9595
``google-api-python-client`` ``>=1.6.0,<2.0.0``
9696
``google-auth-httplib2`` ``>=0.0.1``

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def write_version(filename: str = os.path.join(*[my_dir, "airflow", "git_version
277277
]
278278
google = [
279279
'PyOpenSSL',
280-
'google-ads>=4.0.0,<8.0.0',
280+
'google-ads>=12.0.0',
281281
'google-api-core>=1.25.1,<2.0.0',
282282
'google-api-python-client>=1.6.0,<2.0.0',
283283
'google-auth>=1.0.0,<2.0.0',

tests/providers/google/ads/operators/test_ads.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
gcp_conn_id = "gcp_conn_id"
3939
google_ads_conn_id = "google_ads_conn_id"
40-
api_version = "v5"
40+
api_version = "v8"
4141

4242

4343
class TestGoogleAdsListAccountsOperator:

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/apache/airflow/commit/5d2224795b3548516311025d5549094a9b168f3b

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy