foxglove-sdk 0.1.2__cp311-cp311-macosx_10_12_x86_64.whl → 0.15.3__cp311-cp311-macosx_10_12_x86_64.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.

Potentially problematic release.


This version of foxglove-sdk might be problematic. Click here for more details.

Files changed (38) hide show
  1. foxglove/__init__.py +220 -42
  2. foxglove/_foxglove_py/__init__.pyi +210 -0
  3. foxglove/_foxglove_py/channels.pyi +2792 -0
  4. foxglove/_foxglove_py/cloud.pyi +9 -0
  5. foxglove/_foxglove_py/mcap.pyi +96 -0
  6. foxglove/_foxglove_py/schemas.pyi +1009 -0
  7. foxglove/_foxglove_py/schemas_wkt.pyi +85 -0
  8. foxglove/_foxglove_py/websocket.pyi +321 -0
  9. foxglove/_foxglove_py.cpython-311-darwin.so +0 -0
  10. foxglove/benchmarks/test_mcap_serialization.py +160 -0
  11. foxglove/channel.py +228 -27
  12. foxglove/channels/__init__.py +94 -0
  13. foxglove/cloud.py +61 -0
  14. foxglove/mcap.py +12 -0
  15. foxglove/notebook/__init__.py +0 -0
  16. foxglove/notebook/foxglove_widget.py +100 -0
  17. foxglove/notebook/notebook_buffer.py +114 -0
  18. foxglove/notebook/static/widget.js +1 -0
  19. foxglove/schemas/__init__.py +162 -4
  20. foxglove/tests/__init__.py +0 -0
  21. foxglove/tests/test_channel.py +243 -0
  22. foxglove/tests/test_context.py +10 -0
  23. foxglove/tests/test_logging.py +62 -0
  24. foxglove/tests/test_mcap.py +199 -0
  25. foxglove/tests/test_parameters.py +178 -0
  26. foxglove/tests/test_schemas.py +17 -0
  27. foxglove/tests/test_server.py +112 -0
  28. foxglove/tests/test_time.py +137 -0
  29. foxglove/websocket.py +205 -0
  30. foxglove_sdk-0.15.3.dist-info/METADATA +53 -0
  31. foxglove_sdk-0.15.3.dist-info/RECORD +33 -0
  32. {foxglove_sdk-0.1.2.dist-info → foxglove_sdk-0.15.3.dist-info}/WHEEL +1 -1
  33. foxglove/_foxglove_py.pyi +0 -37
  34. foxglove/_protobuf/__init__.py +0 -38
  35. foxglove/encoding.py +0 -71
  36. foxglove/schemas_utils.py +0 -25
  37. foxglove_sdk-0.1.2.dist-info/METADATA +0 -75
  38. foxglove_sdk-0.1.2.dist-info/RECORD +0 -12
