lima2.client 3.0a1__tar.gz → 3.1.0__tar.gz

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.
Files changed (49) hide show
  1. {lima2_client-3.0a1/src/lima2_client.egg-info → lima2_client-3.1.0}/PKG-INFO +2 -2
  2. {lima2_client-3.0a1 → lima2_client-3.1.0}/pyproject.toml +1 -1
  3. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/client.py +67 -1
  4. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/detector.py +16 -2
  5. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/pipeline.py +16 -16
  6. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/state_machine.py +3 -0
  7. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/topology.py +11 -6
  8. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/master_file/generate.py +39 -27
  9. lima2_client-3.1.0/src/lima2/master_file/py.typed +0 -0
  10. {lima2_client-3.0a1 → lima2_client-3.1.0/src/lima2_client.egg-info}/PKG-INFO +2 -2
  11. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2_client.egg-info/SOURCES.txt +1 -0
  12. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2_client.egg-info/requires.txt +1 -1
  13. {lima2_client-3.0a1 → lima2_client-3.1.0}/LICENSE +0 -0
  14. {lima2_client-3.0a1 → lima2_client-3.1.0}/README.md +0 -0
  15. {lima2_client-3.0a1 → lima2_client-3.1.0}/setup.cfg +0 -0
  16. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/__init__.py +0 -0
  17. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/draw_state_diagram.py +0 -0
  18. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/examples/__init__.py +0 -0
  19. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/examples/async_github_pull_request.py +0 -0
  20. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/examples/async_turnstile.py +0 -0
  21. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/examples/boolean_field.py +0 -0
  22. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/examples/github_pull_request.py +0 -0
  23. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/examples/turnstile.py +0 -0
  24. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/exceptions.py +0 -0
  25. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/finite_state_machine/state_machine.py +0 -0
  26. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/__init__.py +0 -0
  27. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/convert.py +0 -0
  28. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/devencoded/__init__.py +0 -0
  29. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/devencoded/dense_frame.py +0 -0
  30. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/devencoded/exception.py +0 -0
  31. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/devencoded/smx_sparse_frame.py +0 -0
  32. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/devencoded/sparse_frame.py +0 -0
  33. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/devencoded/structured_array.py +0 -0
  34. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/pipelines/__init__.py +0 -0
  35. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/pipelines/failing.py +0 -0
  36. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/pipelines/legacy.py +0 -0
  37. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/pipelines/smx.py +0 -0
  38. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/pipelines/xpcs.py +0 -0
  39. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/progress_counter.py +0 -0
  40. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/py.typed +0 -0
  41. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/shell.py +0 -0
  42. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/client/utils.py +0 -0
  43. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/server/__init__.py +0 -0
  44. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/server/control.py +0 -0
  45. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/server/receiver.py +0 -0
  46. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2/server/timer.py +0 -0
  47. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
  48. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2_client.egg-info/entry_points.txt +0 -0
  49. {lima2_client-3.0a1 → lima2_client-3.1.0}/src/lima2_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lima2-client
3
- Version: 3.0a1
3
+ Version: 3.1.0
4
4
  Summary: Python client library for Lima2 detector
5
5
  Author: BCU (ESRF)
6
6
  License: Copyright (c) 2020-2024 Beamline Control Unit, ESRF
@@ -24,7 +24,7 @@ Requires-Dist: gevent
24
24
  Requires-Dist: jsonschema
25
25
  Requires-Dist: jsonschema-default
26
26
  Requires-Dist: beartype
27
- Requires-Dist: numpy
27
+ Requires-Dist: numpy<2.3
28
28
  Requires-Dist: pyyaml
29
29
  Requires-Dist: typing-extensions
30
30
  Requires-Dist: h5py
