Skip to content

Commit 74326f4

Browse files
authored
feat(api core): simplify from_rfc3339 methods (#9641)
1 parent 0f6ad2a commit 74326f4

File tree

6 files changed

+93
-57
lines changed

6 files changed

+93
-57
lines changed

google/api_core/bidi.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ def __init__(self, access_limit, time_window):
172172

173173
self._time_window = time_window
174174
self._access_limit = access_limit
175-
self._past_entries = collections.deque(maxlen=access_limit) # least recent first
175+
self._past_entries = collections.deque(
176+
maxlen=access_limit
177+
) # least recent first
176178
self._entry_lock = threading.Lock()
177179

178180
def __enter__(self):
@@ -198,9 +200,7 @@ def __exit__(self, *_):
198200

199201
def __repr__(self):
200202
return "{}(access_limit={}, time_window={})".format(
201-
self.__class__.__name__,
202-
self._access_limit,
203-
repr(self._time_window),
203+
self.__class__.__name__, self._access_limit, repr(self._time_window)
204204
)
205205

206206

@@ -423,7 +423,7 @@ def __init__(
423423

424424
if throttle_reopen:
425425
self._reopen_throttle = _Throttle(
426-
access_limit=5, time_window=datetime.timedelta(seconds=10),
426+
access_limit=5, time_window=datetime.timedelta(seconds=10)
427427
)
428428
else:
429429
self._reopen_throttle = None

google/api_core/datetime_helpers.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,10 @@ def from_iso8601_time(value):
115115

116116

117117
def from_rfc3339(value):
118-
"""Convert a microsecond-precision timestamp to datetime.
118+
"""Convert an RFC3339-format timestamp to a native datetime.
119119
120-
Args:
121-
value (str): The RFC3339 string to convert.
122-
123-
Returns:
124-
datetime.datetime: The datetime object equivalent to the timestamp in
125-
UTC.
126-
"""
127-
return datetime.datetime.strptime(value, _RFC3339_MICROS).replace(tzinfo=pytz.utc)
128-
129-
130-
def from_rfc3339_nanos(value):
131-
"""Convert a nanosecond-precision timestamp to a native datetime.
120+
Supported formats include those without fractional seconds, or with
121+
any fraction up to nanosecond precision.
132122
133123
.. note::
134124
Python datetimes do not support nanosecond precision; this function
@@ -138,11 +128,11 @@ def from_rfc3339_nanos(value):
138128
value (str): The RFC3339 string to convert.
139129
140130
Returns:
141-
datetime.datetime: The datetime object equivalent to the timestamp in
142-
UTC.
131+
datetime.datetime: The datetime object equivalent to the timestamp
132+
in UTC.
143133
144134
Raises:
145-
ValueError: If the timestamp does not match the RFC 3339
135+
ValueError: If the timestamp does not match the RFC3339
146136
regular expression.
147137
"""
148138
with_nanos = _RFC3339_NANOS.match(value)
@@ -169,6 +159,9 @@ def from_rfc3339_nanos(value):
169159
return bare_seconds.replace(microsecond=micros, tzinfo=pytz.utc)
170160

171161

162+
from_rfc3339_nanos = from_rfc3339 # from_rfc3339_nanos method was deprecated.
163+
164+
172165
def to_rfc3339(value, ignore_zone=True):
173166
"""Convert a datetime to an RFC3339 timestamp string.
174167
@@ -215,22 +208,22 @@ def nanosecond(self):
215208
return self._nanosecond
216209

217210
def rfc3339(self):
218-
"""Return an RFC 3339-compliant timestamp.
211+
"""Return an RFC3339-compliant timestamp.
219212
220213
Returns:
221-
(str): Timestamp string according to RFC 3339 spec.
214+
(str): Timestamp string according to RFC3339 spec.
222215
"""
223216
if self._nanosecond == 0:
224217
return to_rfc3339(self)
225-
nanos = str(self._nanosecond).rjust(9, '0').rstrip("0")
218+
nanos = str(self._nanosecond).rjust(9, "0").rstrip("0")
226219
return "{}.{}Z".format(self.strftime(_RFC3339_NO_FRACTION), nanos)
227220

228221
@classmethod
229222
def from_rfc3339(cls, stamp):
230-
"""Parse RFC 3339-compliant timestamp, preserving nanoseconds.
223+
"""Parse RFC3339-compliant timestamp, preserving nanoseconds.
231224
232225
Args:
233-
stamp (str): RFC 3339 stamp, with up to nanosecond precision
226+
stamp (str): RFC3339 stamp, with up to nanosecond precision
234227
235228
Returns:
236229
:class:`DatetimeWithNanoseconds`:
@@ -280,7 +273,7 @@ def timestamp_pb(self):
280273

281274
@classmethod
282275
def from_timestamp_pb(cls, stamp):
283-
"""Parse RFC 3339-compliant timestamp, preserving nanoseconds.
276+
"""Parse RFC3339-compliant timestamp, preserving nanoseconds.
284277
285278
Args:
286279
stamp (:class:`~google.protobuf.timestamp_pb2.Timestamp`): timestamp message

google/api_core/iam.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@
3434
"""
3535

3636
import collections
37+
import warnings
38+
3739
try:
3840
from collections import abc as collections_abc
3941
except ImportError: # Python 2.7
4042
import collections as collections_abc
41-
import warnings
4243

4344
# Generic IAM roles
4445

google/api_core/protobuf_helpers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@
1515
"""Helpers for :mod:`protobuf`."""
1616

1717
import collections
18-
try:
19-
from collections import abc as collections_abc
20-
except ImportError: # Python 2.7
21-
import collections as collections_abc
2218
import copy
2319
import inspect
2420

2521
from google.protobuf import field_mask_pb2
2622
from google.protobuf import message
2723
from google.protobuf import wrappers_pb2
2824

25+
try:
26+
from collections import abc as collections_abc
27+
except ImportError: # Python 2.7
28+
import collections as collections_abc
29+
30+
2931
_SENTINEL = object()
3032
_WRAPPER_TYPES = (
3133
wrappers_pb2.BoolValue,

google/api_core/retry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def __init__(
237237
maximum=_DEFAULT_MAXIMUM_DELAY,
238238
multiplier=_DEFAULT_DELAY_MULTIPLIER,
239239
deadline=_DEFAULT_DEADLINE,
240-
on_error=None
240+
on_error=None,
241241
):
242242
self._predicate = predicate
243243
self._initial = initial

tests/unit/test_datetime_helpers.py

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,18 @@ def test_from_rfc3339():
8282
)
8383

8484

85-
def test_from_rfc3339_with_bad_tz():
86-
value = "2009-12-17T12:44:32.123456BAD"
87-
88-
with pytest.raises(ValueError):
89-
datetime_helpers.from_rfc3339(value)
90-
85+
def test_from_rfc3339_nanos():
86+
value = "2009-12-17T12:44:32.123456Z"
87+
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
88+
2009, 12, 17, 12, 44, 32, 123456, pytz.utc
89+
)
9190

92-
def test_from_rfc3339_with_nanos():
93-
value = "2009-12-17T12:44:32.123456789Z"
9491

95-
with pytest.raises(ValueError):
96-
datetime_helpers.from_rfc3339(value)
92+
def test_from_rfc3339_without_nanos():
93+
value = "2009-12-17T12:44:32Z"
94+
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
95+
2009, 12, 17, 12, 44, 32, 0, pytz.utc
96+
)
9797