foxglove/__init__.py CHANGED
@@ -1,63 +1,241 @@
1
+ """
2
+ This module provides interfaces for logging messages to Foxglove.
3
+
4
+ See :py:mod:`foxglove.schemas` and :py:mod:`foxglove.channels` for working with well-known Foxglove
5
+ schemas.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
1
10
  import atexit
2
- from typing import Union
3
- from google.protobuf.message import Message
11
+ import logging
12
+ from typing import TYPE_CHECKING
13
+
14
+ from . import _foxglove_py as _foxglove
15
+
16
+ # Re-export these imports
4
17
  from ._foxglove_py import (
5
- record_file,
6
- enable_log_forwarding,
7
- disable_log_forwarding,
8
- start_server,
9
- shutdown,
18
+ ChannelDescriptor,
19
+ Context,
20
+ Schema,
21
+ SinkChannelFilter,
22
+ open_mcap,
10
23
  )
11
- from .encoding import Encoder, ProtobufEncoder, JsonEncoder
12
- from .channel import Channel
24
+ from .channel import Channel, log
13
25
 
14
- import logging
26
+ # Deprecated. Use foxglove.mcap.MCAPWriter instead.
27
+ from .mcap import MCAPWriter
15
28
 
16
- logging.basicConfig(
17
- level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s"
18
- )
29
+ if TYPE_CHECKING:
30
+ from .notebook.notebook_buffer import NotebookBuffer
19
31
 
20
- atexit.register(shutdown)
32
+ atexit.register(_foxglove.shutdown)
21
33
 
22
34
 
23
- def log_level_from_int(level: int) -> str:
24
- log_levels = {10: "debug", 20: "info", 30: "warn", 40: "error"}
25
- return log_levels.get(level, "unknown")
35
+ try:
36
+ from ._foxglove_py.cloud import CloudSink
26
37
 
38
+ # from ._foxglove_py.cloud import start_cloud_sink as _start_cloud_sink
39
+ from .cloud import CloudSinkListener
40
+ from .websocket import (
41
+ AssetHandler,
42
+ Capability,
43
+ ServerListener,
44
+ Service,
45
+ WebSocketServer,
46
+ )
27
47
 
28
- def verbose_on(level: Union[int, str] = "debug") -> None:
29
- if isinstance(level, int):
30
- assert level in [
31
- logging.DEBUG,
32
- logging.INFO,
33
- logging.WARN,
34
- logging.ERROR,
35
- ], ValueError("Invalid log level")
36
- level = log_level_from_int(level)
37
- else:
38
- assert level in ["debug", "info", "warn", "error"], ValueError(
39
- "Invalid log level"
48
+ def start_server(
49
+ *,
50
+ name: str | None = None,
51
+ host: str | None = "127.0.0.1",
52
+ port: int | None = 8765,
53
+ capabilities: list[Capability] | None = None,
54
+ server_listener: ServerListener | None = None,
55
+ supported_encodings: list[str] | None = None,
56
+ services: list[Service] | None = None,
57
+ asset_handler: AssetHandler | None = None,
58
+ context: Context | None = None,
59
+ session_id: str | None = None,
60
+ channel_filter: SinkChannelFilter | None = None,
61
+ ) -> WebSocketServer:
62
+ """
63
+ Start a websocket server for live visualization.
64
+
65
+ :param name: The name of the server.
66
+ :param host: The host to bind to.
67
+ :param port: The port to bind to.
68
+ :param capabilities: A list of capabilities to advertise to clients.
69
+ :param server_listener: A Python object that implements the
70
+ :py:class:`websocket.ServerListener` protocol.
71
+ :param supported_encodings: A list of encodings to advertise to clients.
72
+ :param services: A list of services to advertise to clients.
73
+ :param asset_handler: A callback function that returns the asset for a given URI, or None if
74
+ it doesn't exist.
75
+ :param context: The context to use for logging. If None, the global context is used.
76
+ :param session_id: An ID which allows the client to understand if the connection is a
77
+ re-connection or a new server instance. If None, then an ID is generated based on the
78
+ current time.
79
+ :param channel_filter: A `Callable` that determines whether a channel should be logged to.
80
+ Return `True` to log the channel, or `False` to skip it. By default, all channels
81
+ will be logged.
82
+ """
83
+ return _foxglove.start_server(
84
+ name=name,
85
+ host=host,
86
+ port=port,
87
+ capabilities=capabilities,
88
+ server_listener=server_listener,
89
+ supported_encodings=supported_encodings,
90
+ services=services,
91
+ asset_handler=asset_handler,
92
+ context=context,
93
+ session_id=session_id,
94
+ channel_filter=channel_filter,
95
+ )
96
+
97
+ def start_cloud_sink(
98
+ *,
99
+ listener: CloudSinkListener | None = None,
100
+ supported_encodings: list[str] | None = None,
101
+ context: Context | None = None,
102
+ session_id: str | None = None,
103
+ ) -> CloudSink:
104
+ """
105
+ Connect to Foxglove Agent for live visualization and teleop.
106
+
107
+ Foxglove Agent must be running on the same host for this to work.
108
+
109
+ :param capabilities: A list of capabilities to advertise to the agent.
110
+ :param listener: A Python object that implements the
111
+ :py:class:`cloud.CloudSinkListener` protocol.
112
+ :param supported_encodings: A list of encodings to advertise to the agent.
113
+ :param context: The context to use for logging. If None, the global context is used.
114
+ :param session_id: An ID which allows the agent to understand if the connection is a
115
+ re-connection or a new connection instance. If None, then an ID is generated based on
116
+ the current time.
117
+ """
118
+ return _foxglove.start_cloud_sink(
119
+ listener=listener,
120
+ supported_encodings=supported_encodings,
121
+ context=context,
122
+ session_id=session_id,
123
+ )
124
+
125
+ except ImportError:
126
+ pass
127
+
128
+
129
+ def set_log_level(level: int | str = "INFO") -> None:
130
+ """
131
+ Enable SDK logging.
132
+
133
+ This function will call logging.basicConfig() for convenience in scripts, but in general you
134
+ should configure logging yourself before calling this function:
135
+ https://docs.python.org/3/library/logging.html
136
+
137
+ :param level: The logging level to set. This accepts the same values as `logging.setLevel` and
138
+ defaults to "INFO". The SDK will not log at levels "CRITICAL" or higher.
139
+ """
140
+ # This will raise a ValueError for invalid levels if the user has not already configured
141
+ logging.basicConfig(level=level, format="%(asctime)s [%(levelname)s] %(message)s")
142
+
143
+ if isinstance(level, str):
144
+ level_map = (
145
+ logging.getLevelNamesMapping()
146
+ if hasattr(logging, "getLevelNamesMapping")
147
+ else _level_names()
40
148
  )
41
- logging.debug(f"SDK logging enabled ({level.upper()})")
42
- enable_log_forwarding(level)
149
+ try:
150
+ level = level_map[level]
151
+ except KeyError:
152
+ raise ValueError(f"Unknown log level: {level}")
153
+ else:
154
+ level = max(0, min(2**32 - 1, level))
155
+
156
+ _foxglove.enable_logging(level)
157
+
158
+
159
+ def _level_names() -> dict[str, int]:
160
+ # Fallback for Python <3.11; no support for custom levels
161
+ return {
162
+ "CRITICAL": logging.CRITICAL,
163
+ "FATAL": logging.FATAL,
164
+ "ERROR": logging.ERROR,
165
+ "WARN": logging.WARNING,
166
+ "WARNING": logging.WARNING,
167
+ "INFO": logging.INFO,
168
+ "DEBUG": logging.DEBUG,
169
+ "NOTSET": logging.NOTSET,
170
+ }
171
+
43
172
 
173
+ def init_notebook_buffer(context: Context | None = None) -> NotebookBuffer:
174
+ """
175
+ Create a NotebookBuffer object to manage data buffering and visualization in Jupyter
176
+ notebooks.
44
177
 
