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