9898

9999
def test_from_rfc3339_nanos_without_nanos():
@@ -103,11 +103,33 @@ def test_from_rfc3339_nanos_without_nanos():
103103
)
104104

105105

106-
def test_from_rfc3339_nanos_with_bad_tz():
107-
value = "2009-12-17T12:44:32.123456789BAD"
106+
@pytest.mark.parametrize(
107+
"truncated, micros",
108+
[
109+
("12345678", 123456),
110+
("1234567", 123456),
111+
("123456", 123456),
112+
("12345", 123450),
113+
("1234", 123400),
114+
("123", 123000),
115+
("12", 120000),
116+
("1", 100000),
117+
],
118+
)
119+
def test_from_rfc3339_with_truncated_nanos(truncated, micros):
120+
value = "2009-12-17T12:44:32.{}Z".format(truncated)
121+
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
122+
2009, 12, 17, 12, 44, 32, micros, pytz.utc
123+
)
124+
125+
126+
def test_from_rfc3339_nanos_is_deprecated():
127+
value = "2009-12-17T12:44:32.123456Z"
108128

109-
with pytest.raises(ValueError):
110-
datetime_helpers.from_rfc3339_nanos(value)
129+
result = datetime_helpers.from_rfc3339(value)
130+
result_nanos = datetime_helpers.from_rfc3339_nanos(value)
131+
132+
assert result == result_nanos
111133

