blob: de86beeb84550e2ad679c5c14f0151bd72d1f1a8 [file] [log] [blame]
Jon Wayne Parrott8713a712016-10-04 14:19:01 -07001# Copyright 2015 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Helper functions for commonly used utilities."""
16
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -080017import base64
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070018import calendar
19import datetime
20
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070021import six
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -070022from six.moves import urllib
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070023
24
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070025def copy_docstring(source_class):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070026 """Decorator that copies a method's docstring from another class.
27
28 Args:
29 source_class (type): The class that has the documented method.
30
31 Returns:
32 Callable: A decorator that will copy the docstring of the same
33 named method in the source class to the decorated method.
34 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070035 def decorator(method):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070036 """Decorator implementation.
37
38 Args:
Jon Wayne Parrott0a0be142016-10-14 14:53:29 -070039 method (Callable): The method to copy the docstring to.
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070040
41 Returns:
42 Callable: the same method passed in with an updated docstring.
43
44 Raises:
45 ValueError: if the method already has a docstring.
46 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070047 if method.__doc__:
48 raise ValueError('Method already has a docstring.')
49
50 source_method = getattr(source_class, method.__name__)
51 method.__doc__ = source_method.__doc__
52
53 return method
54 return decorator
55
56
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070057def utcnow():
58 """Returns the current UTC datetime.
59
60 Returns:
61 datetime: The current time in UTC.
62 """
63 return datetime.datetime.utcnow()
64
65
66def datetime_to_secs(value):
67 """Convert a datetime object to the number of seconds since the UNIX epoch.
68
69 Args:
70 value (datetime): The datetime to convert.
71
72 Returns:
73 int: The number of seconds since the UNIX epoch.
74 """
75 return calendar.timegm(value.utctimetuple())
76
77
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070078def to_bytes(value, encoding='utf-8'):
79 """Converts a string value to bytes, if necessary.
80
81 Unfortunately, ``six.b`` is insufficient for this task since in
82 Python 2 because it does not modify ``unicode`` objects.
83
84 Args:
85 value (Union[str, bytes]): The value to be converted.
86 encoding (str): The encoding to use to convert unicode to bytes.
87 Defaults to "utf-8".
88
89 Returns:
90 bytes: The original value converted to bytes (if unicode) or as
91 passed in if it started out as bytes.
92
93 Raises:
94 ValueError: If the value could not be converted to bytes.
95 """
96 result = (value.encode(encoding)
97 if isinstance(value, six.text_type) else value)
98 if isinstance(result, six.binary_type):
99 return result
100 else:
101 raise ValueError('{0!r} could not be converted to bytes'.format(value))
102
103
104def from_bytes(value):
105 """Converts bytes to a string value, if necessary.
106
107 Args:
108 value (Union[str, bytes]): The value to be converted.
109
110 Returns:
111 str: The original value converted to unicode (if bytes) or as passed in
112 if it started out as unicode.
113
114 Raises:
115 ValueError: If the value could not be converted to unicode.
116 """
117 result = (value.decode('utf-8')
118 if isinstance(value, six.binary_type) else value)
119 if isinstance(result, six.text_type):
120 return result
121 else:
122 raise ValueError(
123 '{0!r} could not be converted to unicode'.format(value))
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700124
125
126def update_query(url, params, remove=None):
127 """Updates a URL's query parameters.
128
129 Replaces any current values if they are already present in the URL.
130
131 Args:
132 url (str): The URL to update.
133 params (Mapping[str, str]): A mapping of query parameter
134 keys to values.
135 remove (Sequence[str]): Parameters to remove from the query string.
136
137 Returns:
138 str: The URL with updated query parameters.
139
140 Examples:
141
142 >>> url = 'http://example.com?a=1'
143 >>> update_query(url, {'a': '2'})
144 http://example.com?a=2
145 >>> update_query(url, {'b': '3'})
146 http://example.com?a=1&b=3
147 >> update_query(url, {'b': '3'}, remove=['a'])
148 http://example.com?b=3
149
150 """
151 if remove is None:
152 remove = []
153
154 # Split the URL into parts.
155 parts = urllib.parse.urlparse(url)
156 # Parse the query string.
157 query_params = urllib.parse.parse_qs(parts.query)
158 # Update the query parameters with the new parameters.
159 query_params.update(params)
160 # Remove any values specified in remove.
161 query_params = {
162 key: value for key, value
163 in six.iteritems(query_params)
164 if key not in remove}
165 # Re-encoded the query string.
166 new_query = urllib.parse.urlencode(query_params, doseq=True)
167 # Unsplit the url.
168 new_parts = parts._replace(query=new_query)
169 return urllib.parse.urlunparse(new_parts)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700170
171
172def scopes_to_string(scopes):
173 """Converts scope value to a string suitable for sending to OAuth 2.0
174 authorization servers.
175
176 Args:
177 scopes (Sequence[str]): The sequence of scopes to convert.
178
179 Returns:
180 str: The scopes formatted as a single string.
181 """
182 return ' '.join(scopes)
183
184
185def string_to_scopes(scopes):
186 """Converts stringifed scopes value to a list.
187
188 Args:
189 scopes (Union[Sequence, str]): The string of space-separated scopes
190 to convert.
191 Returns:
192 Sequence(str): The separated scopes.
193 """
194 if not scopes:
195 return []
196
197 return scopes.split(' ')
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800198
199
200def padded_urlsafe_b64decode(value):
201 """Decodes base64 strings lacking padding characters.
202
203 Google infrastructure tends to omit the base64 padding characters.
204
205 Args:
206 value (Union[str, bytes]): The encoded value.
207
208 Returns:
209 bytes: The decoded value
210 """
211 b64string = to_bytes(value)
212 padded = b64string + b'=' * (-len(b64string) % 4)
213 return base64.urlsafe_b64decode(padded)