blob: 92c6c831741cc5f889d4a1835458470a9fee34a1 [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 Parrott5824ad82016-10-06 09:27:44 -070017import calendar
18import datetime
19
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070020import six
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -070021from six.moves import urllib
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070022
23
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070024def copy_docstring(source_class):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070025 """Decorator that copies a method's docstring from another class.
26
27 Args:
28 source_class (type): The class that has the documented method.
29
30 Returns:
31 Callable: A decorator that will copy the docstring of the same
32 named method in the source class to the decorated method.
33 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070034 def decorator(method):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070035 """Decorator implementation.
36
37 Args:
38 method: The method to copy the docstring to.
39
40 Returns:
41 Callable: the same method passed in with an updated docstring.
42
43 Raises:
44 ValueError: if the method already has a docstring.
45 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070046 if method.__doc__:
47 raise ValueError('Method already has a docstring.')
48
49 source_method = getattr(source_class, method.__name__)
50 method.__doc__ = source_method.__doc__
51
52 return method
53 return decorator
54
55
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070056def utcnow():
57 """Returns the current UTC datetime.
58
59 Returns:
60 datetime: The current time in UTC.
61 """
62 return datetime.datetime.utcnow()
63
64
65def datetime_to_secs(value):
66 """Convert a datetime object to the number of seconds since the UNIX epoch.
67
68 Args:
69 value (datetime): The datetime to convert.
70
71 Returns:
72 int: The number of seconds since the UNIX epoch.
73 """
74 return calendar.timegm(value.utctimetuple())
75
76
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070077def to_bytes(value, encoding='utf-8'):
78 """Converts a string value to bytes, if necessary.
79
80 Unfortunately, ``six.b`` is insufficient for this task since in
81 Python 2 because it does not modify ``unicode`` objects.
82
83 Args:
84 value (Union[str, bytes]): The value to be converted.
85 encoding (str): The encoding to use to convert unicode to bytes.
86 Defaults to "utf-8".
87
88 Returns:
89 bytes: The original value converted to bytes (if unicode) or as
90 passed in if it started out as bytes.
91
92 Raises:
93 ValueError: If the value could not be converted to bytes.
94 """
95 result = (value.encode(encoding)
96 if isinstance(value, six.text_type) else value)
97 if isinstance(result, six.binary_type):
98 return result
99 else:
100 raise ValueError('{0!r} could not be converted to bytes'.format(value))
101
102
103def from_bytes(value):
104 """Converts bytes to a string value, if necessary.
105
106 Args:
107 value (Union[str, bytes]): The value to be converted.
108
109 Returns:
110 str: The original value converted to unicode (if bytes) or as passed in
111 if it started out as unicode.
112
113 Raises:
114 ValueError: If the value could not be converted to unicode.
115 """
116 result = (value.decode('utf-8')
117 if isinstance(value, six.binary_type) else value)
118 if isinstance(result, six.text_type):
119 return result
120 else:
121 raise ValueError(
122 '{0!r} could not be converted to unicode'.format(value))
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700123
124
125def update_query(url, params, remove=None):
126 """Updates a URL's query parameters.
127
128 Replaces any current values if they are already present in the URL.
129
130 Args:
131 url (str): The URL to update.
132 params (Mapping[str, str]): A mapping of query parameter
133 keys to values.
134 remove (Sequence[str]): Parameters to remove from the query string.
135
136 Returns:
137 str: The URL with updated query parameters.
138
139 Examples:
140
141 >>> url = 'http://example.com?a=1'
142 >>> update_query(url, {'a': '2'})
143 http://example.com?a=2
144 >>> update_query(url, {'b': '3'})
145 http://example.com?a=1&b=3
146 >> update_query(url, {'b': '3'}, remove=['a'])
147 http://example.com?b=3
148
149 """
150 if remove is None:
151 remove = []
152
153 # Split the URL into parts.
154 parts = urllib.parse.urlparse(url)
155 # Parse the query string.
156 query_params = urllib.parse.parse_qs(parts.query)
157 # Update the query parameters with the new parameters.
158 query_params.update(params)
159 # Remove any values specified in remove.
160 query_params = {
161 key: value for key, value
162 in six.iteritems(query_params)
163 if key not in remove}
164 # Re-encoded the query string.
165 new_query = urllib.parse.urlencode(query_params, doseq=True)
166 # Unsplit the url.
167 new_parts = parts._replace(query=new_query)
168 return urllib.parse.urlunparse(new_parts)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700169
170
171def scopes_to_string(scopes):
172 """Converts scope value to a string suitable for sending to OAuth 2.0
173 authorization servers.
174
175 Args:
176 scopes (Sequence[str]): The sequence of scopes to convert.
177
178 Returns:
179 str: The scopes formatted as a single string.
180 """
181 return ' '.join(scopes)
182
183
184def string_to_scopes(scopes):
185 """Converts stringifed scopes value to a list.
186
187 Args:
188 scopes (Union[Sequence, str]): The string of space-separated scopes
189 to convert.
190 Returns:
191 Sequence(str): The separated scopes.
192 """
193 if not scopes:
194 return []
195
196 return scopes.split(' ')