Skip to content

Commit 1fc4189

Browse files
committed
AdWords v201705/v201708 sunset, Zeep/google-auth migration
1 parent 76dc782 commit 1fc4189

File tree

317 files changed

+12430
-23612
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

317 files changed

+12430
-23612
lines changed

ChangeLog

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1+
11.0.0 -- 03/21/18
2+
* Removed support and examples for AdWords v201705, and v201708.
3+
* The default SOAP backend is now zeep. If you encounter any issues,
4+
please report them and use your googleads.yaml file to revert back
5+
to suds.
6+
- Zeep logs to the googleads.soap channel.
7+
- When using zeep (the default), you no longer need to encode your
8+
image assets to base64, this will be done automatically.
9+
* Sunset oauth2client, replaced with google-auth.
10+
https://google-auth.readthedocs.io/en/latest/
11+
- Updated googleads.yaml to remove service_account_email configurations that
12+
are no longer necessary.
13+
- Removed httplib2 dependency.
14+
- Added google-auth, google-auth-oauthlib, and requests as dependencies.
15+
- AdWords and DFP auth examples have been updated to use new OAuth 2.0
16+
dependencies.
17+
* Fixed a bug in IncrementalUploadHelper where the specified server would be
18+
lost in serialization/deserialization.
19+
* Breaking changes:
20+
* Connection faults are now raised as GoogleAdsSoapTransportError
21+
instances instead of suds errors.
22+
- Soap faults are now raised as GoogleAdsServerFault instances instead of suds
23+
errors.
24+
- GoogleServiceAccountClient no longer supports P12 key files.
25+
- GoogleServiceAccountClient's initializer arguments have been refactored;
26+
`key_file` is now a required positional argument, `client_email` and
27+
`private_key_password` have been removed.
28+
- Removed Proxy from common.py. When creating a ProxyConfig, you must now
29+
provide the full URI for http_proxy and https_proxy rather than a Proxy
30+
instance. For an example of what these would look like, see:
31+
http://docs.python-requests.org/en/master/user/advanced/#proxies
32+
- The proxy_config used in googleads.yaml has been updated to better resemble
33+
proxy configuration for the underlying HTTP library. The http_proxy and
34+
https_proxy configurations have been removed, and replaced with http and
35+
https. These take the full proxy URI as their value.
36+
- Removed proxy_info attribute from ProxyConfig.
37+
- Refactored proxy_option attribute in ProxyConfig, renamed to proxies.
38+
* Resolved issues:
39+
- Issue 251: https://github.com/googleads/googleads-python-lib/issues/251
40+
- Issue 254: https://github.com/googleads/googleads-python-lib/issues/254
41+
142
10.1.1 -- 03/16/18
243
* Accepted PR #222, to improve AdWords App Engine sample.
344

