Skip to content

Commit afbfda7

Browse files
authored
Move conversion of login_customer_id to a str to config module (googleads#136)
1 parent e71ec74 commit afbfda7

7 files changed

Lines changed: 113 additions & 7 deletions

File tree

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
* 2.4.1:
2+
- Fix bug preventing login_customer_id to be loaded as an int
3+
14
* 2.4.0:
25
- Add utf-8 encoding declaration in generated proto files
36
- Add Service Account support

google/ads/google_ads/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121
import google.ads.google_ads.util
2222

2323

24-
VERSION = '2.4.0'
24+
VERSION = '2.4.1'

google/ads/google_ads/client.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,10 @@ def _get_client_kwargs(cls, config_data):
5555
Raises:
5656
ValueError: If the configuration lacks a required field.
5757
"""
58-
login_customer_id = config_data.get('login_customer_id')
59-
login_customer_id = str(
60-
login_customer_id) if login_customer_id else None
61-
6258
return {'credentials': oauth2.get_credentials(config_data),
6359
'developer_token': config_data.get('developer_token'),
6460
'endpoint': config_data.get('endpoint'),
65-
'login_customer_id': login_customer_id,
61+
'login_customer_id': config_data.get('login_customer_id'),
6662
'logging_config': config_data.get('logging')}
6763

6864
@classmethod

google/ads/google_ads/config.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ def validation_wrapper(*args, **kwargs):
4747
return validation_wrapper
4848

4949

50+
def _config_parser_decorator(func):
51+
"""A decorator used to easily parse config values.
52+
53+
Since configs can be loaded from different locations such as env vars or
54+
from YAML files it's possible that they may have inconsistent types that
55+
need to be parsed to a different type. Add this decorator to any method
56+
that returns the config as a dict.
57+
"""
58+
@functools.wraps(func)
59+
def parser_wrapper(*args, **kwargs):
60+
config_dict = func(*args, **kwargs)
61+
parsed_config = convert_login_customer_id_to_str(config_dict)
62+
return parsed_config
63+
return parser_wrapper
64+
65+
5066
def validate_dict(config_data):
5167
"""Validates the given configuration dict.
5268
@@ -87,6 +103,7 @@ def validate_login_customer_id(login_customer_id):
87103

88104

89105
@_config_validation_decorator
106+
@_config_parser_decorator
90107
def load_from_yaml_file(path=None):
91108
"""Loads configuration data from a YAML file and returns it as a dict.
92109
@@ -114,6 +131,7 @@ def load_from_yaml_file(path=None):
114131

115132

116133
@_config_validation_decorator
134+
@_config_parser_decorator
117135
def parse_yaml_document_to_dict(yaml_doc):
118136
"""Parses a YAML document to a dict.
119137
@@ -131,6 +149,7 @@ def parse_yaml_document_to_dict(yaml_doc):
131149

132150

133151
@_config_validation_decorator
152+
@_config_parser_decorator
134153
def load_from_env():
135154
"""Loads configuration data from the environment and returns it as a dict.
136155
@@ -171,3 +190,24 @@ def get_oauth2_service_account_keys():
171190
A tuple containing the required keys as strs.
172191
"""
173192
return _OAUTH2_SERVICE_ACCOUNT_KEYS
193+
194+
195+
def convert_login_customer_id_to_str(config_data):
196+
"""Parses a config dict's login_customer_id attr value to a str.
197+
198+
Like many values from YAML it's possible for login_customer_id to
199+
either be a str or an int. Since we actually run validations on this
200+
value before making requests it's important to parse it to a str.
201+
202+
Args:
203+
config_data: A config dict object.
204+
205+
Returns:
206+
The same config dict object with a mutated login_customer_id attr.
207+
"""
208+
login_customer_id = config_data.get('login_customer_id')
209+
210+
if login_customer_id:
211+
config_data['login_customer_id'] = str(login_customer_id)
212+
213+
return config_data

setup.py

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

3636
setup(
3737
name='google-ads',
38-
version='2.4.0',
38+
version='2.4.1',
3939
author='Google LLC',
4040
author_email='[email protected]',
4141
classifiers=[

tests/client_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,40 @@ def test_load_from_storage(self):
232232
login_customer_id=None,
233233
logging_config=None)
234234

235+
def test_load_from_storage_login_cid_int(self):
236+
login_cid = 1234567890
237+
config = {
238+
'developer_token': self.developer_token,
239+
'client_id': self.client_id,
240+
'client_secret': self.client_secret,
241+
'refresh_token': self.refresh_token,
242+
'login_customer_id': login_cid}
243+
244+
file_path = os.path.join(os.path.expanduser('~'), 'google-ads.yaml')
245+
self.fs.create_file(file_path, contents=yaml.safe_dump(config))
246+
mock_credentials_instance = mock.Mock()
247+
248+
with mock.patch.object(
249+
Client.GoogleAdsClient,
250+
'__init__',
251+
return_value=None
252+
) as mock_client_init, mock.patch.object(
253+
Client.oauth2,
254+
'get_installed_app_credentials',
255+
return_value=mock_credentials_instance
256+
) as mock_credentials:
257+
Client.GoogleAdsClient.load_from_storage()
258+
mock_credentials.assert_called_once_with(
259+
config.get('client_id'),
260+
config.get('client_secret'),
261+
config.get('refresh_token'))
262+
mock_client_init.assert_called_once_with(
263+
credentials=mock_credentials_instance,
264+
developer_token=self.developer_token,
265+
endpoint=None,
266+
login_customer_id=str(login_cid),
267+
logging_config=None)
268+
235269
def test_load_from_storage_custom_path(self):
236270
config = {
237271
'developer_token': self.developer_token,

tests/config_test.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@ def test_load_from_yaml_file_with_path(self):
7575
self.assertEqual(result['client_secret'], self.client_secret)
7676
self.assertEqual(result['refresh_token'], self.refresh_token)
7777

78+
def test_load_from_yaml_file_login_cid_int(self):
79+
login_cid_int = 1234567890
80+
file_path = os.path.join(os.path.expanduser('~'), 'google-ads.yaml')
81+
self.fs.create_file(file_path, contents=yaml.safe_dump({
82+
'login_customer_id': login_cid_int,
83+
'developer_token': self.developer_token,
84+
'client_id': self.client_id,
85+
'client_secret': self.client_secret,
86+
'refresh_token': self.refresh_token}))
87+
88+
result = config.load_from_yaml_file()
89+
90+
self.assertEqual(result['developer_token'], self.developer_token)
91+
self.assertEqual(result['client_id'], self.client_id)
92+
self.assertEqual(result['client_secret'], self.client_secret)
93+
self.assertEqual(result['refresh_token'], self.refresh_token)
94+
7895
def test_parse_yaml_document_to_dict(self):
7996
yaml_doc = ('client_id: {}\n'
8097
'client_secret: {}\n'
@@ -199,3 +216,19 @@ def test_get_oauth2_installed_app_keys(self):
199216
def test_get_oauth2_service_account_keys(self):
200217
self.assertEqual(config.get_oauth2_service_account_keys(),
201218
config._OAUTH2_SERVICE_ACCOUNT_KEYS)
219+
220+
def test_convert_login_customer_id_to_str_with_int(self):
221+
config_data = {'login_customer_id': 1234567890}
222+
expected = {'login_customer_id': '1234567890'}
223+
self.assertEqual(config.convert_login_customer_id_to_str(config_data),
224+
expected)
225+
226+
def test_parse_login_customer_id_with_str(self):
227+
config_data = {'login_customer_id': '1234567890'}
228+
self.assertEqual(config.convert_login_customer_id_to_str(config_data),
229+
config_data)
230+
231+
def test_parse_login_customer_id_with_none(self):
232+
config_data = {'not_login_customer_id': 1234567890}
233+
self.assertEqual(config.convert_login_customer_id_to_str(config_data),
234+
config_data)

0 commit comments

Comments
 (0)