112134

113135
@pytest.mark.parametrize(
@@ -130,6 +152,18 @@ def test_from_rfc3339_nanos_with_truncated_nanos(truncated, micros):
130152
)
131153

132154

155+
def test_from_rfc3339_wo_nanos_raise_exception():
156+
value = "2009-12-17T12:44:32"
157+
with pytest.raises(ValueError):
158+
datetime_helpers.from_rfc3339(value)
159+
160+
161+
def test_from_rfc3339_w_nanos_raise_exception():
162+
value = "2009-12-17T12:44:32.123456"
163+
with pytest.raises(ValueError):
164+
datetime_helpers.from_rfc3339(value)
165+
166+
133167
def test_to_rfc3339():
134168
value = datetime.datetime(2016, 4, 5, 13, 30, 0)
135169
expected = "2016-04-05T13:30:00.000000Z"
@@ -157,10 +191,11 @@ def test_to_rfc3339_with_non_utc_ignore_zone():
157191

158192

159193
class Test_DateTimeWithNanos(object):
160-
161194
@staticmethod
162195
def test_ctor_wo_nanos():
163-
stamp = datetime_helpers.DatetimeWithNanoseconds(2016, 12, 20, 21, 13, 47, 123456)
196+
stamp = datetime_helpers.DatetimeWithNanoseconds(
197+
2016, 12, 20, 21, 13, 47, 123456
198+
)
164199
assert stamp.year == 2016
165200
assert stamp.month == 12
166201
assert stamp.day == 20
@@ -200,7 +235,9 @@ def test_ctor_w_micros_keyword_and_nanos():
200235

201236
@staticmethod
202237
def test_rfc3339_wo_nanos():
203-
stamp = datetime_helpers.DatetimeWithNanoseconds(2016, 12, 20, 21, 13, 47, 123456)
238+
stamp = datetime_helpers.DatetimeWithNanoseconds(
239+
2016, 12, 20, 21, 13, 47, 123456
240+
)
204241
assert stamp.rfc3339() == "2016-12-20T21:13:47.123456Z"
205242

206243
@staticmethod
@@ -285,12 +322,16 @@ def test_from_rfc3339_w_full_precision():
285322
)
286323
def test_from_rfc3339_test_nanoseconds(fractional, nanos):
287324
value = "2009-12-17T12:44:32.{}Z".format(fractional)
288-
assert datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(value).nanosecond == nanos
325+
assert (
326+
datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(value).nanosecond
327+
== nanos
328+
)
289329

290330
@staticmethod
291331
def test_timestamp_pb_wo_nanos_naive():
292332
stamp = datetime_helpers.DatetimeWithNanoseconds(
293-
2016, 12, 20, 21, 13, 47, 123456)
333+
2016, 12, 20, 21, 13, 47, 123456
334+
)
294335
delta = stamp.replace(tzinfo=pytz.UTC) - datetime_helpers._UTC_EPOCH
295336
seconds = int(delta.total_seconds())
296337
nanos = 123456000
@@ -304,7 +345,8 @@ def test_timestamp_pb_w_nanos():
304345
)
305346
delta = stamp - datetime_helpers._UTC_EPOCH
306347
timestamp = timestamp_pb2.Timestamp(
307-
seconds=int(delta.total_seconds()), nanos=123456789)
348+
seconds=int(delta.total_seconds()), nanos=123456789
349+
)
308350
assert stamp.timestamp_pb() == timestamp
309351

310352
@staticmethod
@@ -314,8 +356,7 @@ def test_from_timestamp_pb_wo_nanos():
314356
seconds = int(delta.total_seconds())
315357
timestamp = timestamp_pb2.Timestamp(seconds=seconds)
316358

317-
stamp = datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(
318-
timestamp)
359+
stamp = datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(timestamp)
319360

320361
assert _to_seconds(when) == _to_seconds(stamp)
321362
assert stamp.microsecond == 0
@@ -329,8 +370,7 @@ def test_from_timestamp_pb_w_nanos():
329370
seconds = int(delta.total_seconds())
330371
timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=123456789)
331372

332-
stamp = datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(
333-
timestamp)
373+
stamp = datetime_helpers.DatetimeWithNanoseconds.from_timestamp_pb(timestamp)
334374

335375
assert _to_seconds(when) == _to_seconds(stamp)
336376
assert stamp.microsecond == 123456

0 commit comments

Comments
 (0)