foxglove-sdk 0.6.2__cp312-cp312-win32.whl → 0.16.6__cp312-cp312-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- foxglove/__init__.py +241 -121
- foxglove/_foxglove_py/__init__.pyi +233 -136
- foxglove/_foxglove_py/channels.pyi +2860 -1053
- foxglove/_foxglove_py/cloud.pyi +9 -0
- foxglove/_foxglove_py/mcap.pyi +125 -0
- foxglove/_foxglove_py/schemas.pyi +1037 -666
- foxglove/_foxglove_py/schemas_wkt.pyi +85 -77
- foxglove/_foxglove_py/websocket.pyi +394 -297
- foxglove/_foxglove_py.cp312-win32.pyd +0 -0
- foxglove/benchmarks/test_mcap_serialization.py +160 -161
- foxglove/channel.py +241 -204
- foxglove/channels/__init__.py +96 -72
- foxglove/cloud.py +61 -0
- foxglove/layouts/__init__.py +17 -0
- foxglove/mcap.py +12 -0
- foxglove/notebook/__init__.py +0 -0
- foxglove/notebook/foxglove_widget.py +82 -0
- foxglove/notebook/notebook_buffer.py +114 -0
- foxglove/notebook/static/widget.js +1 -0
- foxglove/schemas/__init__.py +166 -149
- foxglove/tests/test_channel.py +243 -142
- foxglove/tests/test_context.py +10 -0
- foxglove/tests/test_logging.py +62 -16
- foxglove/tests/test_mcap.py +477 -46
- foxglove/tests/test_parameters.py +178 -0
- foxglove/tests/test_schemas.py +17 -0
- foxglove/tests/test_server.py +141 -68
- foxglove/tests/test_time.py +137 -137
- foxglove/websocket.py +220 -172
- foxglove_sdk-0.16.6.dist-info/METADATA +53 -0
- foxglove_sdk-0.16.6.dist-info/RECORD +34 -0
- {foxglove_sdk-0.6.2.dist-info → foxglove_sdk-0.16.6.dist-info}/WHEEL +1 -1
- foxglove_sdk-0.6.2.dist-info/METADATA +0 -51
- foxglove_sdk-0.6.2.dist-info/RECORD +0 -22
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from foxglove.schemas import Log, LogLevel, Timestamp
|
|
2
|
+
|
|
3
|
+
""" Asserts that foxglove schemas can be encoded as protobuf. """
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_can_encode() -> None:
|
|
7
|
+
msg = Log(
|
|
8
|
+
timestamp=Timestamp(5, 10),
|
|
9
|
+
level=LogLevel.Error,
|
|
10
|
+
message="hello",
|
|
11
|
+
name="logger",
|
|
12
|
+
file="file",
|
|
13
|
+
line=123,
|
|
14
|
+
)
|
|
15
|
+
encoded = msg.encode()
|
|
16
|
+
assert isinstance(encoded, bytes)
|
|
17
|
+
assert len(encoded) == 34
|
foxglove/tests/test_server.py
CHANGED
|
@@ -1,68 +1,141 @@
|
|
|
1
|
-
import time
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
server
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
server.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
import time
|
|
2
|
+
import typing
|
|
3
|
+
from urllib.parse import parse_qs, urlparse
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from foxglove import (
|
|
7
|
+
Capability,
|
|
8
|
+
Channel,
|
|
9
|
+
Context,
|
|
10
|
+
ServerListener,
|
|
11
|
+
Service,
|
|
12
|
+
start_server,
|
|
13
|
+
)
|
|
14
|
+
from foxglove.websocket import PlaybackState, PlaybackStatus, ServiceSchema, StatusLevel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_server_interface() -> None:
|
|
18
|
+
"""
|
|
19
|
+
Exercise the server interface; will also be checked with mypy.
|
|
20
|
+
"""
|
|
21
|
+
server = start_server(
|
|
22
|
+
port=0, session_id="test-session", channel_filter=lambda _: True
|
|
23
|
+
)
|
|
24
|
+
assert isinstance(server.port, int)
|
|
25
|
+
assert server.port != 0
|
|
26
|
+
|
|
27
|
+
raw_url = server.app_url()
|
|
28
|
+
assert raw_url is not None
|
|
29
|
+
url = urlparse(raw_url)
|
|
30
|
+
assert url.scheme == "https"
|
|
31
|
+
assert url.netloc == "app.foxglove.dev"
|
|
32
|
+
assert parse_qs(url.query) == {
|
|
33
|
+
"ds": ["foxglove-websocket"],
|
|
34
|
+
"ds.url": [f"ws://127.0.0.1:{server.port}"],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
raw_url = server.app_url(layout_id="lay_123", open_in_desktop=True)
|
|
38
|
+
assert raw_url is not None
|
|
39
|
+
url = urlparse(raw_url)
|
|
40
|
+
assert url.scheme == "https"
|
|
41
|
+
assert url.netloc == "app.foxglove.dev"
|
|
42
|
+
assert parse_qs(url.query) == {
|
|
43
|
+
"ds": ["foxglove-websocket"],
|
|
44
|
+
"ds.url": [f"ws://127.0.0.1:{server.port}"],
|
|
45
|
+
"layoutId": ["lay_123"],
|
|
46
|
+
"openIn": ["desktop"],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
server.publish_status("test message", StatusLevel.Info, "some-id")
|
|
50
|
+
server.broadcast_time(time.time_ns())
|
|
51
|
+
server.broadcast_playback_state(
|
|
52
|
+
PlaybackState(
|
|
53
|
+
status=PlaybackStatus.Paused,
|
|
54
|
+
playback_speed=1.0,
|
|
55
|
+
current_time=time.time_ns(),
|
|
56
|
+
did_seek=False,
|
|
57
|
+
request_id=None,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
server.remove_status(["some-id"])
|
|
61
|
+
server.clear_session("new-session")
|
|
62
|
+
server.stop()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_server_listener_provides_default_implementation() -> None:
|
|
66
|
+
class DefaultServerListener(ServerListener):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
listener = DefaultServerListener()
|
|
70
|
+
|
|
71
|
+
listener.on_parameters_subscribe(["test"])
|
|
72
|
+
listener.on_parameters_unsubscribe(["test"])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_services_interface() -> None:
|
|
76
|
+
test_svc = Service(
|
|
77
|
+
name="test",
|
|
78
|
+
schema=ServiceSchema(name="test-schema"),
|
|
79
|
+
handler=lambda *_: b"{}",
|
|
80
|
+
)
|
|
81
|
+
test2_svc = Service(
|
|
82
|
+
name="test2",
|
|
83
|
+
schema=ServiceSchema(name="test-schema"),
|
|
84
|
+
handler=lambda *_: b"{}",
|
|
85
|
+
)
|
|
86
|
+
server = start_server(
|
|
87
|
+
port=0,
|
|
88
|
+
capabilities=[Capability.Services],
|
|
89
|
+
supported_encodings=["json"],
|
|
90
|
+
services=[test_svc],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Add a new service.
|
|
94
|
+
server.add_services([test2_svc])
|
|
95
|
+
|
|
96
|
+
# Can't add a service with the same name.
|
|
97
|
+
with pytest.raises(RuntimeError):
|
|
98
|
+
server.add_services([test_svc])
|
|
99
|
+
|
|
100
|
+
# Remove services.
|
|
101
|
+
server.remove_services(["test", "test2"])
|
|
102
|
+
|
|
103
|
+
# Re-add a service.
|
|
104
|
+
server.add_services([test_svc])
|
|
105
|
+
|
|
106
|
+
server.stop()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_context_can_be_attached_to_server() -> None:
|
|
110
|
+
ctx1 = Context()
|
|
111
|
+
ctx2 = Context()
|
|
112
|
+
|
|
113
|
+
server1 = start_server(port=0, context=ctx1)
|
|
114
|
+
server2 = start_server(port=0, context=ctx2)
|
|
115
|
+
|
|
116
|
+
ch1 = Channel("/1", context=ctx1)
|
|
117
|
+
ch2 = Channel("/2", context=ctx2)
|
|
118
|
+
ch1.log("test")
|
|
119
|
+
ch2.log("test")
|
|
120
|
+
|
|
121
|
+
server1.stop()
|
|
122
|
+
server2.stop()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@typing.no_type_check
|
|
126
|
+
def test_server_with_invalid_playback_time_range() -> None:
|
|
127
|
+
with pytest.raises(TypeError):
|
|
128
|
+
# Tuple of a single element
|
|
129
|
+
start_server(port=0, playback_time_range=(123,))
|
|
130
|
+
|
|
131
|
+
with pytest.raises(TypeError):
|
|
132
|
+
# Tuple with invalid types
|
|
133
|
+
start_server(port=0, playback_time_range=("not-a-time", None))
|
|
134
|
+
|
|
135
|
+
with pytest.raises(TypeError):
|
|
136
|
+
# Not a tuple
|
|
137
|
+
start_server(port=0, playback_time_range=23443)
|
|
138
|
+
|
|
139
|
+
with pytest.raises(TypeError):
|
|
140
|
+
# Tuple with too many elements
|
|
141
|
+
start_server(port=0, playback_time_range=(123, 456, 789))
|
foxglove/tests/test_time.py
CHANGED
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from foxglove.schemas import Duration, Timestamp
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_duration_normalization() -> None:
|
|
8
|
-
assert Duration(sec=0, nsec=1_111_222_333) == Duration(sec=1, nsec=111_222_333)
|
|
9
|
-
assert Duration(sec=0, nsec=2**32 - 1) == Duration(sec=4, nsec=294_967_295)
|
|
10
|
-
assert Duration(sec=-2, nsec=1_000_000_001) == Duration(sec=-1, nsec=1)
|
|
11
|
-
assert Duration(sec=-(2**31), nsec=1_000_000_001) == Duration(
|
|
12
|
-
sec=-(2**31) + 1, nsec=1
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
# argument conversions
|
|
16
|
-
d = Duration(sec=-(2**31))
|
|
17
|
-
assert d.sec == -(2**31)
|
|
18
|
-
assert d.nsec == 0
|
|
19
|
-
|
|
20
|
-
d = Duration(sec=0, nsec=2**32 - 1)
|
|
21
|
-
assert d.sec == 4
|
|
22
|
-
assert d.nsec == 294_967_295
|
|
23
|
-
|
|
24
|
-
d = Duration(sec=2**31 - 1, nsec=999_999_999)
|
|
25
|
-
assert d.sec == 2**31 - 1
|
|
26
|
-
assert d.nsec == 999_999_999
|
|
27
|
-
|
|
28
|
-
with pytest.raises(OverflowError):
|
|
29
|
-
Duration(sec=-(2**31) - 1)
|
|
30
|
-
with pytest.raises(OverflowError):
|
|
31
|
-
Duration(sec=2**31)
|
|
32
|
-
with pytest.raises(OverflowError):
|
|
33
|
-
Duration(sec=0, nsec=-1)
|
|
34
|
-
with pytest.raises(OverflowError):
|
|
35
|
-
Duration(sec=0, nsec=2**32)
|
|
36
|
-
|
|
37
|
-
# overflow past upper bound
|
|
38
|
-
with pytest.raises(OverflowError):
|
|
39
|
-
Duration(sec=2**31 - 1, nsec=1_000_000_000)
|
|
40
|
-
|
|
41
|
-
# we don't handle this corner case, where seconds is beyond the lower
|
|
42
|
-
# bound, but nanoseconds overflow to bring the duration within range.
|
|
43
|
-
with pytest.raises(OverflowError):
|
|
44
|
-
Duration(sec=-(2**31) - 1, nsec=1_000_000_000)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_duration_from_secs() -> None:
|
|
48
|
-
assert Duration.from_secs(1.123) == Duration(sec=1, nsec=123_000_000)
|
|
49
|
-
assert Duration.from_secs(-0.123) == Duration(sec=-1, nsec=877_000_000)
|
|
50
|
-
assert Duration.from_secs(-1.123) == Duration(sec=-2, nsec=877_000_000)
|
|
51
|
-
|
|
52
|
-
with pytest.raises(OverflowError):
|
|
53
|
-
Duration.from_secs(-1e42)
|
|
54
|
-
|
|
55
|
-
with pytest.raises(OverflowError):
|
|
56
|
-
Duration.from_secs(1e42)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_duration_from_timedelta() -> None:
|
|
60
|
-
td = datetime.timedelta(seconds=1, milliseconds=123)
|
|
61
|
-
assert Duration.from_timedelta(td) == Duration(sec=1, nsec=123_000_000)
|
|
62
|
-
|
|
63
|
-
# no loss of precision
|
|
64
|
-
td = datetime.timedelta(days=9876, microseconds=123_456)
|
|
65
|
-
assert Duration.from_timedelta(td) == Duration(sec=853_286_400, nsec=123_456_000)
|
|
66
|
-
|
|
67
|
-
# timedeltas are normalized
|
|
68
|
-
td = datetime.timedelta(seconds=8 * 24 * 3600, milliseconds=99_111)
|
|
69
|
-
assert Duration.from_timedelta(td) == Duration(sec=691_299, nsec=111_000_000)
|
|
70
|
-
|
|
71
|
-
with pytest.raises(OverflowError):
|
|
72
|
-
Duration.from_timedelta(datetime.timedelta.min)
|
|
73
|
-
|
|
74
|
-
with pytest.raises(OverflowError):
|
|
75
|
-
Duration.from_timedelta(datetime.timedelta.max)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def test_timestamp_normalization() -> None:
|
|
79
|
-
assert Timestamp(sec=0, nsec=1_111_222_333) == Timestamp(sec=1, nsec=111_222_333)
|
|
80
|
-
assert Timestamp(sec=0, nsec=2**32 - 1) == Timestamp(sec=4, nsec=294_967_295)
|
|
81
|
-
|
|
82
|
-
# argument conversions
|
|
83
|
-
t = Timestamp(sec=0)
|
|
84
|
-
assert t.sec == 0
|
|
85
|
-
assert t.nsec == 0
|
|
86
|
-
|
|
87
|
-
t = Timestamp(sec=0, nsec=2**32 - 1)
|
|
88
|
-
assert t.sec == 4
|
|
89
|
-
assert t.nsec == 294_967_295
|
|
90
|
-
|
|
91
|
-
t = Timestamp(sec=2**32 - 1, nsec=999_999_999)
|
|
92
|
-
assert t.sec == 2**32 - 1
|
|
93
|
-
assert t.nsec == 999_999_999
|
|
94
|
-
|
|
95
|
-
with pytest.raises(OverflowError):
|
|
96
|
-
Timestamp(sec=-1)
|
|
97
|
-
with pytest.raises(OverflowError):
|
|
98
|
-
Timestamp(sec=2**32)
|
|
99
|
-
with pytest.raises(OverflowError):
|
|
100
|
-
Timestamp(sec=0, nsec=-1)
|
|
101
|
-
with pytest.raises(OverflowError):
|
|
102
|
-
Timestamp(sec=0, nsec=2**32)
|
|
103
|
-
|
|
104
|
-
# overflow past upper bound
|
|
105
|
-
with pytest.raises(OverflowError):
|
|
106
|
-
Timestamp(sec=2**32 - 1, nsec=1_000_000_000)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def test_timestamp_from_epoch_secs() -> None:
|
|
110
|
-
assert Timestamp.from_epoch_secs(1.123) == Timestamp(sec=1, nsec=123_000_000)
|
|
111
|
-
|
|
112
|
-
with pytest.raises(OverflowError):
|
|
113
|
-
Timestamp.from_epoch_secs(-1.0)
|
|
114
|
-
|
|
115
|
-
with pytest.raises(OverflowError):
|
|
116
|
-
Timestamp.from_epoch_secs(1e42)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def test_timestamp_from_datetime() -> None:
|
|
120
|
-
utc = datetime.timezone.utc
|
|
121
|
-
dt = datetime.datetime(1970, 1, 1, tzinfo=utc)
|
|
122
|
-
assert Timestamp.from_datetime(dt) == Timestamp(sec=0)
|
|
123
|
-
|
|
124
|
-
# no loss of precision
|
|
125
|
-
dt = datetime.datetime(2025, 1, 1, microsecond=42, tzinfo=utc)
|
|
126
|
-
assert Timestamp.from_datetime(dt) == Timestamp(sec=1_735_689_600, nsec=42_000)
|
|
127
|
-
|
|
128
|
-
# alternative timezone
|
|
129
|
-
local_tz = datetime.timezone(datetime.timedelta(hours=-1))
|
|
130
|
-
dt = datetime.datetime(1970, 1, 1, 0, 0, 1, 123_000, tzinfo=local_tz)
|
|
131
|
-
assert Timestamp.from_datetime(dt) == Timestamp(sec=3601, nsec=123_000_000)
|
|
132
|
-
|
|
133
|
-
with pytest.raises(OverflowError):
|
|
134
|
-
Timestamp.from_datetime(datetime.datetime(1969, 12, 31, tzinfo=utc))
|
|
135
|
-
|
|
136
|
-
with pytest.raises(OverflowError):
|
|
137
|
-
Timestamp.from_datetime(datetime.datetime(2106, 2, 8, tzinfo=utc))
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from foxglove.schemas import Duration, Timestamp
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_duration_normalization() -> None:
|
|
8
|
+
assert Duration(sec=0, nsec=1_111_222_333) == Duration(sec=1, nsec=111_222_333)
|
|
9
|
+
assert Duration(sec=0, nsec=2**32 - 1) == Duration(sec=4, nsec=294_967_295)
|
|
10
|
+
assert Duration(sec=-2, nsec=1_000_000_001) == Duration(sec=-1, nsec=1)
|
|
11
|
+
assert Duration(sec=-(2**31), nsec=1_000_000_001) == Duration(
|
|
12
|
+
sec=-(2**31) + 1, nsec=1
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# argument conversions
|
|
16
|
+
d = Duration(sec=-(2**31))
|
|
17
|
+
assert d.sec == -(2**31)
|
|
18
|
+
assert d.nsec == 0
|
|
19
|
+
|
|
20
|
+
d = Duration(sec=0, nsec=2**32 - 1)
|
|
21
|
+
assert d.sec == 4
|
|
22
|
+
assert d.nsec == 294_967_295
|
|
23
|
+
|
|
24
|
+
d = Duration(sec=2**31 - 1, nsec=999_999_999)
|
|
25
|
+
assert d.sec == 2**31 - 1
|
|
26
|
+
assert d.nsec == 999_999_999
|
|
27
|
+
|
|
28
|
+
with pytest.raises(OverflowError):
|
|
29
|
+
Duration(sec=-(2**31) - 1)
|
|
30
|
+
with pytest.raises(OverflowError):
|
|
31
|
+
Duration(sec=2**31)
|
|
32
|
+
with pytest.raises(OverflowError):
|
|
33
|
+
Duration(sec=0, nsec=-1)
|
|
34
|
+
with pytest.raises(OverflowError):
|
|
35
|
+
Duration(sec=0, nsec=2**32)
|
|
36
|
+
|
|
37
|
+
# overflow past upper bound
|
|
38
|
+
with pytest.raises(OverflowError):
|
|
39
|
+
Duration(sec=2**31 - 1, nsec=1_000_000_000)
|
|
40
|
+
|
|
41
|
+
# we don't handle this corner case, where seconds is beyond the lower
|
|
42
|
+
# bound, but nanoseconds overflow to bring the duration within range.
|
|
43
|
+
with pytest.raises(OverflowError):
|
|
44
|
+
Duration(sec=-(2**31) - 1, nsec=1_000_000_000)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_duration_from_secs() -> None:
|
|
48
|
+
assert Duration.from_secs(1.123) == Duration(sec=1, nsec=123_000_000)
|
|
49
|
+
assert Duration.from_secs(-0.123) == Duration(sec=-1, nsec=877_000_000)
|
|
50
|
+
assert Duration.from_secs(-1.123) == Duration(sec=-2, nsec=877_000_000)
|
|
51
|
+
|
|
52
|
+
with pytest.raises(OverflowError):
|
|
53
|
+
Duration.from_secs(-1e42)
|
|
54
|
+
|
|
55
|
+
with pytest.raises(OverflowError):
|
|
56
|
+
Duration.from_secs(1e42)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_duration_from_timedelta() -> None:
|
|
60
|
+
td = datetime.timedelta(seconds=1, milliseconds=123)
|
|
61
|
+
assert Duration.from_timedelta(td) == Duration(sec=1, nsec=123_000_000)
|
|
62
|
+
|
|
63
|
+
# no loss of precision
|
|
64
|
+
td = datetime.timedelta(days=9876, microseconds=123_456)
|
|
65
|
+
assert Duration.from_timedelta(td) == Duration(sec=853_286_400, nsec=123_456_000)
|
|
66
|
+
|
|
67
|
+
# timedeltas are normalized
|
|
68
|
+
td = datetime.timedelta(seconds=8 * 24 * 3600, milliseconds=99_111)
|
|
69
|
+
assert Duration.from_timedelta(td) == Duration(sec=691_299, nsec=111_000_000)
|
|
70
|
+
|
|
71
|
+
with pytest.raises(OverflowError):
|
|
72
|
+
Duration.from_timedelta(datetime.timedelta.min)
|
|
73
|
+
|
|
74
|
+
with pytest.raises(OverflowError):
|
|
75
|
+
Duration.from_timedelta(datetime.timedelta.max)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_timestamp_normalization() -> None:
|
|
79
|
+
assert Timestamp(sec=0, nsec=1_111_222_333) == Timestamp(sec=1, nsec=111_222_333)
|
|
80
|
+
assert Timestamp(sec=0, nsec=2**32 - 1) == Timestamp(sec=4, nsec=294_967_295)
|
|
81
|
+
|
|
82
|
+
# argument conversions
|
|
83
|
+
t = Timestamp(sec=0)
|
|
84
|
+
assert t.sec == 0
|
|
85
|
+
assert t.nsec == 0
|
|
86
|
+
|
|
87
|
+
t = Timestamp(sec=0, nsec=2**32 - 1)
|
|
88
|
+
assert t.sec == 4
|
|
89
|
+
assert t.nsec == 294_967_295
|
|
90
|
+
|
|
91
|
+
t = Timestamp(sec=2**32 - 1, nsec=999_999_999)
|
|
92
|
+
assert t.sec == 2**32 - 1
|
|
93
|
+
assert t.nsec == 999_999_999
|
|
94
|
+
|
|
95
|
+
with pytest.raises(OverflowError):
|
|
96
|
+
Timestamp(sec=-1)
|
|
97
|
+
with pytest.raises(OverflowError):
|
|
98
|
+
Timestamp(sec=2**32)
|
|
99
|
+
with pytest.raises(OverflowError):
|
|
100
|
+
Timestamp(sec=0, nsec=-1)
|
|
101
|
+
with pytest.raises(OverflowError):
|
|
102
|
+
Timestamp(sec=0, nsec=2**32)
|
|
103
|
+
|
|
104
|
+
# overflow past upper bound
|
|
105
|
+
with pytest.raises(OverflowError):
|
|
106
|
+
Timestamp(sec=2**32 - 1, nsec=1_000_000_000)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_timestamp_from_epoch_secs() -> None:
|
|
110
|
+
assert Timestamp.from_epoch_secs(1.123) == Timestamp(sec=1, nsec=123_000_000)
|
|
111
|
+
|
|
112
|
+
with pytest.raises(OverflowError):
|
|
113
|
+
Timestamp.from_epoch_secs(-1.0)
|
|
114
|
+
|
|
115
|
+
with pytest.raises(OverflowError):
|
|
116
|
+
Timestamp.from_epoch_secs(1e42)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_timestamp_from_datetime() -> None:
|
|
120
|
+
utc = datetime.timezone.utc
|
|
121
|
+
dt = datetime.datetime(1970, 1, 1, tzinfo=utc)
|
|
122
|
+
assert Timestamp.from_datetime(dt) == Timestamp(sec=0)
|
|
123
|
+
|
|
124
|
+
# no loss of precision
|
|
125
|
+
dt = datetime.datetime(2025, 1, 1, microsecond=42, tzinfo=utc)
|
|
126
|
+
assert Timestamp.from_datetime(dt) == Timestamp(sec=1_735_689_600, nsec=42_000)
|
|
127
|
+
|
|
128
|
+
# alternative timezone
|
|
129
|
+
local_tz = datetime.timezone(datetime.timedelta(hours=-1))
|
|
130
|
+
dt = datetime.datetime(1970, 1, 1, 0, 0, 1, 123_000, tzinfo=local_tz)
|
|
131
|
+
assert Timestamp.from_datetime(dt) == Timestamp(sec=3601, nsec=123_000_000)
|
|
132
|
+
|
|
133
|
+
with pytest.raises(OverflowError):
|
|
134
|
+
Timestamp.from_datetime(datetime.datetime(1969, 12, 31, tzinfo=utc))
|
|
135
|
+
|
|
136
|
+
with pytest.raises(OverflowError):
|
|
137
|
+
Timestamp.from_datetime(datetime.datetime(2106, 2, 8, tzinfo=utc))
|