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.
@@ -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
@@ -1,68 +1,141 @@
1
- import time
2
-
3
- import pytest
4
- from foxglove import (
5
- Capability,
6
- ServerListener,
7
- Service,
8
- start_server,
9
- )
10
- from foxglove.websocket import ServiceSchema, StatusLevel
11
-
12
-
13
- def test_server_interface() -> None:
14
- """
15
- Exercise the server interface; will also be checked with mypy.
16
- """
17
- server = start_server(port=0)
18
- assert isinstance(server.port, int)
19
- assert server.port != 0
20
- server.publish_status("test message", StatusLevel.Info, "some-id")
21
- server.broadcast_time(time.time_ns())
22
- server.remove_status(["some-id"])
23
- server.clear_session()
24
- server.stop()
25
-
26
-
27
- def test_server_listener_provides_default_implementation() -> None:
28
- class DefaultServerListener(ServerListener):
29
- pass
30
-
31
- listener = DefaultServerListener()
32
-
33
- listener.on_parameters_subscribe(["test"])
34
- listener.on_parameters_unsubscribe(["test"])
35
-
36
-
37
- def test_services_interface() -> None:
38
- test_svc = Service(
39
- name="test",
40
- schema=ServiceSchema(name="test-schema"),
41
- handler=lambda *_: b"{}",
42
- )
43
- test2_svc = Service(
44
- name="test2",
45
- schema=ServiceSchema(name="test-schema"),
46
- handler=lambda *_: b"{}",
47
- )
48
- server = start_server(
49
- port=0,
50
- capabilities=[Capability.Services],
51
- supported_encodings=["json"],
52
- services=[test_svc],
53
- )
54
-
55
- # Add a new service.
56
- server.add_services([test2_svc])
57
-
58
- # Can't add a service with the same name.
59
- with pytest.raises(RuntimeError):
60
- server.add_services([test_svc])
61
-
62
- # Remove services.
63
- server.remove_services(["test", "test2"])
64
-
65
- # Re-add a service.
66
- server.add_services([test_svc])
67
-
68
- server.stop()
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))
@@ -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))