Skip to content

Commit 01e7d13

Browse files
authored
Changes for release v2_2 with performance improvements (#194)
1 parent 2e9c88a commit 01e7d13

File tree

15 files changed

+4374
-3678
lines changed

15 files changed

+4374
-3678
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.idea
22
*.pyc
33
*.iml
4+
.python-version
45

56
# Distribution / packaging
67
.Python

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
* 4.1.0:
2+
- Performance improvements
3+
- Updated dependencies for more recent versions
4+
- Bumped Python version to 3.7+
5+
16
* 4.0.0:
27
- Removing support for Python 2
38

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Features
1515

1616
Requirements
1717
------------
18-
* Python 3.6.7+
18+
* Python 3.7+
1919
- **NOTE:** Python 2 support has ceased as of v4.0. See this `blog post`_ for more detail.
2020
* `pip`_
2121

google/ads/google_ads/__init__.py

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

2222

23-
VERSION = '3.3.0'
23+
VERSION = '4.1.0'

google/ads/google_ads/client.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313
# limitations under the License.
1414
"""A client and common configurations for the Google Ads API."""
1515

16-
from grpc import intercept_channel
16+
import grpc
1717
from importlib import import_module
1818
import logging.config
1919

20-
from google.ads.google_ads import config
21-
from google.ads.google_ads import oauth2
20+
from google.ads.google_ads import config, oauth2, util
2221
from google.ads.google_ads.interceptors import MetadataInterceptor, \
2322
ExceptionInterceptor, LoggingInterceptor
2423

@@ -35,6 +34,15 @@
3534
('grpc.max_metadata_size', 16 * 1024 * 1024),
3635
('grpc.max_receive_message_length', 64 * 1024 * 1024)]
3736

37+
38+
unary_stream_single_threading_option = util.get_nested_attr(
39+
grpc, 'experimental.ChannelOptions.SingleThreadedUnaryStream', None)
40+
41+
if unary_stream_single_threading_option:
42+
_GRPC_CHANNEL_OPTIONS.append(
43+
(unary_stream_single_threading_option, 1))
44+
45+
3846
class GoogleAdsClient(object):
3947
"""Google Ads client used to configure settings and fetch services."""
4048

@@ -69,7 +77,7 @@ def _get_api_services_by_version(cls, version):
6977
A module containing all services and types for the a API version.
7078
"""
7179
try:
72-
version_module = import_module('google.ads.google_ads.%s' % version)
80+
version_module = import_module(f'google.ads.google_ads.{version}')
7381
except ImportError:
7482
raise ValueError('Specified Google Ads API version "{}" does not '
7583
'exist. Valid API versions are: "{}"'.format(
@@ -167,12 +175,16 @@ def get_type(cls, name, version=_DEFAULT_VERSION):
167175
AttributeError: If the type for the specified name doesn't exist
168176
in the given version.
169177
"""
178+
if name.lower().endswith('pb2'):
179+
raise ValueError(f'Specified type "{name}" must be a class,'
180+
f' not a module')
181+
170182
try:
171183
type_classes = cls._get_api_services_by_version(version).types
172184
message_class = getattr(type_classes, name)
173185
except AttributeError:
174-
raise ValueError('Specified type "{}" does not exist in Google Ads '
175-
'API %s.'.format(name, version))
186+
raise ValueError(f'Specified type "{name}" does not exist in Google Ads '
187+
f'API {version}')
176188
return message_class()
177189

178190
def __init__(self, credentials, developer_token, endpoint=None,
@@ -242,10 +254,10 @@ def get_service(self, name, version=_DEFAULT_VERSION, interceptors=[]):
242254
LoggingInterceptor(_logger, endpoint),
243255
ExceptionInterceptor(version)]
244256

245-
channel = intercept_channel(
257+
channel = grpc.intercept_channel(
246258
channel,
247259
*interceptors)
248260

249261
service_transport = service_transport_class(channel=channel)
250262

251-
return service_client(transport=service_transport)
263+
return service_client(transport=service_transport)

google/ads/google_ads/util.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
# limitations under the License.
1414
"""Common utilities for the Google Ads API client library."""
1515

16+
import functools
17+
import re
18+
19+
# This regex matches characters preceded by start of line or an underscore.
20+
_RE_FIND_CHARS_TO_UPPERCASE = re.compile(r'(?:_|^)([a-z])')
21+
1622

1723
class ResourceName:
1824

@@ -41,3 +47,59 @@ def format_composite(cls, *arg):
4147
"""
4248
return cls._COMPOSITE_DELIMITER.join(arg)
4349

50+
51+
def get_nested_attr(obj, attr, *args):
52+
"""Gets the value of a nested attribute from an object.
53+
54+
Args:
55+
obj: an object to retrieve an attribute value from.
56+
attr: a string of the attribute separated by dots.
57+
58+
Returns:
59+
The object attribute value or the given *args if the attr isn't present.
60+
"""
61+
def _getattr(obj, attr):
62+
return getattr(obj, attr, *args)
63+
64+
return functools.reduce(_getattr, [obj] + attr.split('.'))
65+
66+
67+
def convert_upper_case_to_snake_case(string):
68+
"""Converts a string from UpperCase to snake_case.
69+
70+
Primarily used to translate module names when retrieving them from version
71+
modules' __init__.py files.
72+
73+
Args:
74+
string: an arbitrary string to convert.
75+
"""
76+
new_string = ''
77+
index = 0
78+
79+
for char in string:
80+
if index == 0:
81+
new_string += char.lower()
82+
elif char.isupper():
83+
new_string += f'_{char.lower()}'
84+
else:
85+
new_string += char
86+
87+
index += 1
88+
89+
return new_string
90+
91+
92+
def convert_snake_case_to_upper_case(string):
93+
"""Converts a string from snake_case to UpperCase.
94+
95+
Primarily used to translate module names when retrieving them from version
96+
modules' __init__.py files.
97+
98+
Args:
99+
string: an arbitrary string to convert.
100+
"""
101+
def converter(match):
102+
"""Convert a string to strip underscores then uppercase it."""
103+
return match.group().replace('_', '').upper()
104+
105+
return _RE_FIND_CHARS_TO_UPPERCASE.sub(converter, string)

0 commit comments

Comments
 (0)