bapsf-motion 0.2.0b1__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.
Files changed (51) hide show
  1. bapsf_motion/__init__.py +59 -0
  2. bapsf_motion/actors/__init__.py +18 -0
  3. bapsf_motion/actors/axis_.py +328 -0
  4. bapsf_motion/actors/base.py +281 -0
  5. bapsf_motion/actors/demos/__init__.py +0 -0
  6. bapsf_motion/actors/demos/drive_timed_run_1D.py +80 -0
  7. bapsf_motion/actors/demos/drive_timed_run_2D.py +98 -0
  8. bapsf_motion/actors/demos/mgroup_timed_run_2D.py +103 -0
  9. bapsf_motion/actors/drive_.py +360 -0
  10. bapsf_motion/actors/manager_.py +420 -0
  11. bapsf_motion/actors/motion_group_.py +1059 -0
  12. bapsf_motion/actors/motor_.py +1813 -0
  13. bapsf_motion/actors/tests/__init__.py +0 -0
  14. bapsf_motion/examples/benchtop_motion_group.toml +27 -0
  15. bapsf_motion/examples/benchtop_run.toml +30 -0
  16. bapsf_motion/examples/mg_1d.toml +24 -0
  17. bapsf_motion/examples/mg_2d_strb_test_setup.toml +37 -0
  18. bapsf_motion/examples/motion_group.toml +18 -0
  19. bapsf_motion/gui/__init__.py +4 -0
  20. bapsf_motion/gui/configure.py +3628 -0
  21. bapsf_motion/gui/motor.py +313 -0
  22. bapsf_motion/gui/widgets/__init__.py +24 -0
  23. bapsf_motion/gui/widgets/buttons.py +233 -0
  24. bapsf_motion/gui/widgets/logging.py +305 -0
  25. bapsf_motion/gui/widgets/misc.py +101 -0
  26. bapsf_motion/motion_builder/__init__.py +12 -0
  27. bapsf_motion/motion_builder/core.py +434 -0
  28. bapsf_motion/motion_builder/exclusions/__init__.py +37 -0
  29. bapsf_motion/motion_builder/exclusions/base.py +201 -0
  30. bapsf_motion/motion_builder/exclusions/circular.py +169 -0
  31. bapsf_motion/motion_builder/exclusions/divider.py +197 -0
  32. bapsf_motion/motion_builder/exclusions/helpers.py +176 -0
  33. bapsf_motion/motion_builder/exclusions/lapd.py +330 -0
  34. bapsf_motion/motion_builder/item.py +170 -0
  35. bapsf_motion/motion_builder/layers/__init__.py +27 -0
  36. bapsf_motion/motion_builder/layers/base.py +169 -0
  37. bapsf_motion/motion_builder/layers/helpers.py +178 -0
  38. bapsf_motion/motion_builder/layers/regular_grid.py +210 -0
  39. bapsf_motion/transform/__init__.py +12 -0
  40. bapsf_motion/transform/base.py +451 -0
  41. bapsf_motion/transform/helpers.py +153 -0
  42. bapsf_motion/transform/identity.py +80 -0
  43. bapsf_motion/transform/lapd.py +483 -0
  44. bapsf_motion/utils/__init__.py +112 -0
  45. bapsf_motion/utils/exceptions.py +13 -0
  46. bapsf_motion/utils/toml.py +55 -0
  47. bapsf_motion-0.2.0b1.dist-info/METADATA +140 -0
  48. bapsf_motion-0.2.0b1.dist-info/RECORD +51 -0
  49. bapsf_motion-0.2.0b1.dist-info/WHEEL +5 -0
  50. bapsf_motion-0.2.0b1.dist-info/dependency_links.txt +1 -0
  51. bapsf_motion-0.2.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,59 @@