@@ -24,7 +24,7 @@ dependencies = [
24
24
  "jsonschema",
25
25
  "jsonschema-default",
26
26
  "beartype",
27
- "numpy",
27
+ "numpy<2.3", # CI runner (CentOS 7) has glibc2.17, numpy>=2.3 needs glibc2.28+
28
28
  "pyyaml",
29
29
  "typing-extensions",
30
30
  "h5py",
@@ -34,6 +34,8 @@ from lima2.client.progress_counter import ProgressCounter, SingleCounter
34
34
  from lima2.client.topology import TopologyKind
35
35
  from lima2.client.utils import docstring_from
36
36
 
37
+ from lima2.master_file import generate as mfg
38
+
37
39
  logger = logging.getLogger(__name__)
38
40
 
39
41
 
@@ -77,6 +79,32 @@ class Client:
77
79
 
78
80
  self._tango_db = tg.Database()
79
81
 
82
+ # Show master file generation info messages
83
+ mfg.logger.setLevel(logging.INFO)
84
+
85
+ self.master_file_tasks: dict[UUID, dict[str, mfg.MasterFileTask]] = {}
86
+ """Master file generation tasks for each pipeline UUID.
87
+
88
+ Each entry corresponds to a pipeline, and holds a dict structured like
89
+ {saving channel name: list of tasks}.
90
+ """
91
+
92
+ def cleanup(self):
93
+ """Unregister all state loggers and tango event handlers.
94
+
95
+ This method serves only in specific cases where event handlers must for
96
+ some reason be unregistered. In a normal use case, a Client is
97
+ instantiated once per Lima2 instance, and no cleanup is necessary.
98
+
99
+ NOTE: After this call, the Client instance should no longer be used.
100
+ """
101
+ self._detector.unregister_events()
102
+ self._detector = None
103
+
104
+ for pipeline in self.__pipelines.values():
105
+ pipeline.unregister_events()
106
+ self.__pipelines.clear()
107
+
80
108
  @classmethod
81
109
  def from_yaml(
82
110
  cls,
@@ -147,10 +175,23 @@ class Client:
147
175
  ####################################################################################
148
176
 
149
177
  def prepare_acq(
150
- self, uuid: UUID, ctrl_params: dict, acq_params: dict, proc_params: dict
178
+ self,
179
+ uuid: UUID,
180
+ ctrl_params: dict,
181
+ acq_params: dict,
182
+ proc_params: dict,
151
183
  ) -> None:
152
184
  """Prepare acquisition with a given uuid and set of params."""
153
185
 
186
+ # Check parameters required for master file generation
187
+ checked_params = mfg.check_generation(
188
+ ctrl_params=ctrl_params,
189
+ recv_params=acq_params,
190
+ proc_params=proc_params,
191
+ topology_kind=self.topology_kind,
192
+ )
193
+ ctrl_params, acq_params, proc_params = checked_params
194
+
154
195
  # Adjust params for distributed acquisition
155
196
  ctl, acq, proc = topology.distribute_acq(
156
197
  ctl_params=ctrl_params,
@@ -175,16 +216,41 @@ class Client:
175
216
 
176
217
  self.current_pipeline.register_on_error(on_error)
177
218
 
219
+ # Remove master file tasks of previous acquisitions if done or not started
220
+ for prev_uuid in list(self.master_file_tasks.keys()):
221
+ tasks = self.master_file_tasks[prev_uuid].values()
222
+ if all([not task.started() or task.ready() for task in tasks]):
223
+ logger.debug(
224
+ "All master file tasks done (or never started) for "
225
+ f"{prev_uuid} -> cleaning up"
226
+ )
227
+ self.master_file_tasks.pop(prev_uuid)
228
+ else:
229
+ logger.debug(f"Not all master file tasks done for {prev_uuid}...")
230
+
231
+ self.master_file_tasks[uuid] = mfg.enable_generation(
232
+ pipeline=self.current_pipeline,
233
+ num_frames=ctrl_params["acq"]["nb_frames"],
234
+ ctrl_params=ctl,
235
+ recv_params=acq[0],
236
+ proc_params=proc[0],
237
+ topology_kind=self.topology_kind,
238
+ )
239
+
178
240
  @docstring_from(Detector.start_acq)
179
241
  def start_acq(self) -> None:
180
242
  pipeline = self.current_pipeline
181
243
  pipeline.register_on_finished(pipeline.unregister_events)
244
+
182
245
  try:
183
246
  self.detector.start_acq()
184
247
  except Exception:
185
248
  pipeline.unregister_events()
186
249
  raise
187
250
 
251
+ for task in self.master_file_tasks[self.current_pipeline.uuid].values():
252
+ task.start()
253
+
188
254
  @docstring_from(Detector.trigger)
189
255
  def trigger(self) -> None:
190
256
  self.detector.trigger()
@@ -73,6 +73,7 @@ class Detector:
73
73
 
74
74
  self.__ctrl = ctrl_dev
75
75
  self.__recvs = list(recv_devs)
76
+ self._state_event_ids: dict[tango.DeviceProxy, int] = {}
76
77
 
77
78
  self.ping()
78
79
 
@@ -100,6 +101,17 @@ class Detector:
100
101
  d.set_green_mode(tango.GreenMode.Gevent)
101
102
  d.set_timeout_millis(timeout * 1000)
102
103
 
104
+ def unregister_events(self):
105
+ """Unregister all state loggers and tango event handlers.
106
+
107
+ This method serves only in specific cases where event handlers must for
108
+ some reason be unregistered. In a normal use case, a Client is
109
+ instantiated once per Lima2 instance, and no cleanup is necessary.
110
+ """
111
+ for device, event_id in self._state_event_ids.items():
112
+ device.unsubscribe_event(event_id)
113
+ self._state_event_ids.clear()
114
+
103
115
  @contextlib.contextmanager
104
116
  def _locked_fsm(self) -> Iterator[fsm.StateMachine]:
105
117
  self.__fsm_lock.acquire()
@@ -633,12 +645,14 @@ class Detector:
633
645
  finally:
634
646
  _logger.info(f"Current state: {fsm.state}")
635
647
 
636
- for dev in self.devs:
637
- dev.subscribe_event(
648
+ self._state_event_ids = {
649
+ dev: dev.subscribe_event(
638
650
  "acq_state",
639
651
  tango.EventType.CHANGE_EVENT,
640
652
  on_acq_state_change,
641
653
  )
654
+ for dev in self.devs
655
+ }
642
656
 
643
657
  def _register_on_end_acq_handlers(self):
644
658
  """Register an "end of acquisition" callback on each receiver.
@@ -379,13 +379,13 @@ class Pipeline:
379
379
 
380
380
  # Pop stats for new frames from each receiver and concatenate, mixing in cached data.
381
381
  # Data comes from server as 1D array with size (num_frames * num_rois)
382
- new_data = np.concatenate(
383
- [cache.flatten()]
384
- + [
385
- structured_array.decode(dev.popRoiStatistics(), dtype)
386
- for dev in self._devs
387
- ]
388
- )
382
+ def pop_data(dev) -> np.ndarray:
383
+ return structured_array.decode(dev.popRoiStatistics(), dtype)
384
+
385
+ num_rcvs = len(self._devs)
386
+ with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
387
+ flat_cache = [cache.flatten()]
388
+ new_data = np.concatenate(flat_cache + list(pool.map(pop_data, self._devs)))
389
389
 
390
390
  # Guess number of frames from the data
391
391
  num_frames = np.unique(new_data["frame_idx"]).size
@@ -473,13 +473,13 @@ class Pipeline:
473
473
 
474
474
  # Pop profiles for new frames from each receiver and concatenate, mixing in cached data.
475
475
  # Data comes from server as 1D array with size (num_frames * sum(roi_lengths))
476
- new_data = np.concatenate(
477
- [cache.flatten()]
478
- + [
479
- structured_array.decode(dev.popRoiProfiles(), dtype)
480
- for dev in self._devs
481
- ]
482
- )
476
+ def pop_data(dev) -> np.ndarray:
477
+ return structured_array.decode(dev.popRoiProfiles(), dtype)
478
+
479
+ num_rcvs = len(self._devs)
480
+ with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
481
+ flat_cache = [cache.flatten()]
482
+ new_data = np.concatenate(flat_cache + list(pool.map(pop_data, self._devs)))
483
483
 
484
484
  # Reshape into (num_frames, sum(roi_lengths))
485
485
  new_data = new_data.reshape((-1, sum(roi_lengths)))
@@ -654,9 +654,9 @@ class Pipeline:
654
654
  "no associated label"
655
655
  )
656
656
 
657
- def last_frames(rcv_idx):
657
+ def last_frames(rcv_idx: int) -> int:
658
658
  last_frames_str = self._devs[rcv_idx].last_frames
659
- return json.loads(last_frames_str)[f"{source.label}_idx"]
659
+ return int(json.loads(last_frames_str)[f"{source.label}_idx"])
660
660
 
661
661
  num_rcvs = len(self._devs)
662
662
  with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
@@ -239,6 +239,9 @@ class StateMachine(fsm.StateMachine):
239
239
  if logger in self.__loggers:
240
240
  self.__loggers.remove(logger)
241
241
 
242
+ def clear_transition_loggers(self):
243
+ self.__loggers.clear()
244
+
242
245
  def on_state_change(self, state):
243
246
  _logger.debug(f"on_state_change {state}")
244
247
  futures = []
@@ -9,6 +9,7 @@ import copy
9
9
  from abc import ABC, abstractmethod
10
10
  from collections.abc import Callable
11
11
  from enum import Enum
12
+ from concurrent.futures import ThreadPoolExecutor
12
13
 
13
14
  import tango as tg
14
15
  from beartype import beartype
@@ -107,18 +108,22 @@ class UnevenTopology(Topology):
107
108
  If the frame is found on multiple devices (e.g. frame_idx=-1),
108
109
  return the one found last (not necessarily the most recent).
109
110
  """
110
- frame: EncodedFrame | None = None
111
111
 
112
- for i in range(self.num_receivers):
112
+ def get_frame(rcv_idx) -> EncodedFrame | None:
113
113
  try:
114
- frame = getters[i](frame_idx)
114
+ frame: EncodedFrame = getters[rcv_idx](frame_idx)
115
+ return frame
115
116
  except tg.DevFailed:
116
- pass
117
+ return None
117
118
 
118
- if frame is None:
119
+ num_rcvs = self.num_receivers
120
+ with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
121
+ frames = [f for f in pool.map(get_frame, range(num_rcvs)) if f is not None]
122
+
123
+ if len(frames) == 0:
119
124
  raise IndexError(f"Frame {frame_idx} not available from any receiver")
120
125
 
121
- return frame
126
+ return frames[0]
122
127
 
123
128
 
124
129
  def distribute_acq(
@@ -13,7 +13,6 @@ processing pipelines finish.
13
13
  import json
14
14
  import logging
15
15
  import os
16
- import sys
17
16
  import time
18
17
  import traceback
19
18
  from copy import deepcopy
@@ -25,8 +24,10 @@ import h5py
25
24
  import numpy as np
26
25
 
27
26
  import gevent
27
+ import gevent.event
28
28
 
29
29
  from lima2.client.pipeline import FrameType, Pipeline
30
+ from lima2.client.topology import TopologyKind
30
31
 
31
32
  logger = logging.getLogger(__name__)
32
33
 
@@ -39,13 +40,19 @@ class LUTSource(Enum):
39
40
 
40
41
 
41
42
  class MasterFileTask:
42
- def __init__(self, task, end_event):
43
+ def __init__(self, task: gevent.Greenlet, end_event):
43
44
  self.task = task
44
45
  self.end_event = end_event
45
46
 
46
47
  def ready(self):
47
48
  return self.task.ready()
48
49
 
50
+ def start(self):
51
+ self.task.start()
52
+
53
+ def started(self) -> bool:
54
+ return self.task.started
55
+
49
56
  def wait(self, timeout):
50
57
  finished = gevent.wait([self.task], timeout=timeout)
51
58
  return len(finished) > 0
@@ -60,11 +67,11 @@ class MasterFileTask:
60
67
  self.task.join()
61
68
 
62
69
 
63
- active_tasks = {}
64
-
65
-
66
70
  def check_generation(
67
- ctrl_params: dict, recv_params: dict, proc_params: dict
71
+ ctrl_params: dict,
72
+ recv_params: dict,
73
+ proc_params: dict,
74
+ topology_kind: TopologyKind,
68
75
  ) -> list[dict]:
69
76
  """Check the parameters to be sent to prepare to ensure master file info"""
70
77
 
@@ -86,13 +93,15 @@ def check_generation(
86
93
  if params["enabled"]:
87
94
  active_saving_params.append(params)
88
95
 
96
+ need_frame_lut = topology_kind == TopologyKind.UNEVEN
97
+
89
98
  # include detector frame_idx dataset in HDF5 files
90
99
  for saving_params in active_saving_params:
91
- if "include_frame_idx" in saving_params:
100
+ if need_frame_lut and "include_frame_idx" in saving_params:
92
101
  saving_params["include_frame_idx"] = True
93
102
 
94
103
  # allow retreiving recv-det-frame-idx mapping
95
- if "frame_idx_enabled" in proc_params:
104
+ if need_frame_lut and "frame_idx_enabled" in proc_params:
96
105
  logger.debug("Enabling frame_idx generation")
97
106
  proc_params["frame_idx_enabled"] = True
98
107
 
@@ -105,9 +114,10 @@ def enable_generation(
105
114
  ctrl_params: dict,
106
115
  recv_params: dict,
107
116
  proc_params: dict,
117
+ topology_kind: TopologyKind,
108
118
  rr_offset: int = 0,
109
119
  lut_source: LUTSource = LUTSource.AUTO,
110
- ) -> None:
120
+ ) -> dict[str, MasterFileTask]:
111
121
  """Enable the generation of a master file for a given pipeline.
112
122
 
113
123
  Must be called between Client.prepare_acq() and Client.start_acq(), so that the
@@ -177,6 +187,8 @@ def enable_generation(
177
187
  proc_params=proc_params,
178
188
  )
179
189
 
190
+ need_frame_lut = topology_kind == TopologyKind.UNEVEN
191
+
180
192
  for source, frame_info, saving_params, cnt_name in active_saving_params:
181
193
  base_path = saving_params["base_path"]
182
194
  filename_prefix = saving_params["filename_prefix"]
@@ -204,10 +216,12 @@ def enable_generation(
204
216
  this_lut_source = LUTSource.ROUND_ROBIN
205
217
  if this_lut_source is None:
206
218
  raise ValueError(f"Cannot satisfy {lut_source=}: {file_lut=}, {recv_lut=}")
219
+ elif this_lut_source == LUTSource.ROUND_ROBIN and need_frame_lut:
220
+ raise ValueError(f"Cannot use {this_lut_source} with {topology_kind}")
207
221
 
208
222
  logger.info(
209
223
  f"Master file (channel={source}) will be generated "
210
- f"at {master_file_path}..."
224
+ f"at {master_file_path} [{this_lut_source}]..."
211
225
  )
212
226
 
213
227
  if frame_info is not None:
@@ -244,31 +258,29 @@ def enable_generation(
244
258
  logger.warning(
245
259
  "Master file generation not enabled: no dense frames saving channel enabled."
246
260
  )
247
- return None
261
+ return {}
248
262
 
249
263
  def master_file_task(src, params) -> None:
250
- try:
251
- p = MasterFileGeneration.Params(**params)
252
- gen = MasterFileGeneration(p)
253
- gen.run()
254
- logger.debug(f"On finished {src} master file generation")
255
- except Exception as e:
256
- logger.error("Error generating %s master file: %s", src, e)
257
- traceback.print_exception(*sys.exc_info())
264
+ p = MasterFileGeneration.Params(**params)
265
+ gen = MasterFileGeneration(p)
266
+ gen.run()
267
+ logger.debug(f"On finished {src} master file generation")
258
268
 
259
- tasks = {}
269
+ tasks: dict[str, MasterFileTask] = {}
260
270
 
261
271
  for src, params in generate_params.items():
262
- task = gevent.spawn(master_file_task, src, params)
272
+ task = gevent.Greenlet(master_file_task, src, params)
263
273
  tasks[src] = MasterFileTask(task, end_event)
264
274
 
265
- def cleanup(task):
266
- if all([t.ready() for t in tasks.values()]):
267
- active_tasks.pop(uuid)
268
-
269
- task.link(cleanup)
275
+ def log_error(greenlet: gevent.Greenlet) -> None:
276
+ msg = (
277
+ f"Exception in master file task {str(uuid)} (channel '{src}'):\n"
278
+ + "".join(traceback.format_exception(greenlet.exception))
279
+ )
280
+ logger.error(msg)
281
+ print(msg)
270
282
 
271
- active_tasks[uuid] = tasks
283
+ task.link_exception(callback=log_error)
272
284
 
273
285
  return tasks
274
286
 
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lima2-client
3
- Version: 3.0a1
3
+ Version: 3.1.0
4
4
  Summary: Python client library for Lima2 detector
5
5
  Author: BCU (ESRF)
6
6
  License: Copyright (c) 2020-2024 Beamline Control Unit, ESRF
@@ -24,7 +24,7 @@ Requires-Dist: gevent
24
24
  Requires-Dist: jsonschema
25
25
  Requires-Dist: jsonschema-default
26
26
  Requires-Dist: beartype
27
- Requires-Dist: numpy
27
+ Requires-Dist: numpy<2.3
28
28
  Requires-Dist: pyyaml
29
29
  Requires-Dist: typing-extensions
30
30
  Requires-Dist: h5py
@@ -34,6 +34,7 @@ src/lima2/client/pipelines/legacy.py
34
34
  src/lima2/client/pipelines/smx.py
35
35
  src/lima2/client/pipelines/xpcs.py
36
36
  src/lima2/master_file/generate.py
37
+ src/lima2/master_file/py.typed
37
38
  src/lima2/server/__init__.py
38
39
  src/lima2/server/control.py
39
40
  src/lima2/server/receiver.py
@@ -3,7 +3,7 @@ gevent
3
3
  jsonschema
4
4
  jsonschema-default
5
5
  beartype
6
- numpy
6
+ numpy<2.3
7
7
  pyyaml
8
8
  typing-extensions
9
9
  h5py
File without changes
File without changes
File without changes