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