README.md

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,14 @@ configuration via the configuration file; see [googleads.yaml](https://github.co
106106
for an example.
107107

108108
Alternatively, you can manually specify your logging configuration. For example,
109-
if you want to log your SOAP interactions to stdout, you can do the following:
109+
if you want to log your SOAP interactions to stdout, and you are using the Zeep soap library, you
110+
can do the following:
111+
```python
112+
logging.basicConfig(level=logging.INFO, format=googleads.util.LOGGER_FORMAT)
113+
logging.getLogger('googleads.soap').setLevel(logging.DEBUG)
114+
```
115+
116+
If you are using suds, you can achieve the same like this:
110117
```python
111118
logging.basicConfig(level=logging.INFO, format=googleads.util.LOGGER_FORMAT)
112119
logging.getLogger('suds.transport').setLevel(logging.DEBUG)
@@ -116,7 +123,22 @@ which is configured to write the output to a file.
116123

117124

118125
## How do I disable log filters?
119-
By default, this library will apply log filters to the `googleads.common`,
126+
The zeep plugin used for logging strips sensitive data from its output. If you would like this data
127+
included in logs, you'll need to implement your own simple logging plugin. For example:
128+
```python
129+
class DangerousZeepLogger(zeep.Plugin):
130+
def ingress(self, envelope, http_headers, operation):
131+
logging.debug('Incoming response: \n%s', etree.tostring(envelope, pretty_print=True))
132+
return envelope, http_headers
133+
134+
def egress(self, envelope, http_headers, operation, binding_options):
135+
logging.debug('Incoming response: \n%s', etree.tostring(envelope, pretty_print=True))
136+
return envelope, http_headers
137+
138+
adwords_client.zeep_client.plugins.append(DangerousZeepLogger())
139+
```
140+
141+
When using suds, this library will apply log filters to the `googleads.common`,
120142
`suds.client`, and `suds.transport` loggers in order to omit sensitive data. If
121143
you need to see this data in your logs, you can disable the filters with the
122144
following:
@@ -134,10 +156,10 @@ logging.getLogger('suds.transport').removeFilter(
134156
```
135157

136158

137-
## I'm familiar with suds. Can I use suds features with this library?
159+
## I'm familiar with suds/zeep. Can I use those features in the library?
138160
Yes, you can. The services returned by the `client.GetService()` functions all
139-
have a reference to the underlying suds client stored in the `suds_client`
140-
attribute. You can retrieve the client and use it in familiar ways:
161+
have a reference to the underlying client stored in the `zeep_client` or `suds_client`
162+
attributes. You can retrieve the client and use it in familiar ways:
141163
```python
142164
client = AdWordsClient.LoadFromStorage()
143165
campaign_service = client.GetService('CampaignService')
@@ -170,25 +192,40 @@ suds_client.set_options(
170192
suds_client.service.mutate([operation])
171193
```
172194

173-
## How can I configure or disable caching for the suds client?
195+
## How can I configure or disable caching?
174196

175-
By default, the suds clients are cached because reading and digesting the WSDL
197+
By default, clients are cached because reading and digesting the WSDL
176198
can be expensive. However, the default caching method requires permission to
177199
access a local file system that may not be available in certain hosting
178200
environments such as App Engine.
179201

180-
You can pass an implementation of `suds.cache.Cache` to the `AdWordsClient` or
202+
You can pass an implementation of `suds.cache.Cache` or `zeep.cache.Base` to the `AdWordsClient` or
181203
`DfpClient` initializer to modify the default caching behavior.
182204

183-
For example, configuring a different location and duration of the cache file:
205+
For example, configuring a different location and duration of the cache file with zeep
206+
```python
207+
doc_cache = zeep.cache.SqliteCache(path=cache_path)
208+
adwords_client = adwords.AdWordsClient(
209+
developer_token, oauth2_client, user_agent,
210+
client_customer_id=client_customer_id, cache=doc_cache)
211+
```
212+
213+
And with suds:
184214
```python
185215
doc_cache = suds.cache.DocumentCache(location=cache_path, days=2)
186216
adwords_client = adwords.AdWordsClient(
187217
developer_token, oauth2_client, user_agent,
188218
client_customer_id=client_customer_id, cache=doc_cache)
189219
```
190220

191-
You can also disable caching in similar fashion:
221+
You can also disable caching in similar fashion with zeep
222+
```python
223+
adwords_client = adwords.AdWordsClient(
224+
developer_token, oauth2_client, user_agent,
225+
client_customer_id=client_customer_id, cache=None)
226+
```
227+
228+
And with suds:
192229
```python
193230
adwords_client = adwords.AdWordsClient(
194231
developer_token, oauth2_client, user_agent,
@@ -212,6 +249,7 @@ have Python 2.7.9 (or higher) or Python 3.4 (or higher) installed.
212249
- pytz -- https://pypi.python.org/pypi/pytz
213250
- pyYAML -- https://pypi.python.org/pypi/pyYAML/
214251
- xmltodict -- https://pypi.python.org/pypi/xmltodict/
252+
- zeep -- https://pypi.python.org/pypi/zeep
215253
- mock -- https://pypi.python.org/pypi/mock
216254
(only needed to run unit tests)
217255
- pyfakefs -- https://pypi.python.org/pypi/pyfakefs

examples/adwords/adwords_appengine_demo/appengine_config.py

100644100755
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#!/usr/bin/env python
2+
#
13
# Copyright 2018 Google Inc. All Rights Reserved.
24
#
35
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +20,6 @@
1820
import os
1921
from google.appengine.ext import vendor
2022

21-
# Add any libraries install in the "lib" folder.
23+
# Add any libraries installed in the "lib" folder to the path.
2224
vendor.add(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib'))
2325

examples/adwords/authentication/create_adwords_client_with_service_account.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
# OAuth2 credential information. In a real application, you'd probably be
2424
# pulling these values from a credential storage.
25-
SERVICE_ACCOUNT_EMAIL = 'INSERT_SERVICE_ACCOUNT_EMAIL_HERE'
2625
KEY_FILE = 'INSERT_PATH_TO_KEY_FILE'
2726
SERVICE_ACCOUNT_USER = 'INSERT_IMPERSONATED_EMAIL_HERE'
2827

@@ -32,11 +31,10 @@
3231
CLIENT_CUSTOMER_ID = 'INSERT_CLIENT_CUSTOMER_ID_HERE'
3332

3433

35-
def main(service_account_email, key_file, service_account_user,
36-
developer_token, user_agent, client_customer_id):
34+
def main(key_file, service_account_user, developer_token, user_agent,
35+
client_customer_id):
3736
oauth2_client = oauth2.GoogleServiceAccountClient(
38-
oauth2.GetAPIScope('adwords'), service_account_email, key_file,
39-
sub=service_account_user)
37+
key_file, oauth2.GetAPIScope('adwords'), sub=service_account_user)
4038

4139
adwords_client = adwords.AdWordsClient(
4240
developer_token, oauth2_client, user_agent,
@@ -53,5 +51,5 @@ def main(service_account_email, key_file, service_account_user,
5351

5452

5553
if __name__ == '__main__':
56-
main(SERVICE_ACCOUNT_EMAIL, KEY_FILE, SERVICE_ACCOUNT_USER,
57-
DEVELOPER_TOKEN, USER_AGENT, CLIENT_CUSTOMER_ID)
54+
main(KEY_FILE, SERVICE_ACCOUNT_USER, DEVELOPER_TOKEN, USER_AGENT,
55+
CLIENT_CUSTOMER_ID)

examples/adwords/authentication/generate_refresh_token.py

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
import argparse
2121
import sys
2222

23-
from oauth2client import client
23+
from google_auth_oauthlib.flow import InstalledAppFlow
24+
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
2425

2526
# Your OAuth2 Client ID and Secret. If you do not have an ID and Secret yet,
2627
# please go to https://console.developers.google.com and create a set.
@@ -29,6 +30,9 @@
2930

3031
# The AdWords API OAuth2 scope.
3132
SCOPE = u'https://www.googleapis.com/auth/adwords'
33+
# The redirect URI set for the given Client ID. The redirect URI for Client ID
34+
# generated for an installed application will always have this value.
35+
_REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
3236

3337
parser = argparse.ArgumentParser(description='Generates a refresh token with '
3438
'the provided credentials.')
@@ -42,31 +46,68 @@
4246
'refresh token. Each scope should be separated by a comma.')
4347

4448

49+
class ClientConfigBuilder(object):
50+
"""Helper class used to build a client config dict used in the OAuth 2.0 flow.
51+
"""
52+
_DEFAULT_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
53+
_DEFAULT_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
54+
CLIENT_TYPE_WEB = 'web'
55+
CLIENT_TYPE_INSTALLED_APP = 'installed'
56+
57+
def __init__(self, client_type=None, client_id=None, client_secret=None,
58+
auth_uri=_DEFAULT_AUTH_URI, token_uri=_DEFAULT_TOKEN_URI):
59+
self.client_type = client_type
60+
self.client_id = client_id
61+
self.client_secret = client_secret
62+
self.auth_uri = auth_uri
63+
self.token_uri = token_uri
64+
65+
def Build(self):
66+
"""Builds a client config dictionary used in the OAuth 2.0 flow."""
67+
if all((self.client_type, self.client_id, self.client_secret,
68+
self.auth_uri, self.token_uri)):
69+
client_config = {
70+
self.client_type: {
71+
'client_id': self.client_id,
72+
'client_secret': self.client_secret,
73+
'auth_uri': self.auth_uri,
74+
'token_uri': self.token_uri
75+
}
76+
}
77+
else:
78+
raise ValueError('Required field is missing.')
79+
80+
return client_config
81+
82+
4583
def main(client_id, client_secret, scopes):
4684
"""Retrieve and display the access and refresh token."""
47-
flow = client.OAuth2WebServerFlow(
48-
client_id=client_id,
49-
client_secret=client_secret,
50-
scope=scopes,
51-
user_agent='Ads Python Client Library',
52-
redirect_uri='urn:ietf:wg:oauth:2.0:oob')
85+
client_config = ClientConfigBuilder(
86+
client_type=ClientConfigBuilder.CLIENT_TYPE_WEB, client_id=client_id,
87+
client_secret=client_secret)
88+
89+
flow = InstalledAppFlow.from_client_config(
90+
client_config.Build(), scopes=scopes)
91+
# Note that from_client_config will not produce a flow with the
92+
# redirect_uris (if any) set in the client_config. This must be set
93+
# separately.
94+
flow.redirect_uri = _REDIRECT_URI
5395

54-
authorize_url = flow.step1_get_authorize_url()
96+
auth_url, _ = flow.authorization_url(prompt='consent')
5597

56-
print ('Log into the Google Account you use to access your AdWords account'
57-
'and go to the following URL: \n%s\n' % (authorize_url))
98+
print ('Log into the Google Account you use to access your AdWords account '
99+
'and go to the following URL: \n%s\n' % auth_url)
58100
print 'After approving the token enter the verification code (if specified).'
59101
code = raw_input('Code: ').strip()
60102

61103
try:
62-
credential = flow.step2_exchange(code)
63-
except client.FlowExchangeError, e:
64-
print 'Authentication has failed: %s' % e
104+
flow.fetch_token(code=code)
105+
except InvalidGrantError as ex:
106+
print 'Authentication has failed: %s' % ex
65107
sys.exit(1)
66-
else:
67-
print ('OAuth2 authorization successful!\n\n'
68-
'Your access token is:\n %s\n\nYour refresh token is:\n %s'
69-
% (credential.access_token, credential.refresh_token))
108+
109+
print 'Access token: %s' % flow.credentials.token
110+
print 'Refresh token: %s' % flow.credentials.refresh_token
70111

71112

72113
if __name__ == '__main__':

examples/adwords/v201705/__init__.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

examples/adwords/v201705/account_management/__init__.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

examples/adwords/v201705/account_management/accept_service_link.py

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)