bluesky-tiled-plugins 2.0.0__py3-none-any.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,11 @@
1
+ from .clients.bluesky_event_stream import BlueskyEventStream
2
+ from .clients.bluesky_run import BlueskyRun
3
+ from .clients.catalog_of_bluesky_runs import CatalogOfBlueskyRuns
4
+ from .writing.tiled_writer import TiledWriter
5
+
6
+ __all__ = [
7
+ "BlueskyEventStream",
8
+ "BlueskyRun",
9
+ "CatalogOfBlueskyRuns",
10
+ "TiledWriter",
11
+ ]
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '2.0.0'
32
+ __version_tuple__ = version_tuple = (2, 0, 0)
33
+
34
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,9 @@
1
+ # There are methods that IPython will try to call.
2
+ # We special-case them because we want to avoid the getattr
3
+ # resulting in an unnecessary network hit just to raise
4
+ # AttributeError.
5
+
6
+ IPYTHON_METHODS = {
7
+ "_ipython_canary_method_should_not_exist_",
8
+ "_repr_mimebundle__ipython_display_",
9
+ }
@@ -0,0 +1,387 @@
1
+ import functools
2
+ import keyword
3
+ import warnings
4
+ from collections import defaultdict
5
+
6
+ import numpy
7
+ import xarray
8
+ from tiled.client.composite import CompositeClient
9
+ from tiled.client.container import DEFAULT_STRUCTURE_CLIENT_DISPATCH, Container
10
+ from tiled.utils import DictView, OneShotCachedMap, Sentinel, node_repr
11
+
12
+ from ._common import IPYTHON_METHODS
13
+
14
+ DATAVALUES = Sentinel("DATAVALUES")
15
+ TIMESTAMPS = Sentinel("TIMESTAMPS")
16
+
17
+
18
+ class BlueskyEventStream(Container):
19
+ _ipython_display_ = None
20
+ _repr_mimebundle__ = None
21
+
22
+ def __new__(cls, context, *, item, structure_clients, **kwargs):
23
+ # When inheriting from BlueskyEventStream, return the class itself
24
+ if cls is not BlueskyEventStream:
25
+ return super().__new__(cls)
26
+
27
+ # Set the version based on the specs
28
+ _cls = BlueskyEventStreamV3 if cls._is_sql(item) else BlueskyEventStreamV2Mongo
29
+ return _cls(context, item=item, structure_clients=structure_clients, **kwargs)
30
+
31
+ @staticmethod
32
+ def _is_sql(item):
33
+ for spec in item["attributes"]["specs"]:
34
+ if spec["name"] == "BlueskyEventStream":
35
+ if spec["version"].startswith("3."):
36
+ return True
37
+ return False
38
+
39
+
40
+ class BlueskyEventStreamV2Mongo(BlueskyEventStream):
41
+ """
42
+ This encapsulates the data and metadata for one 'stream' in a Bluesky 'run'.
43
+
44
+ This adds for bluesky-specific conveniences to the standard client Container.
45
+ """
46
+
47
+ def __repr__(self):
48
+ stream_name = self.metadata.get("stream_name") or self.item["id"]
49
+ return f"<BlueskyEventStream {set(self)!r} stream_name={stream_name!r}>"
50
+
51
+ @property
52
+ def descriptors(self):
53
+ return self.metadata["descriptors"]
54
+
55
+ @property
56
+ def _descriptors(self):
57
+ # For backward-compatibility.
58
+ # We do not normally worry about backward-compatibility of _ methods, but
59
+ # for a time databroker.v2 *only* have _descriptors and not descriptors,
60
+ # and I know there is useer code that relies on that.
61
+ warnings.warn("Use `.descriptors` instead of `._descriptors`.", stacklevel=2)
62
+ return self.descriptors
63
+
64
+ def __getattr__(self, key):
65
+ """
66
+ Let run.X be a synonym for run['X'] unless run.X already exists.
67
+
68
+ This behavior is the same as with pandas.DataFrame.
69
+ """
70
+ # The wisdom of this kind of "magic" is arguable, but we
71
+ # need to support it for backward-compatibility reasons.
72
+ if key in IPYTHON_METHODS:
73
+ raise AttributeError(key)
74
+ if key in self:
75
+ return self[key]
76
+ raise AttributeError(key)
77
+
78
+ def __dir__(self):
79
+ # Build a list of entries that are valid attribute names
80
+ # and add them to __dir__ so that they tab-complete.
81
+ tab_completable_entries = [
82
+ entry
83
+ for entry in self
84
+ if (entry.isidentifier() and (not keyword.iskeyword(entry)))
85
+ ]
86
+ return super().__dir__() + tab_completable_entries
87
+
88
+ def read(self, *args, **kwargs):
89
+ """
90
+ Shortcut for reading the 'data' (as opposed to timestamps or config).
91
+
92
+ That is:
93
+
94
+ >>> stream.read(...)
95
+
96
+ is equivalent to
97
+
98
+ >>> stream["data"].read(...)
99
+ """
100
+ return self["data"].read(*args, **kwargs)
101
+
102
+ def to_dask(self):
103
+ warnings.warn(
104
+ """Do not use this method.
105
+ Instead, set dask or when first creating the client, as in
106
+
107
+ >>> catalog = from_uri("...", "dask")
108
+
109
+ and then read() will return dask objects.""",
110
+ DeprecationWarning,
111
+ stacklevel=2,
112
+ )
113
+ return self.new_variation(
114
+ structure_clients=DEFAULT_STRUCTURE_CLIENT_DISPATCH["dask"]
115
+ ).read()
116
+
117
+
118
+ class BlueskyEventStreamV2SQL(OneShotCachedMap):
119
+ def __init__(self, internal_dict, metadata=None):
120
+ super().__init__(internal_dict)
121
+ self.metadata = metadata or {}
122
+
123
+ def __repr__(self):
124
+ stream_name = self.metadata.get("stream_name")
125
+ return f"<BlueskyEventStream {set(self)!r} stream_name={stream_name!r}>"
126
+
127
+ def __getitem__(self, key):
128
+ if "/" in key:
129
+ key, rest = key.split("/", 1)
130
+ return self[key][rest]
131
+
132
+ return super().__getitem__(key)
133
+
134
+ @classmethod
135
+ def from_stream_client(cls, stream_client, metadata=None):
136
+ stream_parts = set(stream_client.base.keys())
137
+ data_keys = [k for k in stream_parts if k != "internal"]
138
+ ts_keys = ["time"]
139
+ if "internal" in stream_parts:
140
+ internal_cols = stream_client.base["internal"].columns
141
+ data_keys += [
142
+ col
143
+ for col in internal_cols
144
+ if col != "seq_num" and not col.startswith("ts_")
145
+ ]
146
+ ts_keys += [col for col in internal_cols if col.startswith("ts_")]
147
+
148
+ # Construct clients for the configuration data
149
+ cf_vals, cf_time = defaultdict(dict), defaultdict(dict)
150
+ if config := stream_client.metadata.get("configuration", {}):
151
+ updates = stream_client.metadata.get("_config_updates", [])
152
+ for obj_name, obj in config.items():
153
+ for key in obj["data"].keys():
154
+ _vs, _ts = [obj["data"][key]], [obj["timestamps"][key]]
155
+
156
+ # Add values and timestamps from config_updates
157
+ for upd in updates:
158
+ if upd_config := upd.get("configuration", {}):
159
+ _vs.append(upd_config.get("data", {}).get(key))
160
+ _ts.append(upd_config.get("timestamps", {}).get(key))
161
+
162
+ cf_vals[obj_name][key] = VirtualArrayClient(_vs)
163
+ cf_time[obj_name][key] = VirtualArrayClient(_ts)
164
+
165
+ internal_dict = {
166
+ "data": lambda: CompositeSubsetClient(stream_client, data_keys),
167
+ "timestamps": lambda: CompositeSubsetClient(stream_client, ts_keys),
168
+ "config": lambda: VirtualContainer(
169
+ {k: ConfigDatasetClient(v) for k, v in cf_vals.items()}
170
+ ),
171
+ "config_timestamps": lambda: VirtualContainer(
172
+ {k: ConfigDatasetClient(v) for k, v in cf_time.items()}
173
+ ),
174
+ }
175
+
176
+ # Construct the metadata
177
+ metadata = {
178
+ "descriptors": [],
179
+ "stream_name": stream_client.item["id"],
180
+ **stream_client.metadata,
181
+ **(metadata or {}),
182
+ }
183
+
184
+ return cls(internal_dict, metadata=metadata)
185
+
186
+ @functools.cached_property
187
+ def descriptors(self):
188
+ # Go back to the BlueskyRun node and request the documents
189
+ # the path is: bs_run_node/streams/current_stream (old) or bs_run_node/current_stream (new)
190
+ bs_run_node = self["data"].parent
191
+ if bs_run_node.item["id"] == "streams" and (
192
+ "BlueskyRun" not in {s.name for s in bs_run_node.specs}
193
+ ):
194
+ # The parent is the old "streams" node, go up one more level
195
+ bs_run_node = bs_run_node.parent
196
+ stream_name = self.metadata.get("stream_name") or self["data"].item["id"]
197
+ return [
198
+ doc
199
+ for name, doc in bs_run_node.documents()
200
+ if name == "descriptor" and doc["name"] == stream_name
201
+ ]
202
+
203
+ @property
204
+ def _descriptors(self):
205
+ # For backward-compatibility.
206
+ # We do not normally worry about backward-compatibility of _ methods, but
207
+ # for a time databroker.v2 *only* have _descriptors and not descriptors,
208
+ # and I know there is useer code that relies on that.
209
+ warnings.warn("Use `.descriptors` instead of `._descriptors`.", stacklevel=2)
210
+ return self.descriptors
211
+
212
+ def __getattr__(self, key):
213
+ """
214
+ Let run.X be a synonym for run['X'] unless run.X already exists.
215
+
216
+ This behavior is the same as with pandas.DataFrame.
217
+ """
218
+ # The wisdom of this kind of "magic" is arguable, but we
219
+ # need to support it for backward-compatibility reasons.
220
+ if key in IPYTHON_METHODS:
221
+ raise AttributeError(key)
222
+ if key in self:
223
+ return self[key]
224
+ raise AttributeError(key)
225
+
226
+ def read(self, *args, **kwargs):
227
+ """Read the data from the stream.
228
+
229
+ This is a shortcut for reading the 'data' (as opposed to timestamps or config).
230
+ """
231
+ return self["data"].read(*args, **kwargs)
232
+
233
+
234
+ class ConfigDatasetClient(DictView):
235
+ def __repr__(self):
236
+ tiled_repr = node_repr(self, self._internal_dict.keys())
237
+ return tiled_repr.replace(type(self).__name__, "DatasetClient")
238
+
239
+ def read(self):
240
+ # Delay this import for fast startup. In some cases only metadata
241
+ # is handled, and we can avoid the xarray import altogether.
242
+
243
+ d = {
244
+ k: {"dims": "time", "data": v.read()}
245
+ for k, v in self._internal_dict.items()
246
+ }
247
+ return xarray.Dataset.from_dict(d)
248
+
249
+
250
+ class CompositeSubsetClient(CompositeClient):
251
+ """A composite client with only a subset of its keys exposed."""
252
+
253
+ def __init__(self, client, keys=None):
254
+ super().__init__(
255
+ context=client.context,
256
+ item=client.item,
257
+ structure_clients=client.structure_clients,
258
+ )
259
+ self._keys = keys or list(client.keys())
260
+
261
+ def __repr__(self):
262
+ return node_repr(self, self._keys).replace(type(self).__name__, "DatasetClient")
263
+
264
+ def _keys_slice(
265
+ self, start, stop, direction, page_size: int | None = None, **kwargs
266
+ ):
267
+ yield from self._keys[start : stop : -1 if direction < 0 else 1] # noqa: 203
268
+
269
+ def _items_slice(
270
+ self, start, stop, direction, page_size: int | None = None, **kwargs
271
+ ):
272
+ for key in self._keys[start : stop : -1 if direction < 0 else 1]: # noqa: 203
273
+ yield key, self[key]
274
+
275
+ def __iter__(self):
276
+ yield from self._keys
277
+
278
+ def __getitem__(self, key):
279
+ if key in self._keys:
280
+ return super().__getitem__(key)
281
+ raise KeyError(key)
282
+
283
+ def __len__(self):
284
+ return len(self._keys)
285
+
286
+ def __contains__(self, key):
287
+ return key in self._keys
288
+
289
+ def read(self, variables=None, dim0=None):
290
+ variables = set(self._keys).intersection(variables or self._keys)
291
+
292
+ return super().read(variables, dim0=dim0)
293
+
294
+
295
+ class VirtualContainer(DictView):
296
+ def __repr__(self):
297
+ tiled_repr = node_repr(self, self._internal_dict.keys())
298
+ return tiled_repr.replace(type(self).__name__, "ContainerClient")
299
+
300
+ def __getitem__(self, key):
301
+ if "/" in key:
302
+ key, rest = key.split("/", 1)
303
+ return self[key][rest]
304
+
305
+ return super().__getitem__(key)
306
+
307
+
308
+ class VirtualArrayClient:
309
+ def __init__(self, data, dims=None):
310
+ # Delay this import for fast startup. In some cases only metadata
311
+ # is handled, and we can avoid the numpy import altogether.
312
+
313
+ # Ensure data is an array-like object
314
+ if not hasattr(data, "__iter__") or isinstance(data, str):
315
+ data = [data]
316
+ if not hasattr(data, "__array__"):
317
+ data = numpy.asanyarray(data)
318
+
319
+ self._data = data
320
+ self._dims = dims
321
+
322
+ def __getitem__(self, slice):
323
+ return self.read(slice)
324
+
325
+ def __repr__(self):
326
+ attrs = {"shape": self.shape, "dtype": self.dtype}
327
+ if dims := self.dims:
328
+ attrs["dims"] = dims
329
+ return "<ArrayClient" + "".join(f" {k}={v}" for k, v in attrs.items()) + ">"
330
+
331
+ def read(self, slice=None):
332
+ return self._data if slice is None else self._data[slice]
333
+
334
+ @property
335
+ def size(self):
336
+ return self._data.size
337
+
338
+ @property
339
+ def shape(self):
340
+ return self._data.shape
341
+
342
+ @property
343
+ def dtype(self):
344
+ return self._data.dtype
345
+
346
+ @property
347
+ def dims(self):
348
+ return self._dims
349
+
350
+
351
+ class BlueskyEventStreamV3(BlueskyEventStream, CompositeClient):
352
+ def __repr__(self):
353
+ stream_name = self.metadata.get("stream_name") or self.item["id"]
354
+ return f"<BlueskyEventStream {self._var_keys!r} stream_name={stream_name!r}>"
355
+
356
+ @property
357
+ def _var_keys(self):
358
+ return {k for k in self if not k.startswith("ts_") and k != "seq_num"}
359
+
360
+ @property
361
+ def _ts_keys(self):
362
+ return {k for k in self if k.startswith("ts_")}
363
+
364
+ def read(self, variables=(DATAVALUES,), dim0=None):
365
+ if DATAVALUES in variables:
366
+ variables = self._var_keys.union(variables) - {DATAVALUES}
367
+ if TIMESTAMPS in variables:
368
+ variables = self._ts_keys.union(variables) - {TIMESTAMPS}
369
+
370
+ return super().read(variables=variables, dim0=dim0)
371
+
372
+ @functools.cached_property
373
+ def descriptors(self):
374
+ # Go back to the BlueskyRun node and requests the documents
375
+ stream_name = self.metadata.get("stream_name") or self.item["id"]
376
+ # the path is: bs_run_node/streams/current_stream (old) or bs_run_node/current_stream (new)
377
+ bs_run_node = self.parent
378
+ if bs_run_node.item["id"] == "streams" and (
379
+ "BlueskyRun" not in {s.name for s in bs_run_node.specs}
380
+ ):
381
+ # The parent is the old "streams" node, go up one more level
382
+ bs_run_node = bs_run_node.parent
383
+ return [
384
+ doc
385
+ for name, doc in bs_run_node.documents()
386
+ if name == "descriptor" and doc["name"] == stream_name
387
+ ]