45
- def verbose_off() -> None:
46
- logging.debug("SDK logging disabled")
47
- disable_log_forwarding()
178
+ The NotebookBuffer object will buffer all data logged to the provided context. When you
179
+ are ready to visualize the data, you can call the :meth:`show` method to display an embedded
180
+ Foxglove visualization widget. The widget provides a fully-featured Foxglove interface
181
+ directly within your Jupyter notebook, allowing you to explore multi-modal robotics data
182
+ including 3D scenes, plots, images, and more.
48
183
 
184
+ Args:
185
+ context: The Context used to log the messages. If no Context is provided, the global
186
+ context will be used. Logged messages will be buffered.
49
187
 
50
- def log(topic: str, proto_msg: Message) -> None: ...
188
+ Returns:
189
+ NotebookBuffer: A NotebookBuffer object that can be used to manage the data buffering
190
+ and visualization.
191
+
192
+ Raises:
193
+ Exception: If the notebook extra package is not installed. Install it
194
+ with `pip install foxglove-sdk[notebook]`.
195
+
196
+ Note:
197
+ This function is only available when the `notebook` extra package
198
+ is installed. Install it with `pip install foxglove-sdk[notebook]`.
199
+
200
+ Example:
201
+ >>> import foxglove
202
+ >>>
203
+ >>> # Create a basic viewer using the default context
204
+ >>> nb_buffer = foxglove.init_notebook_buffer()
205
+ >>>
206
+ >>> # Or use a specific context
207
+ >>> nb_buffer = foxglove.init_notebook_buffer(context=my_ctx)
208
+ >>>
209
+ >>> # ... log data as usual ...
210
+ >>>
211
+ >>> # Display the widget in the notebook
212
+ >>> nb_buffer.show()
213
+ """
214
+ try:
215
+ from .notebook.notebook_buffer import NotebookBuffer
216
+
217
+ except ImportError:
218
+ raise Exception(
219
+ "NotebookBuffer is not installed. "
220
+ 'Please install it with `pip install "foxglove-sdk[notebook]"`'
221
+ )
222
+
223
+ return NotebookBuffer(context=context)
51
224
 
52
225
 
53
226
  __all__ = [
54
227
  "Channel",
55
- "start_server",
56
- "record_file",
57
- "Encoder",
58
- "ProtobufEncoder",
59
- "JsonEncoder",
60
- "verbose_on",
61
- "verbose_off",
228
+ "ChannelDescriptor",
229
+ "Context",
230
+ "MCAPWriter",
231
+ "Schema",
232
+ "SinkChannelFilter",
233
+ "CloudSink",
234
+ "CloudSinkListener",
235
+ "start_cloud_sink",
62
236
  "log",
237
+ "open_mcap",
238
+ "set_log_level",
239
+ "start_server",
240
+ "init_notebook_buffer",
63
241
  ]
@@ -0,0 +1,210 @@
1
+ from pathlib import Path
2
+ from typing import Any, Callable
3
+
4
+ from foxglove.websocket import AssetHandler
5
+
6
+ from .cloud import CloudSink
7
+ from .mcap import MCAPWriteOptions, MCAPWriter
8
+ from .websocket import Capability, Service, WebSocketServer
9
+
10
+ class BaseChannel:
11
+ """
12
+ A channel for logging messages.
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ topic: str,
18
+ message_encoding: str,
19
+ schema: "Schema" | None = None,
20
+ metadata: dict[str, str] | None = None,
21
+ ) -> None: ...
22
+ def id(self) -> int:
23
+ """The unique ID of the channel"""
24
+ ...
25
+
26
+ def topic(self) -> str:
27
+ """The topic name of the channel"""
28
+ ...
29
+
30
+ @property
31
+ def message_encoding(self) -> str:
32
+ """The message encoding for the channel"""
33
+ ...
34
+
35
+ def metadata(self) -> dict[str, str]:
36
+ """
37
+ Returns a copy of the channel's metadata.
38
+
39
+ Note that changes made to the returned dictionary will not be applied to
40
+ the channel's metadata.
41
+ """
42
+ ...
43
+
44
+ def schema(self) -> "Schema" | None:
45
+ """
46
+ Returns a copy of the channel's schema.
47
+
48
+ Note that changes made to the returned object will not be applied to
49
+ the channel's schema.
50
+ """
51
+ ...
52
+
53
+ def schema_name(self) -> str | None:
54
+ """The name of the schema for the channel"""
55
+ ...
56
+
57
+ def has_sinks(self) -> bool:
58
+ """Returns true if at least one sink is subscribed to this channel"""
59
+ ...
60
+
61
+ def log(
62
+ self,
63
+ msg: bytes,
64
+ log_time: int | None = None,
65
+ sink_id: int | None = None,
66
+ ) -> None:
67
+ """
68
+ Log a message to the channel.
69
+
70
+ :param msg: The message to log.
71
+ :param log_time: The optional time the message was logged.
72
+ :param sink_id: The sink ID to log the message to. If not provided, the message will be
73
+ sent to all sinks.
74
+ """
75
+ ...
76
+
77
+ def close(self) -> None: ...
78
+
79
+ class Schema:
80
+ """
81
+ A schema for a message or service call.
82
+ """
83
+
84
+ name: str
85
+ encoding: str
86
+ data: bytes
87
+
88
+ def __init__(
89
+ self,
90
+ *,
91
+ name: str,
92
+ encoding: str,
93
+ data: bytes,
94
+ ) -> None: ...
95
+
96
+ class Context:
97
+ """
98
+ A context for logging messages.
99
+
100
+ A context is the binding between channels and sinks. By default, the SDK will use a single
101
+ global context for logging, but you can create multiple contexts in order to log to different
102
+ topics to different sinks or servers. To do so, associate the context by passing it to the
103
+ channel constructor and to :py:func:`open_mcap` or :py:func:`start_server`.
104
+ """
105
+
106
+ def __init__(self) -> None: ...
107
+ def _create_channel(
108
+ self,
109
+ topic: str,
110
+ message_encoding: str,
111
+ schema: Schema | None = None,
112
+ metadata: list[tuple[str, str]] | None = None,
113
+ ) -> "BaseChannel":
114
+ """
115
+ Instead of calling this method, pass a context to a channel constructor.
116
+ """
117
+ ...
118
+
119
+ @staticmethod
120
+ def default() -> "Context":
121
+ """
122
+ Returns the default context.
123
+ """
124
+ ...
125
+
126
+ class ChannelDescriptor:
127
+ """
128
+ Information about a channel
129
+ """
130
+
131
+ id: int
132
+ topic: str
133
+ message_encoding: str
134
+ metadata: dict[str, str]
135
+ schema: "Schema" | None
136
+
137
+ SinkChannelFilter = Callable[[ChannelDescriptor], bool]
138
+
139
+ def start_server(
140
+ *,
141
+ name: str | None = None,
142
+ host: str | None = "127.0.0.1",
143
+ port: int | None = 8765,
144
+ capabilities: list[Capability] | None = None,
145
+ server_listener: Any = None,
146
+ supported_encodings: list[str] | None = None,
147
+ services: list[Service] | None = None,
148
+ asset_handler: AssetHandler | None = None,
149
+ context: Context | None = None,
150
+ session_id: str | None = None,
151
+ channel_filter: SinkChannelFilter | None = None,
152
+ ) -> WebSocketServer:
153
+ """
154
+ Start a websocket server for live visualization.
155
+ """
156
+ ...
157
+
158
+ def start_cloud_sink(
159
+ *,
160
+ listener: Any = None,
161
+ supported_encodings: list[str] | None = None,
162
+ context: Context | None = None,
163
+ session_id: str | None = None,
164
+ ) -> CloudSink:
165
+ """
166
+ Connect to Foxglove Agent for remote visualization and teleop.
167
+ """
168
+ ...
169
+
170
+ def enable_logging(level: int) -> None:
171
+ """
172
+ Forward SDK logs to python's logging facility.
173
+ """
174
+ ...
175
+
176
+ def disable_logging() -> None:
177
+ """
178
+ Stop forwarding SDK logs.
179
+ """
180
+ ...
181
+
182
+ def shutdown() -> None:
183
+ """
184
+ Shutdown the running websocket server.
185
+ """
186
+ ...
187
+
188
+ def open_mcap(
189
+ path: str | Path,
190
+ *,
191
+ allow_overwrite: bool = False,
192
+ context: Context | None = None,
193
+ channel_filter: SinkChannelFilter | None = None,
194
+ writer_options: MCAPWriteOptions | None = None,
195
+ ) -> MCAPWriter:
196
+ """
197
+ Creates a new MCAP file for recording.
198
+
199
+ If a context is provided, the MCAP file will be associated with that context. Otherwise, the
200
+ global context will be used.
201
+
202
+ You must close the writer with close() or the with statement to ensure the file is correctly finished.
203
+ """
204
+ ...
205
+
206
+ def get_channel_for_topic(topic: str) -> BaseChannel | None:
207
+ """
208
+ Get a previously-registered channel.
209
+ """
210
+ ...