1
+ """`bapsf_motion`"""
2
+ __all__ = ["__version__"]
3
+
4
+ # Enforce Python version check during package import.
5
+ # This is the same check as the one at the top of setup.py
6
+ import sys
7
+
8
+ if sys.version_info < (3, 7): # coverage: ignore
9
+ raise ImportError("bapsf_motion does not support Python < 3.7")
10
+
11
+ if sys.version_info >= (3, 8):
12
+ from importlib.metadata import version, PackageNotFoundError
13
+ else:
14
+ from importlib_metadata import version, PackageNotFoundError
15
+
16
+ from bapsf_motion import actors, motion_builder, transform, utils
17
+
18
+ # define version
19
+ try:
20
+ # this places a runtime dependency on setuptools
21
+ #
22
+ # note: if there's any distribution metadata in your source files, then this
23
+ # will find a version based on those files. Keep distribution metadata
24
+ # out of your repository unless you've intentionally installed the package
25
+ # as editable (e.g. `pip install -e {bapsf_motion_directory_root}`),
26
+ # but then __version__ will not be updated with each commit, it is
27
+ # frozen to the version at time of install.
28
+ #
29
+ #: bapsf_motion version string
30
+ __version__ = version("bapsf_motion")
31
+ except PackageNotFoundError:
32
+ # package is not installed
33
+ fallback_version = "unknown"
34
+ try:
35
+ # code most likely being used from source
36
+ # if setuptools_scm is installed then generate a version
37
+ from setuptools_scm import get_version
38
+
39
+ __version__ = get_version(
40
+ root="../", relative_to=__file__, fallback_version=fallback_version
41
+ )
42
+ del get_version
43
+ warn_add = "setuptools_scm failed to detect the version"
44
+ except ModuleNotFoundError:
45
+ # setuptools_scm is not installed
46
+ __version__ = fallback_version
47
+ warn_add = "setuptools_scm is not installed"
48
+
49
+ if __version__ == fallback_version:
50
+ from warnings import warn
51
+
52
+ warn(
53
+ f"bapsf_motion.__version__ not generated (set to 'unknown'), "
54
+ f"bapsf_motion is not an installed package and {warn_add}.",
55
+ RuntimeWarning,
56
+ )
57
+
58
+ del warn
59
+ del fallback_version, warn_add
@@ -0,0 +1,18 @@
1
+ __all__ = []
2
+ __actors__ = [
3
+ "Axis",
4
+ "BaseActor",
5
+ "Drive",
6
+ "EventActor",
7
+ "RunManager",
8
+ "MotionGroup",
9
+ "Motor",
10
+ ]
11
+ __all__ += __actors__
12
+
13
+ from bapsf_motion.actors.axis_ import Axis
14
+ from bapsf_motion.actors.base import BaseActor, EventActor
15
+ from bapsf_motion.actors.drive_ import Drive
16
+ from bapsf_motion.actors.manager_ import RunManager, RunManagerConfig
17
+ from bapsf_motion.actors.motion_group_ import MotionGroup, MotionGroupConfig
18
+ from bapsf_motion.actors.motor_ import Motor
@@ -0,0 +1,328 @@
1
+ """
2
+ Module for functionality focused around the
3
+ `~bapsf_motion.actors.axis_.Axis` actor class.
4
+ """
5
+ __all__ = ["Axis"]
6
+ __actors__ = ["Axis"]
7
+
8
+ import asyncio
9
+ import logging
10
+
11
+ from typing import Any, Dict, Union
12
+
13
+ from bapsf_motion.actors.base import EventActor
14
+ from bapsf_motion.actors.motor_ import Motor
15
+ from bapsf_motion.utils import units as u
16
+
17
+
18
+ class Axis(EventActor):
19
+ """
20
+ The `Axis` actor is the next level actor above the |Motor| actor.
21
+ This actor is ignorant of how it is situated in a probe drive, but
22
+ is fully aware of the entire physical axis that defines it and the
23
+ motor that moves the axis. This actor operates in physical units
24
+ and will handle all the necessary unit converstion to communicate
25
+ with the |Motor| actor.
26
+
27
+ Parameters
28
+ ----------
29
+ ip: str
30
+ IPv4 address for the motor driving the axis
31
+
32
+ units: str
33
+ Physical units the axis operates in (e.g. ``'cm'``)
34
+
35
+ units_per_rev: float
36
+ The number of ``units`` traversed per motor revolution.
37
+
38
+ name: str
39
+ Name the axis. (DEFAULT: ``'Axis'``)
40
+
41
+ logger: `~logging.Logger`, optional
42
+ An instance of `~logging.Logger` that the Actor will record
43
+ events and status updates to. If `None`, then a logger will
44
+ automatically be generated. (DEFUALT: `None`)
45
+
46
+ loop: `asyncio.AbstractEventLoop`, optional
47
+ Instance of an `asyncio` `event loop`_. Communication with the
48
+ motor will happen primaritly through the evenet loop. If
49
+ `None`, then an `event loop`_ will be auto-generated.
50
+ (DEFAULT: `None`)
51
+
52
+ auto_run: bool, optional
53
+ If `True`, then the `event loop`_ will be placed in a separate
54
+ thread and started. This is all done via the :meth:`run`
55
+ method. (DEFAULT: `False`)
56
+
57
+ Examples
58
+ --------
59
+
60
+ >>> from bapsf_motion.actors import Axis
61
+ >>> import logging
62
+ >>> import sys
63
+ >>> logging.basicConfig(stream=sys.stdout, level=logging.NOTSET)
64
+ >>> ax = Axis(
65
+ ... ip="192.168.6.104",
66
+ ... units="cm",
67
+ ... units_per_rev=0.1*2.54, # acme rod with .1 in pitch
68
+ ... name="WALL-E",
69
+ ... auto_run=True,
70
+ ... )
71
+
72
+ """
73
+ # TODO: better handle naming of the Axis and child Motor
74
+
75
+ def __init__(
76
+ self,
77
+ *,
78
+ ip: str,
79
+ units: str,
80
+ units_per_rev: float,
81
+ name: str = "Axis",
82
+ logger: logging.Logger = None,
83
+ loop: asyncio.AbstractEventLoop = None,
84
+ auto_run: bool = False,
85
+ ):
86
+ # TODO: update units so inches can be used
87
+ self._motor = None
88
+ self._units = u.Unit(units)
89
+ self._units_per_rev = units_per_rev * self._units / u.rev
90
+
91
+ super().__init__(
92
+ name=name,
93
+ logger=logger,
94
+ loop=loop,
95
+ auto_run=False,
96
+ )
97
+
98
+ self._motor = Motor(
99
+ ip=ip,
100
+ name="motor",
101
+ logger=self.logger,
102
+ loop=self.loop,
103
+ auto_run=False,
104
+ )
105
+
106
+ self.run(auto_run=auto_run)
107
+
108
+ def _configure_before_run(self):
109
+ return
110
+
111
+ def _initialize_tasks(self):
112
+ return
113
+
114
+ def run(self, auto_run=True):
115
+ super().run(auto_run=auto_run)
116
+
117
+ if self.motor is None:
118
+ return
119
+
120
+ self.motor.run(auto_run=auto_run)
121
+
122
+ def terminate(self, delay_loop_stop=False):
123
+ self.motor.terminate(delay_loop_stop=True)
124
+ super().terminate(delay_loop_stop=delay_loop_stop)
125
+
126
+ @property
127
+ def config(self) -> Dict[str, Any]:
128
+ """Dictionary of the axis configuration parameters."""
129
+ return {
130
+ "name": self.name,
131
+ "ip": self.motor.ip,
132
+ "units": str(self.units),
133
+ "units_per_rev": self.units_per_rev.value.item()
134
+ }
135
+ config.__doc__ = EventActor.config.__doc__
136
+
137
+ @property
138
+ def motor(self) -> Motor:
139
+ """Instance of the |Motor| object that belongs to |Axis|."""
140
+ return self._motor
141
+
142
+ @property
143
+ def ip(self):
144
+ """IPv4 address for the Axis' motor"""
145
+ return self.motor.ip
146
+
147
+ @property
148
+ def is_moving(self) -> bool:
149
+ """
150
+ `True` or `False` indicating if the axis is currently moving.
151
+ """
152
+ return self.motor.is_moving
153
+
154
+ @property
155
+ def position(self):
156
+ """
157
+ Current axis position in units defined by the :attr:`units`
158
+ attribute.
159
+ """
160
+ pos = self.motor.position
161
+ return pos.to(self.units, equivalencies=self.equivalencies)
162
+
163
+ @property
164
+ def steps_per_rev(self):
165
+ """Number of motor steps for a full revolution."""
166
+ return self.motor.steps_per_rev
167
+
168
+ @property
169
+ def units(self) -> u.Unit:
170
+ """
171
+ The unit of measure for the `Axis` physical parameters like
172
+ position, speed, etc.
173
+ """
174
+ return self._units
175
+
176
+ @units.setter
177
+ def units(self, new_units: u.Unit):
178
+ """Set the units of measure."""
179
+ if self.units.physical_type != new_units.physical_type:
180
+ raise ValueError
181
+
182
+ self._units_per_rev = self.units_per_rev.to(new_units / u.rev)
183
+ self._units = new_units
184
+
185
+ @property
186
+ def units_per_rev(self) -> u.Quantity:
187
+ """
188
+ The number of units (:attr:`units`) translated per full
189
+ revolution of the motor (:attr:`motor`).
190
+ """
191
+ return self._units_per_rev
192
+
193
+ @units_per_rev.setter
194
+ def units_per_rev(self, value: Union[float, u.Quantity]):
195
+ """
196
+ Update the number of units translated per full revolution of the
197
+ motor.
198
+ """
199
+ if isinstance(value, float) and value > 0.0:
200
+ self._units_per_rev = value * self.units / u.rev
201
+ elif (
202
+ isinstance(value, u.Quantity)
203
+ and value.unit == self.units / u.rev
204
+ and value > 0.0
205
+ ):
206
+ self._units_per_rev = value
207
+
208
+ @property
209
+ def equivalencies(self):
210
+ """
211
+ List of unit equivalencies to convert back-and-forth between
212
+ the axis physical units and the motor units.
213
+ """
214
+ steps_per_rev = self.steps_per_rev.value
215
+ units_per_rev = self.units_per_rev.value
216
+
217
+ equivs = [
218
+ (
219
+ u.rev,
220
+ u.steps,
221
+ lambda x: int(x * steps_per_rev),
222
+ lambda x: x / steps_per_rev,
223
+ ),
224
+ (
225
+ u.rev,
226
+ self.units,
227
+ lambda x: x * units_per_rev,
228
+ lambda x: x / units_per_rev,
229
+ ),
230
+ (
231
+ u.steps,
232
+ self.units,
233
+ lambda x: x * units_per_rev / steps_per_rev,
234
+ lambda x: int(x * steps_per_rev / units_per_rev),
235
+ ),
236
+ ]
237
+ for equiv in equivs.copy():
238
+ equivs.extend(
239
+ [
240
+ (equiv[0] / u.s, equiv[1] / u.s, equiv[2], equiv[3]),
241
+ (equiv[0] / u.s / u.s, equiv[1] / u.s / u.s, equiv[2], equiv[3]),
242
+ ]
243
+ )
244
+
245
+ return equivs
246
+
247
+ @property
248
+ def conversion_pairs(self):
249
+ """
250
+ List of conversion pairs between motor units and physical
251
+ units. For example, ``[(u.steps, self.units), ...]``.
252
+ """
253
+ return [
254
+ (u.steps, self.units),
255
+ (u.steps / u.s, self.units / u.s),
256
+ (u.steps / u.s / u.s, self.units / u.s / u.s),
257
+ (u.rev / u.s, self.units / u.s),
258
+ (u.rev / u.s / u.s, self.units / u.s / u.s),
259
+ ]
260
+
261
+ def send_command(self, command, *args):
262
+ """
263
+ Send ``command`` to the motor, and receive its response. If the
264
+ `event loop`_ is running, then the command will be sent as
265
+ a threadsafe coroutine_ in the loop. Otherwise, the command
266
+ will be sent directly to the motor.
267
+
268
+ Parameters
269
+ ----------
270
+ command: str
271
+ The desired command to be sent to the motor.
272
+ *args:
273
+ Any arguments to the ``command`` that will be sent with the
274
+ motor command.
275
+ """
276
+ cmd_entry = self.motor._commands[command]
277
+ motor_unit = cmd_entry["units"] # type: u.Unit
278
+
279
+ # TODO: put this into a separate convert() method that can handle both
280
+ # the send and recv unit conversion
281
+ if motor_unit is not None and len(args):
282
+ axis_unit = None
283
+ for motor_u, axis_u in self.conversion_pairs:
284
+ if motor_unit == motor_u:
285
+ axis_unit = axis_u
286
+ break
287
+
288
+ if axis_unit is not None:
289
+ args = list(args)
290
+ args[0] = args[0] * axis_unit.to(
291
+ motor_unit, equivalencies=self.equivalencies
292
+ )
293
+
294
+ # TODO: There should be a cleaner way of enforcing this
295
+ # int conversion...maybe add it to the Motor class,
296
+ # but I [Erik] currently feel the conversion should
297
+ # happen outside the Motor class
298
+ if motor_unit is u.steps:
299
+ args[0] = int(args[0])
300
+
301
+ rtn = self.motor.send_command(command, *args)
302
+
303
+ # TODO: see detailing todo above
304
+ if hasattr(rtn, "unit"):
305
+ axis_unit = None
306
+ for motor_u, axis_u in self.conversion_pairs:
307
+ if rtn.unit == motor_u:
308
+ axis_unit = axis_u
309
+ break
310
+
311
+ if axis_unit is not None:
312
+ rtn = rtn.to(axis_unit, equivalencies=self.equivalencies)
313
+
314
+ return rtn
315
+
316
+ def move_to(self, *args):
317
+ """
318
+ Quick access command for ``send_command("move_to", *args)``.
319
+ """
320
+ return self.send_command("move_to", *args)
321
+
322
+ def stop(self):
323
+ """
324
+ Quick access command for ``send_command("stop")``.
325
+ """
326
+ # not sending STOP command through send_command() since using
327
+ # motor.stop() should result in faster execution
328
+ return self.motor.stop()
@@ -0,0 +1,281 @@
1
+ """
2
+ Module for functionality focused around the [Abstract] base actors.
3
+ """
4
+
5
+ __all__ = ["BaseActor", "EventActor"]
6
+ __actors__ = ["BaseActor", "EventActor"]
7
+
8
+ import asyncio
9
+ import logging
10
+ import threading
11
+
12
+ from abc import ABC, abstractmethod
13
+ from typing import Any, Dict, List, Optional, Union
14
+
15
+
16
+ # TODO: create an EventActor for an actor that utilizes asyncio event loops
17
+ # - EventActor should inherit from BaseActor and ABC
18
+
19
+
20
+ class BaseActor(ABC):
21
+ """
22
+ Low-level base class for any Actor class.
23
+
24
+ Parameters
25
+ ----------
26
+ name : str, optional
27
+ A unique :attr:`name` for the Actor instance.
28
+ logger : `~logging.Logger`, optional
29
+ The instance of `~logging.Logger` that the Actor should record
30
+ events and status updates.
31
+ """
32
+
33
+ def __init__(
34
+ self, *, name: str = None, logger: logging.Logger = None,
35
+ ):
36
+ # setup logger to track events
37
+ log_name = "Actor" if logger is None else logger.name
38
+ if name is not None:
39
+ log_name += f".{name}"
40
+
41
+ self.name = name if name is not None else ""
42
+ self.logger = logging.getLogger(log_name)
43
+
44
+ @property
45
+ def name(self) -> str:
46
+ """
47
+ (`str`) A unique name given for the instance of the actor. This
48
+ name is used as an identifier in the actor logger (see
49
+ :attr:`logger`).
50
+
51
+ If the user does not specify a name, then the Actor should
52
+ auto-generate a name.
53
+ """
54
+ return self._name
55
+
56
+ @name.setter
57
+ def name(self, value):
58
+ self._name = value
59
+
60
+ @property
61
+ def logger(self) -> logging.Logger:
62
+ """The `~logger.Logger` instance being used for the actor."""
63
+ return self._logger
64
+
65
+ @logger.setter
66
+ def logger(self, value):
67
+ self._logger = value
68
+
69
+ @property
70
+ @abstractmethod
71
+ def config(self) -> Dict[str, Any]:
72
+ """
73
+ Configuration dictionary of the actor.
74
+
75
+ .. warning::
76
+
77
+ This dictionary should never be written to from outside the
78
+ owning actor.
79
+ """
80
+ ...
81
+
82
+
83
+ # TODO: Create an EventActor
84
+ # - must setup the asyncio event loop
85
+ # - must handle running th loop in a separate thread
86
+ # - How should I incorporate the heartbeat?
87
+ # - Must have the option to auto_run the event loop
88
+ # - must inherit from BaseActor
89
+ # - will likely need abstract methods _actor_setup_pre_loop() and
90
+ # _actor_setup_post_loop() for setup actions before and after
91
+ # the loop creation, respectively.
92
+
93
+
94
+ class EventActor(BaseActor, ABC):
95
+ r"""
96
+ A base class for any Actor that will be interacting with an `asncio`
97
+ event loop.
98
+
99
+ Parameters
100
+ ----------
101
+ name : str, optional
102
+ A unique :attr:`name` for the Actor instance.
103
+
104
+ logger : `~logging.Logger`, optional
105
+ The instance of `~logging.Logger` that the Actor should record
106
+ events and status updates.
107
+
108
+ loop: `asyncio.AbstractEventLoop`, optional
109
+ Instance of an `asyncio` `event loop`_\ . If `None`, then an
110
+ `event loop`_ will be auto-generated. (DEFAULT: `None`)
111
+
112
+ auto_run: bool, optional
113
+ If `True`, then the `event loop`_ will be placed in a separate
114
+ thread and started. This is all done via the :meth:`run`
115
+ method. (DEFAULT: `False`)
116
+ """
117
+
118
+ def __init__(
119
+ self,
120
+ *,
121
+ name: str = None,
122
+ logger: logging.Logger = None,
123
+ loop: asyncio.AbstractEventLoop = None,
124
+ auto_run: bool = False,
125
+ ):
126
+
127
+ super().__init__(name=name, logger=logger)
128
+
129
+ self._thread = None
130
+ self._loop = self.setup_event_loop(loop)
131
+ self._tasks = None
132
+
133
+ self._configure_before_run()
134
+ self._initialize_tasks()
135
+
136
+ self.run(auto_run)
137
+
138
+ @property
139
+ def tasks(self) -> List[asyncio.Task]:
140
+ r"""
141
+ List of `asyncio.Task`\ s this actor has in its `event loop`_.
142
+ """
143
+ if self._tasks is None:
144
+ self._tasks = []
145
+
146
+ return self._tasks
147
+
148
+ @property
149
+ def loop(self) -> asyncio.AbstractEventLoop:
150
+ """The `asyncio` :term:`event loop` for the actor."""
151
+ return self._loop
152
+
153
+ @property
154
+ def thread(self) -> threading.Thread:
155
+ """
156
+ The `~threading.Thread` the `event loop`_ is running in.
157
+
158
+ If :attr:`loop` was given during instantiation, then there is
159
+ no way of obtaining the thread object the event loop is
160
+ running in. In this case :attr:`thread` will be `None`.
161
+
162
+ The thread id can always be retrieved using :attr:`_thread_id`.
163
+ """
164
+ return self._thread
165
+
166
+ @property
167
+ def _thread_id(self) -> Union[int, None]:
168
+ """
169
+ Unique ID for the thread the loop is running in.
170
+
171
+ `None` if the :attr:`loop` does not exit or is not running.
172
+ """
173
+ if self.loop is None or not self.loop.is_running():
174
+ # no loop has been created or loop is not running
175
+ return None
176
+ elif self._thread is not None:
177
+ return self._thread.ident
178
+
179
+ # get thread id from inside the event loop
180
+ future = asyncio.run_coroutine_threadsafe(
181
+ self._thread_id_async(),
182
+ self.loop
183
+ )
184
+ return future.result(5)
185
+
186
+ async def _thread_id_async(self):
187
+ """
188
+ Asyncio coroutine for retrieving the id of the thread the event
189
+ loop is running in.
190
+ """
191
+ return threading.current_thread().ident
192
+
193
+ @abstractmethod
194
+ def _configure_before_run(self):
195
+ # A set of functionality for the subclass to run before the
196
+ # asyncio tasks are created and the event loop is started.
197
+ #
198
+ # This method is executed by __init__ before the event loop is
199
+ # started.
200
+ ...
201
+
202
+ @abstractmethod
203
+ def _initialize_tasks(self):
204
+ # Used by the subclass to initialize a list of tasks to be
205
+ # executed in the event loop after the loop is started.
206
+ #
207
+ # This method is executed by __init__ after
208
+ # _configure_before_run() but before the event loop is started.
209
+ ...
210
+
211
+ def setup_event_loop(
212
+ self, loop: Optional[asyncio.AbstractEventLoop] = None
213
+ ):
214
+ """
215
+ Set up the `asyncio` `event loop`_. If the given loop is not an
216
+ instance of `~asyncio.AbstractEventLoop`, then a new loop will
217
+ be created.
218
+
219
+ Parameters
220
+ ----------
221
+ loop: `asyncio.AbstractEventLoop`
222
+ `asyncio` `event loop`_ for the actor's tasks
223
+
224
+ """
225
+ # get a valid event loop
226
+ if loop is None:
227
+ loop = asyncio.new_event_loop()
228
+ elif not isinstance(loop, asyncio.AbstractEventLoop):
229
+ self.logger.warning(
230
+ "Given asyncio event is not valid. Creating a new event loop to use."
231
+ )
232
+ loop = asyncio.new_event_loop()
233
+ return loop
234
+
235
+ def run(self, auto_run=True):
236
+ r"""
237
+ Activate the `asyncio` `event loop`_\ . If the event loop is
238
+ running, then nothing happens. Otherwise, the event loop is
239
+ placed in a separate thread and set to
240
+ `~asyncio.loop.run_forever`.
241
+
242
+ Parameters
243
+ ----------
244
+ auto_run: `bool`, optional
245
+ If `False`, then do NOT start the event loop. This keyword
246
+ is only made available to help with subclassing.
247
+ (DEFAULT: `True`)
248
+ """
249
+ if self.loop is None or self.loop.is_running() or not auto_run:
250
+ return
251
+
252
+ self._thread = threading.Thread(target=self._loop.run_forever)
253
+ self._thread.start()
254
+
255
+ def terminate(self, delay_loop_stop=False):
256
+ r"""
257
+ Stop the actor's `event loop`_\ . All actor tasks will be
258
+ cancelled, the connection to the motor will be shutdown, and
259
+ the event loop will be stopped.
260
+
261
+ Parameters
262
+ ----------
263
+ delay_loop_stop: bool
264
+ If `True`, then do NOT stop the `event loop`_\ . In this
265
+ case it is assumed the calling functionality is managing
266
+ additional tasks in the event loop, and it is up to that
267
+ functionality to stop the loop. (DEFAULT: `False`)
268
+ """
269
+ for task in list(self.tasks):
270
+ self.loop.call_soon_threadsafe(task.cancel)
271
+ self.tasks.remove(task)
272
+
273
+ if delay_loop_stop:
274
+ return
275
+
276
+ # if we're stopping the loop, then all tasks need to be cancelled
277
+ for task in asyncio.all_tasks(self.loop):
278
+ if not task.done() or not task.cancelled():
279
+ self.loop.call_soon_threadsafe(task.cancel)
280
+
281
+ self.loop.call_soon_threadsafe(self.loop.stop)
File without changes