foxglove-sdk 0.15.1__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.15.2__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
- foxglove/__init__.py +58 -0
- foxglove/_foxglove_py.cpython-312-aarch64-linux-gnu.so +0 -0
- foxglove/notebook/__init__.py +0 -0
- foxglove/notebook/foxglove_widget.py +100 -0
- foxglove/notebook/notebook_buffer.py +114 -0
- foxglove/notebook/static/widget.js +1 -0
- {foxglove_sdk-0.15.1.dist-info → foxglove_sdk-0.15.2.dist-info}/METADATA +5 -1
- {foxglove_sdk-0.15.1.dist-info → foxglove_sdk-0.15.2.dist-info}/RECORD +9 -5
- {foxglove_sdk-0.15.1.dist-info → foxglove_sdk-0.15.2.dist-info}/WHEEL +0 -0
foxglove/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import atexit
|
|
11
11
|
import logging
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
12
13
|
|
|
13
14
|
from . import _foxglove_py as _foxglove
|
|
14
15
|
|
|
@@ -25,6 +26,9 @@ from .channel import Channel, log
|
|
|
25
26
|
# Deprecated. Use foxglove.mcap.MCAPWriter instead.
|
|
26
27
|
from .mcap import MCAPWriter
|
|
27
28
|
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from .notebook.notebook_buffer import NotebookBuffer
|
|
31
|
+
|
|
28
32
|
atexit.register(_foxglove.shutdown)
|
|
29
33
|
|
|
30
34
|
|
|
@@ -166,6 +170,59 @@ def _level_names() -> dict[str, int]:
|
|
|
166
170
|
}
|
|
167
171
|
|
|
168
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.
|
|
177
|
+
|
|
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.
|
|
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.
|
|
187
|
+
|
|
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)
|
|
224
|
+
|
|
225
|
+
|
|
169
226
|
__all__ = [
|
|
170
227
|
"Channel",
|
|
171
228
|
"ChannelDescriptor",
|
|
@@ -180,4 +237,5 @@ __all__ = [
|
|
|
180
237
|
"open_mcap",
|
|
181
238
|
"set_log_level",
|
|
182
239
|
"start_server",
|
|
240
|
+
"init_notebook_buffer",
|
|
183
241
|
]
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
5
|
+
|
|
6
|
+
import anywidget
|
|
7
|
+
import traitlets
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .notebook_buffer import NotebookBuffer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FoxgloveWidget(anywidget.AnyWidget):
|
|
14
|
+
"""
|
|
15
|
+
A widget that displays a Foxglove viewer in a notebook.
|
|
16
|
+
|
|
17
|
+
:param buffer: The NotebookBuffer object that contains the data to display in the widget.
|
|
18
|
+
:param layout_storage_key: The storage key of the layout to use for the widget.
|
|
19
|
+
:param width: The width of the widget. Defaults to "full".
|
|
20
|
+
:param height: The height of the widget in pixels. Defaults to 500.
|
|
21
|
+
:param src: The source URL of the Foxglove viewer. Defaults to "https://embed.foxglove.dev/".
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
|
|
25
|
+
width = traitlets.Union(
|
|
26
|
+
[traitlets.Int(), traitlets.Enum(values=["full"])], default_value="full"
|
|
27
|
+
).tag(sync=True)
|
|
28
|
+
height = traitlets.Int(default_value=500).tag(sync=True)
|
|
29
|
+
src = traitlets.Unicode(default_value=None, allow_none=True).tag(sync=True)
|
|
30
|
+
_layout_params = traitlets.Dict(
|
|
31
|
+
per_key_traits={
|
|
32
|
+
"storage_key": traitlets.Unicode(),
|
|
33
|
+
"opaque_layout": traitlets.Dict(allow_none=True, default_value=None),
|
|
34
|
+
"force": traitlets.Bool(False),
|
|
35
|
+
},
|
|
36
|
+
allow_none=True,
|
|
37
|
+
default_value=None,
|
|
38
|
+
).tag(sync=True)
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
buffer: NotebookBuffer,
|
|
43
|
+
layout_storage_key: str,
|
|
44
|
+
width: int | Literal["full"] | None = None,
|
|
45
|
+
height: int | None = None,
|
|
46
|
+
src: str | None = None,
|
|
47
|
+
**kwargs: Any,
|
|
48
|
+
):
|
|
49
|
+
super().__init__(**kwargs)
|
|
50
|
+
if width is not None:
|
|
51
|
+
self.width = width
|
|
52
|
+
else:
|
|
53
|
+
self.width = "full"
|
|
54
|
+
if height is not None:
|
|
55
|
+
self.height = height
|
|
56
|
+
if src is not None:
|
|
57
|
+
self.src = src
|
|
58
|
+
|
|
59
|
+
self.select_layout(layout_storage_key, **kwargs)
|
|
60
|
+
|
|
61
|
+
# Callback to get the data to display in the widget
|
|
62
|
+
self._buffer = buffer
|
|
63
|
+
# Keep track of when the widget is ready to receive data
|
|
64
|
+
self._ready = False
|
|
65
|
+
# Pending data to be sent when the widget is ready
|
|
66
|
+
self._pending_data: list[bytes] = []
|
|
67
|
+
self.on_msg(self._handle_custom_msg)
|
|
68
|
+
self.refresh()
|
|
69
|
+
|
|
70
|
+
def select_layout(self, storage_key: str, **kwargs: Any) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Select a layout in the Foxglove viewer.
|
|
73
|
+
"""
|
|
74
|
+
opaque_layout = kwargs.get("opaque_layout", None)
|
|
75
|
+
force_layout = kwargs.get("force_layout", False)
|
|
76
|
+
|
|
77
|
+
self._layout_params = {
|
|
78
|
+
"storage_key": storage_key,
|
|
79
|
+
"opaque_layout": opaque_layout if isinstance(opaque_layout, dict) else None,
|
|
80
|
+
"force": force_layout,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def refresh(self) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Refresh the widget by getting the data from the callback function and sending it
|
|
86
|
+
to the widget.
|
|
87
|
+
"""
|
|
88
|
+
data = self._buffer.get_data()
|
|
89
|
+
if not self._ready:
|
|
90
|
+
self._pending_data = data
|
|
91
|
+
else:
|
|
92
|
+
self.send({"type": "update-data"}, data)
|
|
93
|
+
|
|
94
|
+
def _handle_custom_msg(self, msg: dict, buffers: list[bytes]) -> None:
|
|
95
|
+
if msg["type"] == "ready":
|
|
96
|
+
self._ready = True
|
|
97
|
+
|
|
98
|
+
if len(self._pending_data) > 0:
|
|
99
|
+
self.send({"type": "update-data"}, self._pending_data)
|
|
100
|
+
self._pending_data = []
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
from tempfile import TemporaryDirectory
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from mcap.reader import make_reader
|
|
9
|
+
|
|
10
|
+
from .._foxglove_py import Context, open_mcap
|
|
11
|
+
from .foxglove_widget import FoxgloveWidget
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NotebookBuffer:
|
|
15
|
+
"""
|
|
16
|
+
A data buffer to collect and manage messages and visualization in Jupyter notebooks.
|
|
17
|
+
|
|
18
|
+
The NotebookBuffer object will buffer all data logged to the provided context. When you
|
|
19
|
+
are ready to visualize the data, you can call the :meth:`show` method to display an embedded
|
|
20
|
+
Foxglove visualization widget. The widget provides a fully-featured Foxglove interface
|
|
21
|
+
directly within your Jupyter notebook, allowing you to explore multi-modal robotics data
|
|
22
|
+
including 3D scenes, plots, images, and more.
|
|
23
|
+
|
|
24
|
+
:param context: The Context used to log the messages. If no Context is provided, the global
|
|
25
|
+
context will be used. Logged messages will be buffered.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, context: Context | None = None):
|
|
29
|
+
"""
|
|
30
|
+
Initialize a new NotebookBuffer for collecting logged messages.
|
|
31
|
+
"""
|
|
32
|
+
# We need to keep the temporary directory alive until the writer is closed
|
|
33
|
+
self._temp_directory = TemporaryDirectory()
|
|
34
|
+
self._context = context
|
|
35
|
+
self._files: list[str] = []
|
|
36
|
+
self._create_writer()
|
|
37
|
+
|
|
38
|
+
def show(
|
|
39
|
+
self,
|
|
40
|
+
layout_storage_key: str,
|
|
41
|
+
width: int | Literal["full"] | None = None,
|
|
42
|
+
height: int | None = None,
|
|
43
|
+
src: str | None = None,
|
|
44
|
+
**kwargs: Any,
|
|
45
|
+
) -> FoxgloveWidget:
|
|
46
|
+
"""
|
|
47
|
+
Show the Foxglove viewer. Call this method as the last step of a notebook cell
|
|
48
|
+
to display the viewer.
|
|
49
|
+
"""
|
|
50
|
+
widget = FoxgloveWidget(
|
|
51
|
+
buffer=self,
|
|
52
|
+
width=width,
|
|
53
|
+
height=height,
|
|
54
|
+
src=src,
|
|
55
|
+
layout_storage_key=layout_storage_key,
|
|
56
|
+
**kwargs,
|
|
57
|
+
)
|
|
58
|
+
return widget
|
|
59
|
+
|
|
60
|
+
def clear(self) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Clear the buffered data.
|
|
63
|
+
"""
|
|
64
|
+
self._writer.close()
|
|
65
|
+
# Delete the temporary directory and all its contents
|
|
66
|
+
self._temp_directory.cleanup()
|
|
67
|
+
# Reset files list
|
|
68
|
+
self._files = []
|
|
69
|
+
# Create a new temporary directory
|
|
70
|
+
self._temp_directory = TemporaryDirectory()
|
|
71
|
+
self._create_writer()
|
|
72
|
+
|
|
73
|
+
def get_data(self) -> list[bytes]:
|
|
74
|
+
"""
|
|
75
|
+
Retrieve all collected data.
|
|
76
|
+
"""
|
|
77
|
+
# close the current writer
|
|
78
|
+
self._writer.close()
|
|
79
|
+
|
|
80
|
+
if len(self._files) > 1:
|
|
81
|
+
if is_mcap_empty(self._files[-1]):
|
|
82
|
+
# If the last file is empty, remove the last file since it won't add any new data
|
|
83
|
+
# to the buffer
|
|
84
|
+
os.remove(self._files[-1])
|
|
85
|
+
self._files.pop()
|
|
86
|
+
elif is_mcap_empty(self._files[0]):
|
|
87
|
+
# If the first file is empty, remove the first file since it won't add any new data
|
|
88
|
+
# to the buffer
|
|
89
|
+
os.remove(self._files[0])
|
|
90
|
+
self._files.pop(0)
|
|
91
|
+
|
|
92
|
+
# read the content of the files
|
|
93
|
+
contents: list[bytes] = []
|
|
94
|
+
for file_name in self._files:
|
|
95
|
+
with open(file_name, "rb") as f_read:
|
|
96
|
+
contents.append(f_read.read())
|
|
97
|
+
|
|
98
|
+
self._create_writer()
|
|
99
|
+
|
|
100
|
+
return contents
|
|
101
|
+
|
|
102
|
+
def _create_writer(self) -> None:
|
|
103
|
+
random_id = uuid.uuid4().hex[:8]
|
|
104
|
+
file_name = f"{self._temp_directory.name}/log-{random_id}.mcap"
|
|
105
|
+
self._files.append(file_name)
|
|
106
|
+
self._writer = open_mcap(path=file_name, context=self._context)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_mcap_empty(file_name: str) -> bool:
|
|
110
|
+
with open(file_name, "rb") as f_read:
|
|
111
|
+
iter = make_reader(f_read).iter_messages()
|
|
112
|
+
is_empty = next(iter, None) is None
|
|
113
|
+
|
|
114
|
+
return is_empty
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var y=EventTarget,c="https://embed.foxglove.dev/",p="foxglove.default-layout",h=class extends y{#e;#s;#n;#i=!1;#t={dataSource:void 0,layout:void 0,selectLayout:void 0,extension:[]};#a=!1;constructor(e){super();let{parent:a,src:i,orgSlug:o,initialDataSource:s,initialLayout:n,initialLayoutParams:r,initialExtensions:l,colorScheme:u="auto"}=e;this.#n=o;let d=i??c;try{this.#s=new URL(d)}catch{throw new Error(`[FoxgloveViewer] Invalid server URL: ${d}`)}window.addEventListener("message",this.#l),s&&this.setDataSource(s),n!=null&&r==null&&this.setLayoutData(n),r!=null&&this.selectLayout(r),l&&this.installExtensions(l),this.#e=document.createElement("iframe"),this.#e.src=d,this.#e.title="Foxglove",this.#e.allow="cross-origin-isolated",this.#e.style.width="100%",this.#e.style.height="100%",this.#e.style.border="none",this.setColorScheme(u),a.appendChild(this.#e)}setDataSource(e){this.#o({type:"set-data-source",payload:e})}selectLayout(e){this.#o({type:"select-layout",payload:e})}setLayoutData(e){this.selectLayout({storageKey:p,opaqueLayout:e,force:!0})}installExtensions(e){this.#o({type:"install-extension",payload:e})}isReady(){return this.#i}destroy(){this.#a=!0,this.#e.remove(),window.removeEventListener("message",this.#l)}isDestroyed(){return this.#a}setColorScheme(e){this.#e.style.colorScheme=e==="auto"?"normal":e}#o(e){e.type==="install-extension"?this.#t.extension.push(e):e.type==="set-data-source"?this.#t.dataSource=e:e.type==="set-layout"&&!this.#t.selectLayout?this.#t.layout=e:e.type==="select-layout"&&(this.#t.layout=void 0,this.#t.selectLayout=e),this.#i&&this.#r(e)}#r(e){if(this.#a){console.warn("[FoxgloveViewer] Unable to post command. Frame has been destroyed.");return}f(this.#e.contentWindow,"Invariant: iframe should be loaded."),this.#e.contentWindow.postMessage(e,this.#s.href)}#l=e=>{let a=new URL(e.origin);if(!(e.source!==this.#e.contentWindow||a.href!==this.#s.href)){if(this.#a){console.warn("[FoxgloveViewer] Unable to handle message. Frame has been destroyed.");return}switch(e.data.type){case"foxglove-origin-request":this.#r({type:"origin-ack"});break;case"foxglove-handshake-request":{this.#i=!0,this.#r({type:"handshake-ack",payload:{orgSlug:this.#n,initialDataSource:this.#t.dataSource?.payload,initialLayoutParams:this.#t.selectLayout?.payload,initialExtensions:this.#t.extension.flatMap(i=>i.payload)}});break}case"foxglove-handshake-complete":{this.dispatchEvent(new Event("ready"));break}case"foxglove-error":{this.dispatchEvent(new CustomEvent("error",{detail:e.data.payload}));break}default:{console.warn("[FoxgloveViewer] Unhandled message type:",e.data);break}}}}};function f(t,e="no additional info provided"){if(!t)throw new Error("Assertion Error: "+e)}function g({model:t,el:e}){let a=document.createElement("div"),i=t.get("_layout_params"),o=new h({parent:a,src:t.get("src"),orgSlug:void 0,initialLayoutParams:i?{storageKey:i.storage_key,opaqueLayout:i.opaque_layout,force:i.force}:void 0});o.addEventListener("ready",()=>{t.send({type:"ready"})}),t.on("msg:custom",(s,n)=>{if(s.type==="update-data"){let r=n.map((l,u)=>new File([l.buffer],`data-${u}.mcap`));o.setDataSource({type:"file",file:r})}}),a.style.width=t.get("width")==="full"?"100%":`${t.get("width")}px`,a.style.height=`${t.get("height")}px`,t.on("change:width",()=>{a.style.width=t.get("width")==="full"?"100%":`${t.get("width")}px`}),t.on("change:height",()=>{a.style.height=`${t.get("height")}px`}),t.on("change:_layout_params",()=>{let s=t.get("_layout_params");s&&o.selectLayout({storageKey:s.storage_key,opaqueLayout:s.opaque_layout,force:s.force})}),e.appendChild(a)}var _={render:g};export{_ as default};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxglove-sdk
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.2
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
|
+
Requires-Dist: anywidget ; extra == 'notebook'
|
|
7
|
+
Requires-Dist: mcap ; extra == 'notebook'
|
|
8
|
+
Requires-Dist: traitlets ; extra == 'notebook'
|
|
9
|
+
Provides-Extra: notebook
|
|
6
10
|
Summary: Foxglove Python SDK
|
|
7
11
|
Author-email: Foxglove <support@foxglove.dev>
|
|
8
12
|
License-Expression: MIT
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
foxglove/__init__.py,sha256=
|
|
2
|
-
foxglove/_foxglove_py.cpython-312-aarch64-linux-gnu.so,sha256=
|
|
1
|
+
foxglove/__init__.py,sha256=07Fz3ZZJdqsXRl0uKpIOID8ysa1HpQ741pQlvz1yA3I,8222
|
|
2
|
+
foxglove/_foxglove_py.cpython-312-aarch64-linux-gnu.so,sha256=5PXYSPgFrxuJdMPN9Ah8oDBjYavY47H3YMYN5vAvL0o,6583544
|
|
3
3
|
foxglove/_foxglove_py/__init__.pyi,sha256=lh2gtNThduHDOIO1pQ7NaGnjAPr6s_VlbdBMRKM2dns,5201
|
|
4
4
|
foxglove/_foxglove_py/channels.pyi,sha256=c3WXhK5FfeCJ1aDGB-AsayIossnC8v8qFij0XWl1AEA,67327
|
|
5
5
|
foxglove/_foxglove_py/cloud.pyi,sha256=9Hqj7b9S2CTiWeWOIqaAw3GSmR-cGoSL4R7fi5WI-QA,255
|
|
@@ -12,6 +12,10 @@ foxglove/channel.py,sha256=MRiiOm09ZNATOvVCFuvn19KB9HkVCtsDTJYiqE7LQlA,8452
|
|
|
12
12
|
foxglove/channels/__init__.py,sha256=qTr5-HKlr4e386onX9OiYBPWUsS1n2vO23PncL0TiPY,2398
|
|
13
13
|
foxglove/cloud.py,sha256=eOHV8ZCnut59uBLiw1c5MiwbIALeVwPzxo2Yb-2jbII,1942
|
|
14
14
|
foxglove/mcap.py,sha256=LR9TSyRlDWuHZpXR8iglmDp-S-4BRqgmvTOiUKHwlsA,200
|
|
15
|
+
foxglove/notebook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
foxglove/notebook/foxglove_widget.py,sha256=mOXElZLZSIQfvzbZfMd2jtvmWA-H4Z8j7fa39Vwns34,3418
|
|
17
|
+
foxglove/notebook/notebook_buffer.py,sha256=ooQIb8xcyNeuCoRm7CL0Uhnh9VPcd41YMUNYRu15FYg,3801
|
|
18
|
+
foxglove/notebook/static/widget.js,sha256=LAmo9HlxLgNAgDSROag8_3KWFLVc7gcE8EGU1ZaFjMk,3639
|
|
15
19
|
foxglove/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
20
|
foxglove/schemas/__init__.py,sha256=bqYBLc0HXRe-BMRP3VnF9IUB2yKYsvku3pZnulss3GY,3232
|
|
17
21
|
foxglove/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -24,6 +28,6 @@ foxglove/tests/test_schemas.py,sha256=4empQg8gqS7MD63ibU3kdQgJUSsUpnNQQWQ7oAf-4x
|
|
|
24
28
|
foxglove/tests/test_server.py,sha256=PHraaIt4m27MXn1UveV6BRpzYCMFOQfjQD4d4ZHRjIY,2893
|
|
25
29
|
foxglove/tests/test_time.py,sha256=By_sM5r87s9iu4Df12r6p9DJrzTeZSLys3XGUGhvUps,4661
|
|
26
30
|
foxglove/websocket.py,sha256=oPcI2dttjr-RdL7z30MKEEF1cswyoDu_VetEaxAiwtQ,5812
|
|
27
|
-
foxglove_sdk-0.15.
|
|
28
|
-
foxglove_sdk-0.15.
|
|
29
|
-
foxglove_sdk-0.15.
|
|
31
|
+
foxglove_sdk-0.15.2.dist-info/METADATA,sha256=Iz7-5YqTaMpdYHLi-lpy-qzKOPfvqm38pHrDUC2sn_E,1714
|
|
32
|
+
foxglove_sdk-0.15.2.dist-info/WHEEL,sha256=Zh2Nm9_KPw53ft3ii58WjPmMCU4Yjeq6NqKuwRR6qU4,131
|
|
33
|
+
foxglove_sdk-0.15.2.dist-info/RECORD,,
|
|
File without changes
|