blissdata 0.3.4__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.
- blissdata/__init__.py +17 -0
- blissdata/beacon/__init__.py +1 -0
- blissdata/beacon/_base.py +106 -0
- blissdata/beacon/config.py +24 -0
- blissdata/beacon/data.py +45 -0
- blissdata/beacon/files.py +141 -0
- blissdata/client.py +62 -0
- blissdata/common/__init__.py +15 -0
- blissdata/common/utils.py +159 -0
- blissdata/data/__init__.py +15 -0
- blissdata/data/events/__init__.py +22 -0
- blissdata/data/events/channel.py +149 -0
- blissdata/data/events/lima.py +478 -0
- blissdata/data/events/node.py +28 -0
- blissdata/data/events/scan.py +49 -0
- blissdata/data/events/walk.py +42 -0
- blissdata/data/expiration.py +42 -0
- blissdata/data/lima_image.py +465 -0
- blissdata/data/node.py +1619 -0
- blissdata/data/nodes/__init__.py +11 -0
- blissdata/data/nodes/channel.py +394 -0
- blissdata/data/nodes/dataset.py +82 -0
- blissdata/data/nodes/dataset_collection.py +12 -0
- blissdata/data/nodes/lima.py +422 -0
- blissdata/data/nodes/node_ref_channel.py +34 -0
- blissdata/data/nodes/proposal.py +12 -0
- blissdata/data/nodes/scan.py +192 -0
- blissdata/data/nodes/scan_group.py +12 -0
- blissdata/data/remote_node.py +204 -0
- blissdata/data/scan.py +666 -0
- blissdata/h5api/__init__.py +1 -0
- blissdata/h5api/abstract.py +97 -0
- blissdata/h5api/dynamic_hdf5.py +153 -0
- blissdata/h5api/file_arguments.py +28 -0
- blissdata/h5api/static_hdf5.py +139 -0
- blissdata/h5api/utils/__init__.py +0 -0
- blissdata/h5api/utils/bliss.py +138 -0
- blissdata/h5api/utils/hdf5.py +280 -0
- blissdata/h5api/utils/hdf5_retry.py +98 -0
- blissdata/h5api/utils/lima.py +286 -0
- blissdata/h5api/utils/types.py +13 -0
- blissdata/redis/__init__.py +12 -0
- blissdata/redis/caching.py +390 -0
- blissdata/redis/connection.py +169 -0
- blissdata/redis/manager.py +164 -0
- blissdata/redis/proxy.py +971 -0
- blissdata/redis/scripting.py +24 -0
- blissdata/settings.py +1174 -0
- blissdata/streaming.py +819 -0
- blissdata/streaming_events.py +355 -0
- blissdata/tests/__init__.py +0 -0
- blissdata/tests/beacon/__init__.py +0 -0
- blissdata/tests/beacon/test_config.py +26 -0
- blissdata/tests/beacon/test_data.py +50 -0
- blissdata/tests/beacon/test_files.py +80 -0
- blissdata/tests/conftest.py +0 -0
- blissdata/tests/h5api/__init__.py +0 -0
- blissdata/tests/h5api/scanner.py +390 -0
- blissdata/tests/h5api/test_dynamic_files.py +309 -0
- blissdata/tests/h5api/test_static_files.py +169 -0
- blissdata/tests/redis/__init__.py +0 -0
- blissdata/tests/redis/manager.py +23 -0
- blissdata-0.3.4.dist-info/LICENSE +165 -0
- blissdata-0.3.4.dist-info/METADATA +79 -0
- blissdata-0.3.4.dist-info/RECORD +67 -0
- blissdata-0.3.4.dist-info/WHEEL +5 -0
- blissdata-0.3.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# This file is part of the bliss project
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2015-2023 Beamline Control Unit, ESRF
|
|
6
|
+
# Distributed under the GNU LGPLv3. See LICENSE for more info.
|
|
7
|
+
"""
|
|
8
|
+
Events that are added or read from a Redis stream have
|
|
9
|
+
a stream ID and carry a payload of type `dict`. The keys
|
|
10
|
+
and values need to be of type `bytes`.
|
|
11
|
+
|
|
12
|
+
To encode a custom data type to a raw stream payload
|
|
13
|
+
(and decode again when reading) a class can to be derived
|
|
14
|
+
from `StreamEvent`
|
|
15
|
+
|
|
16
|
+
class MyStreamEvent(StreamEvent):
|
|
17
|
+
TYPE = b"MYTYPE"
|
|
18
|
+
|
|
19
|
+
def init(self, var1, var2=None):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def _encode(self):
|
|
23
|
+
raw = super()._encode()
|
|
24
|
+
... # add var1 and var2 to raw
|
|
25
|
+
return raw
|
|
26
|
+
|
|
27
|
+
def _decode(self, raw):
|
|
28
|
+
super()._decode(raw)
|
|
29
|
+
... # get var1 and var2 from raw
|
|
30
|
+
|
|
31
|
+
To prepare data to be added to a Redis stream:
|
|
32
|
+
|
|
33
|
+
event = MyStreamEvent(var1, var2=...)
|
|
34
|
+
raw = event.encode()
|
|
35
|
+
|
|
36
|
+
The data of a Redis stream can be decoded as follows:
|
|
37
|
+
|
|
38
|
+
event = StreamEvent.factory(raw)
|
|
39
|
+
|
|
40
|
+
The factory method allows up-casting:
|
|
41
|
+
|
|
42
|
+
event = MyStreamEvent(var1, var2=...)
|
|
43
|
+
event = StreamEvent.factory(event.encode())
|
|
44
|
+
assert isinstance(event, MyStreamEvent)
|
|
45
|
+
|
|
46
|
+
If you don't want up-casting (avoid unnecessary decoding):
|
|
47
|
+
|
|
48
|
+
event = MyStreamEvent(raw={...})
|
|
49
|
+
|
|
50
|
+
Down-casting raises an exception (missing data):
|
|
51
|
+
|
|
52
|
+
event = StreamEvent()
|
|
53
|
+
try:
|
|
54
|
+
event = MyStreamEvent.factory(event.encode())
|
|
55
|
+
except StreamDecodeError:
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
To know whether data from a Redis stream is of a
|
|
59
|
+
particular type without decoding it:
|
|
60
|
+
|
|
61
|
+
if MyStreamEvent.istype(raw):
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
To know whether an event is of a particular type
|
|
65
|
+
|
|
66
|
+
if event.TYPE == event.types.MYTYPE:
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
if isinstance(event, MyStreamEvent):
|
|
70
|
+
...
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
import pickle
|
|
74
|
+
import datetime
|
|
75
|
+
import numbers
|
|
76
|
+
import functools
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class StreamError(Exception):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class StreamEncodeError(StreamError):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class StreamDecodeError(StreamError):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class StreamEventMeta(type):
|
|
92
|
+
"""Checks whether StreamEvent is being derived properly.
|
|
93
|
+
Makes sure the class gets up-casted upon instantiation
|
|
94
|
+
with data from a DataStream.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __new__(self, name, bases, attr):
|
|
98
|
+
"""Register class when defined"""
|
|
99
|
+
cls = super().__new__(self, name, bases, attr)
|
|
100
|
+
if hasattr(cls, "_SUBCLASS_REGISTRY"):
|
|
101
|
+
# Register the subclass
|
|
102
|
+
event_type = cls.TYPE
|
|
103
|
+
ecls = cls._SUBCLASS_REGISTRY.get(event_type)
|
|
104
|
+
if ecls is not None:
|
|
105
|
+
raise NotImplementedError(
|
|
106
|
+
f"Event type {event_type} is already taken by class {ecls.__name__}"
|
|
107
|
+
)
|
|
108
|
+
if not isinstance(event_type, bytes):
|
|
109
|
+
raise NotImplementedError("{cls.__name__}.TYPE must be of type 'bytes'")
|
|
110
|
+
cls._SUBCLASS_REGISTRY[event_type] = cls
|
|
111
|
+
else:
|
|
112
|
+
# Register the base class
|
|
113
|
+
cls._SUBCLASS_REGISTRY = {None: cls, cls.TYPE: cls}
|
|
114
|
+
return cls
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class _EventTypes:
|
|
118
|
+
def __getattr__(self, attr):
|
|
119
|
+
attr = attr.upper().encode()
|
|
120
|
+
if attr in StreamEvent._SUBCLASS_REGISTRY:
|
|
121
|
+
return attr
|
|
122
|
+
raise AttributeError(attr)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class StreamEvent(metaclass=StreamEventMeta):
|
|
126
|
+
"""Base class to encode/decode DataStream events."""
|
|
127
|
+
|
|
128
|
+
TYPE = b"UNKNOWN"
|
|
129
|
+
TYPE_KEY = b"__EVENT__"
|
|
130
|
+
types = _EventTypes()
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def _use_class(cls, raw):
|
|
134
|
+
event_type = raw.get(cls.TYPE_KEY, b"")
|
|
135
|
+
try:
|
|
136
|
+
# Get the proper class for this event type
|
|
137
|
+
return cls._SUBCLASS_REGISTRY[event_type]
|
|
138
|
+
except KeyError:
|
|
139
|
+
# Event type is missing or unknown:
|
|
140
|
+
# use the base class
|
|
141
|
+
return cls._SUBCLASS_REGISTRY[None]
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def istype(cls, raw):
|
|
145
|
+
usecls = cls._use_class(raw)
|
|
146
|
+
# issubclass does no work?
|
|
147
|
+
# return issubclass(cls, usecls)
|
|
148
|
+
return cls in usecls.__mro__
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def isstricttype(cls, raw):
|
|
152
|
+
return raw.get(cls.TYPE_KEY, b"") == cls.TYPE
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def class_factory(cls, raw):
|
|
156
|
+
"""Returns the class StreamEvent or one of the derived
|
|
157
|
+
classes, depending of the event type.
|
|
158
|
+
|
|
159
|
+
:param dict raw: DataStream event data
|
|
160
|
+
:raises StreamDecodeError:
|
|
161
|
+
:returns type:
|
|
162
|
+
"""
|
|
163
|
+
usecls = cls._use_class(raw)
|
|
164
|
+
# issubclass does not work???
|
|
165
|
+
# if not issubclass(cls, usecls):
|
|
166
|
+
if cls not in usecls.__mro__:
|
|
167
|
+
# Do not allow down-casting (up-casting is allowed)
|
|
168
|
+
raise StreamDecodeError(f"Event not of type {cls.TYPE}: {raw}")
|
|
169
|
+
return usecls
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def factory(cls, raw):
|
|
173
|
+
"""The returned object is on instance of
|
|
174
|
+
this class or a derived classes, depending
|
|
175
|
+
of the event type.
|
|
176
|
+
|
|
177
|
+
:param dict raw: DataStream event data
|
|
178
|
+
:raises StreamDecodeError:
|
|
179
|
+
:returns StreamEvent:
|
|
180
|
+
"""
|
|
181
|
+
usecls = cls.class_factory(raw=raw)
|
|
182
|
+
return usecls(raw=raw)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def merge_factory(cls, events):
|
|
186
|
+
"""
|
|
187
|
+
:param list((index, raw)) events:
|
|
188
|
+
:returns StreamEvent:
|
|
189
|
+
"""
|
|
190
|
+
if not events:
|
|
191
|
+
return
|
|
192
|
+
usecls = cls.class_factory(events[0][1])
|
|
193
|
+
return usecls.merge(events)
|
|
194
|
+
|
|
195
|
+
def __init__(self, *args, raw=None, **kw):
|
|
196
|
+
if raw is None:
|
|
197
|
+
self.init(*args, **kw)
|
|
198
|
+
else:
|
|
199
|
+
self.decode(raw)
|
|
200
|
+
|
|
201
|
+
def __str__(self):
|
|
202
|
+
return f"{self.__class__.__name__}({self.TYPE})"
|
|
203
|
+
|
|
204
|
+
def init(self):
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
def encode(self):
|
|
208
|
+
"""
|
|
209
|
+
:raises StreamEncodeError:
|
|
210
|
+
:returns dict: DataStream event data
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
return self._encode()
|
|
214
|
+
except Exception as e:
|
|
215
|
+
raise StreamEncodeError from e
|
|
216
|
+
|
|
217
|
+
def decode(self, raw):
|
|
218
|
+
"""
|
|
219
|
+
:param dict raw: DataStream event data
|
|
220
|
+
:raises StreamDecodeError:
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
self._decode(raw)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
raise StreamDecodeError from e
|
|
226
|
+
|
|
227
|
+
def _encode(self):
|
|
228
|
+
return {self.TYPE_KEY: self.TYPE}
|
|
229
|
+
|
|
230
|
+
def _decode(self, raw):
|
|
231
|
+
if not self.istype(raw):
|
|
232
|
+
raise TypeError(f"Event not of type {self.TYPE}: {raw}")
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def generic_encode(data, **kw):
|
|
236
|
+
"""
|
|
237
|
+
:param Any data:
|
|
238
|
+
:returns bytes:
|
|
239
|
+
"""
|
|
240
|
+
return pickle.dumps(data, **kw)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def generic_decode(data, **kw):
|
|
244
|
+
"""
|
|
245
|
+
:param Any data:
|
|
246
|
+
:returns Any:
|
|
247
|
+
"""
|
|
248
|
+
return pickle.loads(data, **kw)
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def encode_bytes(cls, data):
|
|
252
|
+
return data
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def decode_bytes(cls, data):
|
|
256
|
+
return data
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def encode_string(cls, data):
|
|
260
|
+
return data.encode()
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
def decode_string(cls, data):
|
|
264
|
+
return data.decode()
|
|
265
|
+
|
|
266
|
+
@classmethod
|
|
267
|
+
def encode_integral(cls, data):
|
|
268
|
+
return b"%d" % data
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def decode_integral(cls, data, int_type=int):
|
|
272
|
+
return int_type(data.decode())
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def encode_decode_methods(cls, data_type):
|
|
276
|
+
if issubclass(data_type, bytes):
|
|
277
|
+
return cls.encode_bytes, cls.decode_bytes
|
|
278
|
+
elif issubclass(data_type, str):
|
|
279
|
+
return cls.encode_string, cls.decode_string
|
|
280
|
+
elif issubclass(data_type, numbers.Integral):
|
|
281
|
+
decode = functools.partial(cls.decode_integral, int_type=data_type)
|
|
282
|
+
return cls.encode_integral, decode
|
|
283
|
+
else:
|
|
284
|
+
return cls.generic_encode, cls.generic_decode
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def merge(cls, events):
|
|
288
|
+
"""
|
|
289
|
+
:param list((index, raw)) events:
|
|
290
|
+
:returns StreamEvent:
|
|
291
|
+
"""
|
|
292
|
+
raise NotImplementedError
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class TimeEvent(StreamEvent):
|
|
296
|
+
|
|
297
|
+
TYPE = b"TIME"
|
|
298
|
+
TIMESTAMP_KEY = b"__TIMESTAMP__"
|
|
299
|
+
STRTIME_KEY = b"__STRTIME__"
|
|
300
|
+
|
|
301
|
+
STRTIME_FMT = "%a %b %d %H:%M:%S %Y"
|
|
302
|
+
|
|
303
|
+
def init(self):
|
|
304
|
+
self.time = datetime.datetime.now()
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def timestamp(self):
|
|
308
|
+
return self.time.timestamp()
|
|
309
|
+
|
|
310
|
+
@timestamp.setter
|
|
311
|
+
def timestamp(self, epoch):
|
|
312
|
+
self.time = datetime.datetime.fromtimestamp(float(epoch))
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def strftime(self):
|
|
316
|
+
return self.time.strftime(self.STRTIME_FMT)
|
|
317
|
+
|
|
318
|
+
def _encode(self):
|
|
319
|
+
raw = super()._encode()
|
|
320
|
+
raw[self.TIMESTAMP_KEY] = self.generic_encode(self.timestamp)
|
|
321
|
+
raw[self.STRTIME_KEY] = self.encode_string(self.strftime)
|
|
322
|
+
return raw
|
|
323
|
+
|
|
324
|
+
def _decode(self, raw):
|
|
325
|
+
super()._decode(raw)
|
|
326
|
+
self.timestamp = self.generic_decode(raw[self.TIMESTAMP_KEY])
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class StartEvent(TimeEvent):
|
|
330
|
+
|
|
331
|
+
TYPE = b"START"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class PreparedEvent(TimeEvent):
|
|
335
|
+
|
|
336
|
+
TYPE = b"PREPARED"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class EndEvent(TimeEvent):
|
|
340
|
+
|
|
341
|
+
TYPE = b"END"
|
|
342
|
+
EXCEPTION_KEY = b"__EXCEPTION__"
|
|
343
|
+
|
|
344
|
+
def init(self, exception=None, **kw):
|
|
345
|
+
super().init(**kw)
|
|
346
|
+
self.exception = exception
|
|
347
|
+
|
|
348
|
+
def _encode(self):
|
|
349
|
+
raw = super()._encode()
|
|
350
|
+
raw[self.EXCEPTION_KEY] = self.generic_encode(self.exception)
|
|
351
|
+
return raw
|
|
352
|
+
|
|
353
|
+
def _decode(self, raw):
|
|
354
|
+
super()._decode(raw)
|
|
355
|
+
self.exception = self.generic_decode(raw[self.EXCEPTION_KEY])
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from blissdata.beacon import config
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_beacon_address(mocker):
|
|
6
|
+
mock_environ = mocker.patch("os.environ.get", return_value="foo:200")
|
|
7
|
+
result = config.get_beacon_address()
|
|
8
|
+
assert result == ("foo", 200)
|
|
9
|
+
mock_environ.assert_called_with("BEACON_HOST")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_missing_beacon_address(mocker):
|
|
13
|
+
mock_environ = mocker.patch("os.environ.get", return_value=None)
|
|
14
|
+
with pytest.raises(ValueError) as excinfo:
|
|
15
|
+
config.get_beacon_address()
|
|
16
|
+
assert "BEACON_HOST" in excinfo.value.args[0]
|
|
17
|
+
mock_environ.assert_called_with("BEACON_HOST")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_wrong_beacon_address_formatting(mocker):
|
|
21
|
+
mock_environ = mocker.patch("os.environ.get", return_value="foo")
|
|
22
|
+
with pytest.raises(ValueError) as excinfo:
|
|
23
|
+
config.get_beacon_address()
|
|
24
|
+
assert "BEACON_HOST" in excinfo.value.args[0]
|
|
25
|
+
assert "foo" in excinfo.value.args[0]
|
|
26
|
+
mock_environ.assert_called_with("BEACON_HOST")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from blissdata.beacon.data import BeaconData
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_empty_constructor(mocker):
|
|
5
|
+
mock_socket = mocker.patch("socket.socket")
|
|
6
|
+
mock_config = mocker.patch(
|
|
7
|
+
"blissdata.beacon.config.get_beacon_address", return_value=("foo", 100)
|
|
8
|
+
)
|
|
9
|
+
_ = BeaconData()
|
|
10
|
+
mock_config.assert_called_once()
|
|
11
|
+
mock_socket.return_value.connect.assert_called_with(("foo", 100))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_param_constructor(mocker):
|
|
15
|
+
mock_socket = mocker.patch("socket.socket")
|
|
16
|
+
mock_config = mocker.patch(
|
|
17
|
+
"blissdata.beacon.config.get_beacon_address", return_value=("foo", 100)
|
|
18
|
+
)
|
|
19
|
+
_ = BeaconData("foo", 200)
|
|
20
|
+
mock_config.assert_not_called()
|
|
21
|
+
mock_socket.return_value.connect.assert_called_with(("foo", 200))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_get_redis_db(mocker):
|
|
25
|
+
RESPONSE1 = b"\x1f\x00\x00\x00C\x00\x00\x00localhost:/tmp/demo_resourcesnd1ti3cr/configuration/redis_demo.sock"
|
|
26
|
+
mock_socket = mocker.patch("socket.socket")
|
|
27
|
+
mock_socket.return_value.recv.side_effect = [RESPONSE1]
|
|
28
|
+
service = BeaconData("foo", 200)
|
|
29
|
+
result = service.get_redis_db()
|
|
30
|
+
mock_socket.return_value.sendall.assert_called_once_with(
|
|
31
|
+
b"\x1e\x00\x00\x00\x00\x00\x00\x00"
|
|
32
|
+
)
|
|
33
|
+
assert (
|
|
34
|
+
result == "localhost:/tmp/demo_resourcesnd1ti3cr/configuration/redis_demo.sock"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_get_redis_data_db(mocker):
|
|
39
|
+
RESPONSE1 = b'"\x00\x00\x00J\x00\x00\x001|localhost|/tmp/demo_resourcesnd1ti3cr/configuration/redis_data_demo.sock'
|
|
40
|
+
mock_socket = mocker.patch("socket.socket")
|
|
41
|
+
mock_socket.return_value.recv.side_effect = [RESPONSE1]
|
|
42
|
+
service = BeaconData("foo", 200)
|
|
43
|
+
result = service.get_redis_data_db()
|
|
44
|
+
mock_socket.return_value.sendall.assert_called_once_with(
|
|
45
|
+
b"\x20\x00\x00\x00\x02\x00\x00\x001|"
|
|
46
|
+
)
|
|
47
|
+
assert (
|
|
48
|
+
result
|
|
49
|
+
== "localhost:/tmp/demo_resourcesnd1ti3cr/configuration/redis_data_demo.sock"
|
|
50
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from blissdata.beacon.files import BeaconFiles, read_config, yaml_load
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_empty_constructor(mocker):
|
|
6
|
+
mock_socket = mocker.patch("socket.socket")
|
|
7
|
+
mock_config = mocker.patch(
|
|
8
|
+
"blissdata.beacon.config.get_beacon_address", return_value=("foo", 100)
|
|
9
|
+
)
|
|
10
|
+
_ = BeaconFiles()
|
|
11
|
+
mock_config.assert_called_once()
|
|
12
|
+
mock_socket.return_value.connect.assert_called_with(("foo", 100))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_param_constructor(mocker):
|
|
16
|
+
mock_socket = mocker.patch("socket.socket")
|
|
17
|
+
mock_config = mocker.patch(
|
|
18
|
+
"blissdata.beacon.config.get_beacon_address", return_value=("foo", 100)
|
|
19
|
+
)
|
|
20
|
+
_ = BeaconFiles("foo", 200)
|
|
21
|
+
mock_config.assert_not_called()
|
|
22
|
+
mock_socket.return_value.connect.assert_called_with(("foo", 200))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_get_file(mocker):
|
|
26
|
+
RESPONSE1 = b"\x34\x00\x00\x00\x0E\x00\x00\x001|MY FILE DATA"
|
|
27
|
+
mock_socket = mocker.patch("socket.socket")
|
|
28
|
+
mock_socket.return_value.recv.side_effect = [RESPONSE1]
|
|
29
|
+
service = BeaconFiles("foo", 200)
|
|
30
|
+
result = service.get_file("sessions/__init__.yml")
|
|
31
|
+
mock_socket.return_value.sendall.assert_called_once_with(
|
|
32
|
+
b"2\x00\x00\x00\x17\x00\x00\x001|sessions/__init__.yml"
|
|
33
|
+
)
|
|
34
|
+
assert result == b"MY FILE DATA"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_get_tree(mocker):
|
|
38
|
+
RESPONSE1 = b"""\x58\x00\x00\x00\x2C\x00\x00\x001|{"afile":null,"adir":{"anotherfile":null}}"""
|
|
39
|
+
mock_socket = mocker.patch("socket.socket")
|
|
40
|
+
mock_socket.return_value.recv.side_effect = [RESPONSE1]
|
|
41
|
+
service = BeaconFiles("foo", 200)
|
|
42
|
+
result = service.get_tree("")
|
|
43
|
+
mock_socket.return_value.sendall.assert_called_once_with(
|
|
44
|
+
b"V\x00\x00\x00\x02\x00\x00\x001|"
|
|
45
|
+
)
|
|
46
|
+
assert result == {"adir": {"anotherfile": None}, "afile": None}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.skipif(yaml_load is None, reason="No yaml lib found")
|
|
50
|
+
def test_read_config__from_file(tmp_path):
|
|
51
|
+
filename = str(tmp_path / "test.yml")
|
|
52
|
+
with open(filename, "wt") as f:
|
|
53
|
+
f.write("foo:\n bar: 2000\n")
|
|
54
|
+
result = read_config(filename)
|
|
55
|
+
assert result == {"foo": {"bar": 2000}}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.skipif(yaml_load is None, reason="No yaml lib found")
|
|
59
|
+
def test_read_config__from_beacon(mocker, tmp_path):
|
|
60
|
+
RESPONSE1 = b"\x34\x00\x00\x00\x15\x00\x00\x001|foo:\n bar: 2000\n"
|
|
61
|
+
mock_socket = mocker.patch("socket.socket")
|
|
62
|
+
mock_socket.return_value.recv.side_effect = [RESPONSE1]
|
|
63
|
+
mock_config = mocker.patch("blissdata.beacon.config.get_beacon_address")
|
|
64
|
+
mock_config.return_value = ("foo", 100)
|
|
65
|
+
result = read_config("beacon:///foo.yml")
|
|
66
|
+
assert result == {"foo": {"bar": 2000}}
|
|
67
|
+
mock_config.assert_called_once()
|
|
68
|
+
mock_socket.return_value.connect.assert_called_with(("foo", 100))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.skipif(yaml_load is None, reason="No yaml lib found")
|
|
72
|
+
def test_read_config__from_explicit_beacon(mocker, tmp_path):
|
|
73
|
+
RESPONSE1 = b"\x34\x00\x00\x00\x15\x00\x00\x001|foo:\n bar: 2000\n"
|
|
74
|
+
mock_socket = mocker.patch("socket.socket")
|
|
75
|
+
mock_socket.return_value.recv.side_effect = [RESPONSE1]
|
|
76
|
+
mock_config = mocker.patch("blissdata.beacon.config.get_beacon_address")
|
|
77
|
+
result = read_config("beacon://foo:200/foo.yml")
|
|
78
|
+
assert result == {"foo": {"bar": 2000}}
|
|
79
|
+
mock_config.assert_not_called()
|
|
80
|
+
mock_socket.return_value.connect.assert_called_with(("foo", 200))
|
|
File without changes
|
|
File without changes
|