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