Skip to content

Commit e7357cf

Browse files
committed
Commiting latest changes.
1 parent 1d48d00 commit e7357cf

File tree

6 files changed

+116
-41
lines changed

6 files changed

+116
-41
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
5.4.0 -- 4/25/17:
2+
* Resolved issues:
3+
- Issue 200: https://github.com/googleads/googleads-python-lib/issues/200
4+
- Issue 196: https://github.com/googleads/googleads-python-lib/issues/196
5+
16
5.3.0 -- 3/20/17:
27
* Removed support for AdWords v201605.
38
* Removed examples for AdWords v201605.

README.md

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
#The googleads Python client library
1+
# The googleads Python client library
22

33

44
This client library simplifies accessing Google's SOAP Ads APIs - AdWords,
55
and DoubleClick for Publishers. The library provides easy ways to store your
66
authentication and create SOAP web service clients. It also contains example
77
code to help you get started integrating with our APIs.
88

9-
##Getting started
9+
## Getting started
1010
1. Download and install the library
1111

1212
*[setuptools](https://pypi.python.org/pypi/setuptools) is a pre-requisite
@@ -46,7 +46,7 @@ code to help you get started integrating with our APIs.
4646

4747
* [Developing a web application (AdWords or DFP)](https://github.com/googleads/googleads-python-lib/wiki/API-access-on-behalf-of-your-clients-(web-flow))
4848

49-
####Where can I find samples?
49+
#### Where can I find samples?
5050

5151
You can find code examples for the latest versions of AdWords or DFP on the
5252
[releases](https://github.com/googleads/googleads-python-lib/releases) page.
@@ -55,11 +55,11 @@ Alternatively, you can find [AdWords](https://github.com/googleads/googleads-pyt
5555
or [DFP](https://github.com/googleads/googleads-python-lib/tree/master/examples/dfp)
5656
samples in the examples directory of this repository.
5757

58-
####Where can I find the pydocs?
58+
#### Where can I find the pydocs?
5959

6060
Our pydocs can be found [here](http://googleads.github.io/googleads-python-lib).
6161

62-
####Caching authentication information
62+
#### Caching authentication information
6363

6464
It is possible to cache your API authentication information. The library
6565
includes a sample file showing how to do this named `googleads.yaml`. Fill
@@ -76,7 +76,7 @@ adwords_client = adwords.AdWordsClient.LoadFromStorage()
7676
dfp_client = dfp.DfpClient.LoadFromStorage('C:\My\Directory\googleads.yaml')
7777
```
7878

79-
####How do I change the Client Customer Id at runtime?
79+
#### How do I change the Client Customer Id at runtime?
8080
You can change the Client Customer Id with the following:
8181

8282
```
@@ -85,7 +85,7 @@ adwords_client.SetClientCustomerId('my_client_customer_id')
8585
```
8686

8787

88-
##Where do I submit bug reports and/or feature requests?
88+
## Where do I submit bug reports and/or feature requests?
8989

9090
If you have issues directly related to the client library, use the [issue
9191
tracker](https://github.com/googleads/googleads-python-lib/issues).
@@ -100,7 +100,7 @@ Make sure to subscribe to our [Google Plus page](https://plus.google.com/+Google
100100
for API change announcements and other news.
101101

102102

103-
##How do I log SOAP interactions?
103+
## How do I log SOAP interactions?
104104
The library uses Python's built in logging framework. If you wish to log your
105105
SOAP interactions to stdout, you can do the following:
106106
```python
@@ -111,7 +111,7 @@ If you wish to log to a file, you'll need to attach a log handler to this source
111111
which is configured to write the output to a file.
112112

113113

114-
##How do I disable log filters?
114+
## How do I disable log filters?
115115
By default, this library will apply log filters to the `googleads.common`,
116116
`suds.client`, and `suds.transport` loggers in order to omit sensitive data. If
117117
you need to see this data in your logs, you can disable the filters with the
@@ -126,7 +126,7 @@ logging.getLogger('suds.transport').removeFilter(
126126
```
127127

128128

129-
##I'm familiar with suds. Can I use suds features with this library?
129+
## I'm familiar with suds. Can I use suds features with this library?
130130
Yes, you can. The services returned by the `client.GetService()` functions all
131131
have a reference to the underlying suds client stored in the `suds_client`
132132
attribute. You can retrieve the client and use it in familiar ways:
@@ -162,7 +162,7 @@ suds_client.set_options(
162162
suds_client.service.mutate([operation])
163163
```
164164

165-
##How can I configure or disable caching for the suds client?
165+
## How can I configure or disable caching for the suds client?
166166

167167
By default, the suds clients are cached because reading and digesting the WSDL
168168
can be expensive. However, the default caching method requires permission to
@@ -187,26 +187,15 @@ adwords_client = adwords.AdWordsClient(
187187
client_customer_id=client_customer_id, cache=suds.cache.NoCache())
188188
```
189189

190-
##Timeout Tips
191-
The requests sent by this library are sent via urllib, which is consequently
192-
where the timeout is set. If you set a system timeout elsewhere, the googleads
193-
library will respect it.
194190

195-
You can do the following if you wish to override the timeout:
191+
## Requirements
196192

197-
```python
198-
import socket
199-
socket.setdefaulttimeout(15 * 60)
200-
```
201-
202-
##Requirements
203-
204-
###Python Versions
193+
### Python Versions
205194

206195
This library supports both Python 2 and 3. To use this library, you will need to
207196
have Python 2.7.9 (or higher) or Python 3.4 (or higher) installed.
208197

209-
###External Dependencies:
198+
### External Dependencies:
210199

211200
- httplib2 -- https://pypi.python.org/pypi/httplib2/
212201
- oauth2client -- https://pypi.python.org/pypi/oauth2client/
@@ -221,5 +210,5 @@ have Python 2.7.9 (or higher) or Python 3.4 (or higher) installed.
221210
(only needed to run unit tests)
222211

223212

224-
##Authors:
213+
## Authors:
225214
Mark Saniscalchi

googleads.yaml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,22 @@ dfp:
4949
#############################################################################
5050
# The network_code is required for all services except NetworkService:
5151
# network_code: INSERT_NETWORK_CODE_HERE
52+
# delegated_account: INSERT_DOMAIN_WIDE_DELEGATION_ACCOUNT
5253
#############################################################################
5354
# OAuth2 Configuration #
5455
# Below you may provide credentials for either the installed application or #
55-
# service account flows. Remove or comment the lines for the flow you're #
56-
# not using. #
56+
# service account (recommended) flows. Remove or comment the lines for the #
57+
# flow you're not using. #
5758
#############################################################################
58-
# The following values configure the client for the installed application
59-
# flow.
60-
client_id: INSERT_OAUTH_2_CLIENT_ID_HERE
61-
client_secret: INSERT_CLIENT_SECRET_HERE
62-
refresh_token: INSERT_REFRESH_TOKEN_HERE
6359
# The following values configure the client for the service account flow.
60+
path_to_private_key_file: INSERT_PATH_TO_FILE_HERE
61+
# Only needed when using P12
6462
# service_account_email: INSERT_SERVICE_ACCOUNT_EMAIL_HERE
65-
# path_to_private_key_file: INSERT_PATH_TO_FILE_HERE
66-
# delegated_account: INSERT_DOMAIN_WIDE_DELEGATION_ACCOUNT
63+
# The following values configure the client for the installed application
64+
# flow.
65+
# client_id: INSERT_OAUTH_2_CLIENT_ID_HERE
66+
# client_secret: INSERT_CLIENT_SECRET_HERE
67+
# refresh_token: INSERT_REFRESH_TOKEN_HERE
6768

6869

6970
# Common configurations:

googleads/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
'compatibility with this library, upgrade to Python 2.7.9 or higher.')
6161

6262

63-
VERSION = '5.3.0'
63+
VERSION = '5.4.0'
6464
_COMMON_LIB_SIG = 'googleads/%s' % VERSION
6565
_HTTP_PROXY_YAML_KEY = 'http_proxy'
6666
_HTTPS_PROXY_YAML_KEY = 'https_proxy'
@@ -649,6 +649,7 @@ def __init__(self, handlers, **kwargs):
649649
handlers: an iterable of urllib2.BaseHandler subclasses.
650650
**kwargs: Keyword arguments.
651651
"""
652+
kwargs['timeout'] = 3600
652653
suds.transport.http.HttpTransport.__init__(self, **kwargs)
653654
self.handlers = handlers
654655

googleads/util.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ def _ApplySudsJurkoSendPatch(self):
8585
enabled. For more details on SOAP Compression, see:
8686
https://developers.google.com/adwords/api/docs/guides/bestpractices?hl=en#use_compression
8787
"""
88+
def GetInflateStream(msg):
89+
stream = io.BytesIO()
90+
stream.write(msg)
91+
stream.flush()
92+
stream.seek(0)
93+
return gzip.GzipFile(fileobj=stream, mode='rb')
94+
8895
def PatchedHttpTransportSend(self, request):
8996
"""Patch for HttpTransport.send to enable gzip compression."""
9097
msg = request.message
@@ -102,6 +109,12 @@ def PatchedHttpTransportSend(self, request):
102109
if e.code in (202, 204):
103110
return None
104111
else:
112+
if e.headers.get('content-encoding') == 'gzip':
113+
# If gzip encoding is used, decompress here.
114+
# Need to read and recreate a stream because urllib result objects
115+
# don't fully implement the file-like API
116+
e.fp = GetInflateStream(e.fp.read())
117+
105118
raise suds.transport.TransportError(e.msg, e.code, e.fp)
106119

107120
self.getcookies(fp, u2request)
@@ -110,11 +123,7 @@ def PatchedHttpTransportSend(self, request):
110123

111124
if result.headers.get('content-encoding') == 'gzip':
112125
# If gzip encoding is used, decompress here.
113-
stream = io.BytesIO()
114-
stream.write(result.message)
115-
stream.flush()
116-
stream.seek(0)
117-
result.message = gzip.GzipFile(fileobj=stream, mode='rb').read()
126+
result.message = GetInflateStream(result.message).read()
118127

119128
suds.transport.http.log.debug('received:\n%s', result)
120129
return result

tests/common_test.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"""Unit tests to cover the common module."""
1616

1717

18+
import io
1819
import unittest
20+
from urllib import addinfourl
1921
import urllib2
2022
import warnings
2123

@@ -339,6 +341,18 @@ def testLoadFromString_passesWithHTTPAndHTTPSProxy(self):
339341
'proxy_config': proxy_config.return_value,
340342
googleads.common.ENABLE_COMPRESSION_KEY: False}, rval)
341343

344+
def testHTTPSProxyExtendsSSLSockTimeout(self):
345+
with mock.patch('googleads.oauth2.GoogleRefreshTokenClient'):
346+
with mock.patch('googleads.common.open', self.fake_open, create=True):
347+
with mock.patch('suds.transport.http.'
348+
'HttpTransport.__init__') as mock_transport:
349+
proxy_config = googleads.common.ProxyConfig()
350+
proxy_config.GetSudsProxyTransport()
351+
mock_transport.assert_called_once()
352+
# 90 is the default, don't expect an exact value so that we can change
353+
# the timeout in the code without returning here.
354+
self.assertGreater(mock_transport.call_args_list[0][1]['timeout'], 90)
355+
342356
def testLoadFromString_passesWithHTTPProxy(self):
343357
yaml_doc = self._CreateYamlDoc(
344358
{'adwords': {},
@@ -985,5 +999,61 @@ def testProxyConfigWithHTTPAndHTTPSProxy(self):
985999
self.assertEqual(t.return_value, transport)
9861000

9871001

1002+
class TestSudsRequestPatcher(unittest.TestCase):
1003+
1004+
class MockHeaders(object):
1005+
1006+
def __init__(self, d):
1007+
self.dict = d
1008+
1009+
def testInflateSuccessfulRequestIfGzipped(self):
1010+
with mock.patch('suds.transport.http.HttpTransport.getcookies'):
1011+
with mock.patch('urllib2.OpenerDirector.open') as mock_open:
1012+
with mock.patch('gzip.GzipFile.read') as mock_read:
1013+
resp_fp = io.BytesIO()
1014+
resp_fp.write('abc')
1015+
resp_fp.flush()
1016+
resp_fp.seek(0)
1017+
resp = addinfourl(resp_fp, self.MockHeaders(
1018+
{'content-encoding': 'gzip'}), 'https://example.com', code=200)
1019+
mock_open.return_value = resp
1020+
1021+
req = suds.transport.Request('https://example.com',
1022+
message='hello world')
1023+
suds.transport.http.HttpTransport().send(req)
1024+
mock_read.assert_called_once()
1025+
1026+
def testInflateFailedRequestIfGzipped(self):
1027+
1028+
with mock.patch('suds.transport.http.HttpTransport.getcookies'):
1029+
with mock.patch('urllib2.OpenerDirector.open') as mock_open:
1030+
with mock.patch('gzip.GzipFile') as mock_gzip:
1031+
gzip_instance_mock = mock.MagicMock
1032+
mock_gzip.return_value = gzip_instance_mock
1033+
resp_fp = io.BytesIO()
1034+
resp_fp.write('abc')
1035+
resp_fp.flush()
1036+
resp_fp.seek(0)
1037+
resp = addinfourl(resp_fp,
1038+
self.MockHeaders({'content-encoding': 'gzip'}),
1039+
'https://example.com', code=200)
1040+
1041+
def fail_to_open(*args, **kwargs):
1042+
raise urllib2.HTTPError(
1043+
'https://example.com',
1044+
500,
1045+
'oops!',
1046+
{'content-encoding': 'gzip'},
1047+
resp
1048+
)
1049+
mock_open.side_effect = fail_to_open
1050+
1051+
req = suds.transport.Request('https://example.com',
1052+
message='hello world')
1053+
with self.assertRaises(suds.transport.TransportError) as exc:
1054+
suds.transport.http.HttpTransport().send(req)
1055+
self.assertEqual(exc.exception.fp, gzip_instance_mock)
1056+
1057+
9881058
if __name__ == '__main__':
9891059
unittest.main()

0 commit comments

Comments
 (0)