genie-python 15.1.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.
Files changed (45) hide show
  1. genie_python/.pylintrc +539 -0
  2. genie_python/__init__.py +1 -0
  3. genie_python/_version.py +16 -0
  4. genie_python/block_names.py +123 -0
  5. genie_python/channel_access_exceptions.py +45 -0
  6. genie_python/genie.py +2462 -0
  7. genie_python/genie_advanced.py +418 -0
  8. genie_python/genie_alerts.py +195 -0
  9. genie_python/genie_api_setup.py +451 -0
  10. genie_python/genie_blockserver.py +64 -0
  11. genie_python/genie_cachannel_wrapper.py +551 -0
  12. genie_python/genie_change_cache.py +151 -0
  13. genie_python/genie_dae.py +2219 -0
  14. genie_python/genie_epics_api.py +906 -0
  15. genie_python/genie_experimental_data.py +186 -0
  16. genie_python/genie_logging.py +200 -0
  17. genie_python/genie_p4p_wrapper.py +203 -0
  18. genie_python/genie_plot.py +77 -0
  19. genie_python/genie_pre_post_cmd_manager.py +21 -0
  20. genie_python/genie_pv_connection_protocol.py +36 -0
  21. genie_python/genie_script_checker.py +507 -0
  22. genie_python/genie_script_generator.py +212 -0
  23. genie_python/genie_simulate.py +69 -0
  24. genie_python/genie_simulate_impl.py +1265 -0
  25. genie_python/genie_startup.py +29 -0
  26. genie_python/genie_toggle_settings.py +58 -0
  27. genie_python/genie_wait_for_move.py +154 -0
  28. genie_python/genie_waitfor.py +576 -0
  29. genie_python/matplotlib_backend/__init__.py +0 -0
  30. genie_python/matplotlib_backend/ibex_websocket_backend.py +366 -0
  31. genie_python/mysql_abstraction_layer.py +272 -0
  32. genie_python/scanning_instrument_pylint_plugin.py +31 -0
  33. genie_python/testing_utils/__init__.py +4 -0
  34. genie_python/testing_utils/script_checker.py +63 -0
  35. genie_python/typings/CaChannel/CaChannel.pyi +893 -0
  36. genie_python/typings/CaChannel/__init__.pyi +9 -0
  37. genie_python/typings/CaChannel/_version.pyi +6 -0
  38. genie_python/typings/CaChannel/ca.pyi +31 -0
  39. genie_python/utilities.py +406 -0
  40. genie_python/version.py +6 -0
  41. genie_python-15.1.0.dist-info/LICENSE +28 -0
  42. genie_python-15.1.0.dist-info/METADATA +84 -0
  43. genie_python-15.1.0.dist-info/RECORD +45 -0
  44. genie_python-15.1.0.dist-info/WHEEL +5 -0
  45. genie_python-15.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,576 @@
1
+ """
2
+ Classes allowing you to wait for states
3
+ """
4
+
5
+ from __future__ import absolute_import, print_function
6
+
7
+ from abc import ABCMeta, abstractmethod
8
+ from builtins import object, str
9
+ from datetime import datetime, timedelta
10
+ from time import sleep, strptime
11
+ from typing import TYPE_CHECKING, Callable, TypeAlias
12
+
13
+ from genie_python.utilities import check_break, get_time_delta
14
+
15
+ if TYPE_CHECKING:
16
+ from genie_python.genie_epics_api import API
17
+
18
+ NUMERIC_TYPE = (float, int)
19
+
20
+ WAITFOR_VALUE: TypeAlias = bool | int | float | str | None
21
+
22
+ # The time in a loop to sleep for when waiting for an event, e.g. polling a pv/block value
23
+ DELAY_IN_WAIT_FOR_SLEEP_LOOP = 0.1
24
+
25
+ # The time in a start_waiting to wait until notifying the user again of an error
26
+ SECONDS_UNTIL_RENOTIFY_EXCEPTION = 5 * 60
27
+
28
+ # Use the state pattern to keep a track of transitions to when exceptions and reconnections should
29
+ # be printed
30
+ # Exceptions should be printed at disconnection, after 5 minutes of disconnection or when the
31
+ # exception changes
32
+ # When the exception disappears pv reconnected is printed once
33
+
34
+
35
+ class WaitForControllerState(object, metaclass=ABCMeta):
36
+ """
37
+ The abstract class for the exception state of the
38
+ WaitForController when it is in the loop in the start_waiting method.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ api: "API",
44
+ last_notification_time: datetime,
45
+ last_exception: "Exception|None" = None,
46
+ context: "WaitForControllerExceptionContext|None" = None,
47
+ ) -> None:
48
+ """
49
+ Initializes the state.
50
+ :param api: The api to use for logging
51
+ :param last_notification_time: The last time the user was notified of an exception
52
+ :param last_exception: The last exception the user was notified of
53
+ :param context: The context the state belongs to
54
+ """
55
+ self._api = api
56
+ self._last_notification_time = last_notification_time
57
+ self._last_exception = last_exception
58
+ self.context = context
59
+
60
+ def process_exception(
61
+ self, exception: Exception | None = None
62
+ ) -> "WaitForControllerState|None":
63
+ """
64
+ Delegate processing of the exception to the relevant subclass method,
65
+ based on whether the exception argument is None or not.
66
+ :param exception: The exception to be processed (if no exception it is None)
67
+ :return: The state the context is now in (could be the same or a transition)
68
+ """
69
+ if exception is not None:
70
+ return self.handle_exception_notification(exception)
71
+ else:
72
+ return self.exception_cleared()
73
+
74
+ @staticmethod
75
+ def _transition(new_state: "WaitForControllerState") -> "WaitForControllerState":
76
+ """
77
+ We need to transition to a new state. Create this state, run enter on it and return it.
78
+ :param new_state: The state to transition to
79
+ :return: The new state
80
+ """
81
+ new_state.enter()
82
+ return new_state
83
+
84
+ @staticmethod
85
+ def start(
86
+ api: "API", context: "WaitForControllerExceptionContext|None" = None
87
+ ) -> "WaitForControllerConnectedState":
88
+ """
89
+ Return the state the context starts at.
90
+ :param api: The api to use for logging
91
+ :param context: The context the state belongs to
92
+ :return: The state the context starts at.
93
+ """
94
+ return WaitForControllerConnectedState(api, datetime.now(), context=context)
95
+
96
+ @abstractmethod
97
+ def handle_exception_notification(self, exception: Exception) -> "WaitForControllerState|None":
98
+ """
99
+ When there is an exception handle it.
100
+ :param exception: The exception to handle
101
+ :return: The state the context is now in (could be the same or a transition)
102
+ """
103
+ pass
104
+
105
+ @abstractmethod
106
+ def exception_cleared(self) -> "WaitForControllerState|None":
107
+ """
108
+ When there is no exception handle it.
109
+ :return: The state the context is now in (could be the same or a transition)
110
+ """
111
+ pass
112
+
113
+ @abstractmethod
114
+ def enter(self) -> None:
115
+ """
116
+ When this state is first entered handle it.
117
+ :return: None
118
+ """
119
+ pass
120
+
121
+
122
+ class WaitForControllerExceptionState(WaitForControllerState):
123
+ """
124
+ The state for when the WaitForController is in an exception state
125
+ but should not notify the user
126
+ """
127
+
128
+ def handle_exception_notification(self, exception: Exception) -> "WaitForControllerState|None":
129
+ """
130
+ Handle the event when an exception is occurring.
131
+ Transition to notifying user of an exception every 5 minutes or if the exception is
132
+ different to the last, else, remain in this state.
133
+ :param exception: The exception that has occurred.
134
+ :return: The new exception state if 5 minutes passed since last or if there is a new type
135
+ of exception. The current state if not.
136
+ """
137
+ seconds_since_last_notification = (datetime.now() - self._last_notification_time).seconds
138
+ if seconds_since_last_notification >= SECONDS_UNTIL_RENOTIFY_EXCEPTION or not isinstance(
139
+ exception, type(self._last_exception)
140
+ ):
141
+ return self._transition(
142
+ WaitForControllerExceptionState(self._api, self._last_notification_time, exception)
143
+ )
144
+ else:
145
+ return self
146
+
147
+ def exception_cleared(self) -> "WaitForControllerState|None":
148
+ """
149
+ Handle the transition from an exception occurring to where the pv is connected.
150
+ :return: The new connected state
151
+ """
152
+ return self._transition(
153
+ WaitForControllerConnectedState(
154
+ self._api, self._last_notification_time, self._last_exception
155
+ )
156
+ )
157
+
158
+ def print_exception(self, exception: Exception | None) -> None:
159
+ """
160
+ Print an exception to stdout and log it to the api.
161
+ :param exception: The exception to print
162
+ :return: None
163
+ """
164
+ message = "{}: Exception in waitfor loop: {}: {}".format(
165
+ str(datetime.now()), exception.__class__.__name__, exception
166
+ )
167
+ print(message)
168
+ self._api.logger.log_info_msg(message)
169
+
170
+ def enter(self) -> None:
171
+ """
172
+ Print message about last exception and update notification time.
173
+ :return: None
174
+ """
175
+ self.print_exception(self._last_exception)
176
+ self._last_notification_time = datetime.now()
177
+
178
+
179
+ class WaitForControllerConnectedState(WaitForControllerState):
180
+ """
181
+ The state for when the WaitForController has just reconnected after an exception state.
182
+ """
183
+
184
+ def handle_exception_notification(
185
+ self, exception: "Exception|None"
186
+ ) -> "WaitForControllerState|None":
187
+ """
188
+ Transition to the exception occurring state.
189
+ :param exception: The exception that has occurred.
190
+ :return: The new state of the context, which is an exception state.
191
+ """
192
+ return self._transition(
193
+ WaitForControllerExceptionState(self._api, self._last_notification_time, exception)
194
+ )
195
+
196
+ def exception_cleared(self) -> WaitForControllerState | None:
197
+ """
198
+ Remain in this state as we are still connected.
199
+ :return: The current state
200
+ """
201
+ return self
202
+
203
+ def print_exception_cleared(self) -> None:
204
+ """
205
+ Print to the console that the pv is connected.
206
+ :return: None
207
+ """
208
+ message = "{}: Exception cleared".format(str(datetime.now()))
209
+ print(message)
210
+ self._api.logger.log_info_msg(message)
211
+
212
+ def enter(self) -> None:
213
+ """
214
+ When first entering this state print the the pv is connected.
215
+ :return: None
216
+ """
217
+ self.print_exception_cleared()
218
+
219
+
220
+ class WaitForControllerExceptionContext(object):
221
+ """
222
+ The exception context of the WaitForController when it is
223
+ in the loop in the start_waiting method
224
+ """
225
+
226
+ def __init__(self, api: "API") -> None:
227
+ """
228
+ Set the starting state of the context.
229
+ :param api: The api for logging
230
+ """
231
+ self._state = WaitForControllerState.start(api, context=self)
232
+
233
+ def process_exception(self, exception: Exception | None) -> None:
234
+ """
235
+ Delegate the processing of the exception to the state
236
+ and create the new state based on what is returned.
237
+ :param exception: The exception to process
238
+ :return: None
239
+ """
240
+ if self._state is not None:
241
+ self._state = self._state.process_exception(exception)
242
+
243
+
244
+ class WaitForController(object):
245
+ """
246
+ Controller for waiting for states
247
+ """
248
+
249
+ def __init__(self, api: "API") -> None:
250
+ self.api = api
251
+ self.time_delta = None
252
+ self.start_time = None
253
+ self.block = None
254
+ self.low = None
255
+ self.high = None
256
+
257
+ def start_waiting(
258
+ self,
259
+ block: str | None = None,
260
+ value: WAITFOR_VALUE | None = None,
261
+ lowlimit: float | int | None = None,
262
+ highlimit: float | int | None = None,
263
+ maxwait: float | None = None,
264
+ wait_all: bool = False,
265
+ seconds: float | None = None,
266
+ minutes: float | None = None,
267
+ hours: float | None = None,
268
+ time: str | None = None,
269
+ frames: int | None = None,
270
+ raw_frames: int | None = None,
271
+ uamps: float | None = None,
272
+ mevents: float | None = None,
273
+ early_exit: Callable[[], bool] = lambda: False,
274
+ quiet: bool = False,
275
+ ) -> None:
276
+ """
277
+ Wait until a condition is reached. If wait_all is False then wait for one of the
278
+ conditions if True wait until all are reached
279
+ Args:
280
+ block: wait for a block to become a value
281
+ value: the value the block should become
282
+ lowlimit: the low limit, the value should be above this value
283
+ highlimit: the high limit, the value should be below this value
284
+ maxwait: maximum time in seconds to wait for the state to be reached
285
+ wait_all: True wait for all conditions to be reached; False wait for one condition
286
+ to be reached
287
+ seconds: number of seconds to wait
288
+ minutes: number of minutes to wait
289
+ hours: number of hours to wait
290
+ time: total time to wait (overrides seconds minutes and hours)
291
+ frames: number of frames to wait
292
+ raw_frames: number of raw frames to wait
293
+ uamps: number of micro amps to wait
294
+ mevents: number of millions of events to wait
295
+ early_exit: function to check if wait should exit early. Function should return true
296
+ to exit wait.
297
+ quiet: Suppress confirmation and countdown output to the console
298
+ (warning/error messages still sent)
299
+
300
+ Returns: nothing
301
+
302
+ """
303
+ # Error checks
304
+ timeout_msg = ""
305
+ self._print_if_not_quiet(
306
+ "Start time: {}".format(datetime.now().time().strftime("%H:%M:%S")), quiet
307
+ )
308
+ self.api.logger.log_info_msg("WAITFOR STARTED")
309
+ if maxwait is not None:
310
+ if not isinstance(maxwait, NUMERIC_TYPE):
311
+ raise Exception("The value entered for maxwait was invalid, it should be numeric.")
312
+ else:
313
+ timeout_msg = "[timeout={}]".format(timedelta(seconds=maxwait).total_seconds())
314
+ if seconds is not None and not isinstance(seconds, NUMERIC_TYPE):
315
+ raise Exception("Invalid value entered for seconds")
316
+ if minutes is not None and not isinstance(minutes, NUMERIC_TYPE):
317
+ raise Exception("Invalid value entered for minutes")
318
+ if hours is not None and not isinstance(hours, NUMERIC_TYPE):
319
+ raise Exception("Invalid value entered for hours")
320
+ if time is not None:
321
+ try:
322
+ ans = strptime(time, "%H:%M:%S")
323
+ seconds = ans.tm_sec
324
+ minutes = ans.tm_min
325
+ hours = ans.tm_hour
326
+ except Exception:
327
+ raise Exception(
328
+ "Time string entered was invalid. It should be of the form HH:MM:SS"
329
+ )
330
+ if frames is not None:
331
+ if not isinstance(frames, int):
332
+ raise Exception("Invalid value entered for frames")
333
+ else:
334
+ self._print_if_not_quiet(
335
+ "Waiting for {} frames {}".format(frames, timeout_msg), quiet
336
+ )
337
+ if raw_frames is not None:
338
+ if not isinstance(raw_frames, int):
339
+ raise Exception("Invalid value entered for raw_frames")
340
+ else:
341
+ self._print_if_not_quiet(
342
+ "Waiting for {} raw frames {}".format(raw_frames, timeout_msg), quiet
343
+ )
344
+ if uamps is not None:
345
+ if not (isinstance(uamps, NUMERIC_TYPE)):
346
+ raise Exception("Invalid value entered for uamps")
347
+ else:
348
+ self._print_if_not_quiet(
349
+ "Waiting for {} uamps {}".format(uamps, timeout_msg), quiet
350
+ )
351
+ if mevents is not None:
352
+ if not (isinstance(mevents, NUMERIC_TYPE)):
353
+ raise Exception("Invalid value entered for mevents")
354
+ else:
355
+ self._print_if_not_quiet(
356
+ "Waiting for {} million events {}".format(mevents, timeout_msg), quiet
357
+ )
358
+
359
+ if block is not None:
360
+ if not self.api.block_exists(block):
361
+ raise NameError('No block with the name "{}" exists'.format(block))
362
+ block = self.api.correct_blockname(block, add_prefix=False)
363
+ if value is not None and (not isinstance(value, NUMERIC_TYPE + (str,))):
364
+ raise Exception(
365
+ "The value entered for the block was invalid, it should be numeric or a string."
366
+ )
367
+ if lowlimit is not None and (not isinstance(lowlimit, NUMERIC_TYPE)):
368
+ raise Exception("The value entered for lowlimit was invalid, it should be numeric.")
369
+ if highlimit is not None and (not isinstance(highlimit, NUMERIC_TYPE)):
370
+ raise Exception(
371
+ "The value entered for highlimit was invalid, it should be numeric."
372
+ )
373
+
374
+ self._init_wait_time(seconds, minutes, hours, quiet, timeout_msg)
375
+ self._init_wait_block(block, value, lowlimit, highlimit, quiet, timeout_msg)
376
+ start_time = datetime.now()
377
+
378
+ # Start with a state where the pv is considered connected and
379
+ # there is no need to notify of it
380
+ context = WaitForControllerExceptionContext(self.api)
381
+
382
+ while True:
383
+ if maxwait is not None:
384
+ if datetime.now() - start_time >= timedelta(seconds=maxwait):
385
+ print("Waitfor timed out after {} seconds".format(maxwait))
386
+ print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S")))
387
+ self.api.logger.log_info_msg("WAITFOR TIMED OUT")
388
+ return
389
+
390
+ if early_exit():
391
+ print("Early exit handler evaluated to true in waitfor - stopping wait")
392
+ print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S")))
393
+ self.api.logger.log_info_msg(
394
+ "EARLY EXIT HANDLER REACHED IN WAITFOR - STOPPING WAIT"
395
+ )
396
+ return
397
+
398
+ try:
399
+ res = []
400
+ if self.block is not None:
401
+ res.append(self._block_has_waited_for_value())
402
+ if self.start_time is not None and self.time_delta is not None:
403
+ res.append(self._waiting_for_time())
404
+ if frames is not None:
405
+ res.append(self.api.dae.get_good_frames() < frames)
406
+ if raw_frames is not None:
407
+ res.append(self.api.dae.get_raw_frames() < raw_frames)
408
+ if uamps is not None:
409
+ res.append(self.api.dae.get_uamps() < uamps)
410
+ if mevents is not None:
411
+ res.append(self.api.dae.get_mevents() < mevents)
412
+ # Notify the context that no exception has occurred
413
+ context.process_exception(None)
414
+
415
+ if len(res) == 0:
416
+ self.api.logger.log_error_msg("NO VALID WAITFOR CONDITIONS PROVIDED")
417
+ print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S")))
418
+ return
419
+ elif (wait_all and True not in res) or (not wait_all and False in res):
420
+ self.api.logger.log_info_msg("WAITFOR EXITED NORMALLY")
421
+ self._print_if_not_quiet(
422
+ "End time: {}".format(datetime.now().time().strftime("%H:%M:%S")), quiet
423
+ )
424
+ return
425
+ except Exception as e:
426
+ # Notify the context that an exception has occurred
427
+ context.process_exception(e)
428
+
429
+ sleep(DELAY_IN_WAIT_FOR_SLEEP_LOOP)
430
+ check_break(2)
431
+
432
+ def _print_if_not_quiet(self, text: str, quiet: bool) -> None:
433
+ if not quiet:
434
+ print(text)
435
+
436
+ def wait_for_runstate(
437
+ self,
438
+ state: str,
439
+ maxwaitsecs: int = 3600,
440
+ onexit: bool = False,
441
+ quiet: bool = False,
442
+ ) -> None:
443
+ """
444
+ Wait for a given run state
445
+ Args:
446
+ state: the run state to wait for
447
+ maxwaitsecs: maximum time to wait
448
+ onexit: if True wait only as long as in transitional state;
449
+ False wait whatever the current state
450
+ quiet: This has been added in alignment with
451
+ genie_simulate_impl.wait_for_runstate()
452
+
453
+ Returns: nothing
454
+
455
+ """
456
+ time_delta = timedelta(seconds=maxwaitsecs)
457
+ state = state.upper().strip()
458
+ start_time = datetime.now()
459
+ while True:
460
+ # The sleep is to allow run control to set state before wait_for_runstate
461
+ # (it polls on 0.1s loop). This is
462
+ # in case user does cset(x, lim=...), waitfor("RUNNING") - without a wait this
463
+ # might not catch the run
464
+ # state change from run control to waiting.
465
+ sleep(0.3)
466
+ check_break(2)
467
+ curr = self.api.dae.get_run_state()
468
+ if onexit:
469
+ if curr != state and not self.api.dae.in_transition():
470
+ self.api.logger.log_info_msg("WAITFOR_RUNSTATE ONEXIT STATE EXITED")
471
+ break
472
+ else:
473
+ if curr == state:
474
+ self.api.logger.log_info_msg("WAITFOR_RUNSTATE STATE REACHED")
475
+ break
476
+ # Check for timeout
477
+ if datetime.now() - start_time >= time_delta:
478
+ self.api.logger.log_info_msg("WAITFOR_RUNSTATE TIMED OUT")
479
+ break
480
+
481
+ def _init_wait_time(
482
+ self,
483
+ seconds: float | int | None,
484
+ minutes: float | int | None,
485
+ hours: float | int | None,
486
+ quiet: bool = False,
487
+ timeout_msg: str = "",
488
+ ) -> None:
489
+ self.time_delta = get_time_delta(seconds, minutes, hours)
490
+ if self.time_delta is not None:
491
+ self.start_time = datetime.now()
492
+ self._print_if_not_quiet(
493
+ "Waiting for {} seconds {}".format(self.time_delta.total_seconds(), timeout_msg),
494
+ quiet,
495
+ )
496
+ else:
497
+ self.start_time = None
498
+
499
+ def _waiting_for_time(self) -> bool:
500
+ if (self.start_time is None) or (self.time_delta is None):
501
+ raise Exception("start_time and time_delta must not be None")
502
+ else:
503
+ if datetime.now() - self.start_time >= self.time_delta:
504
+ return False
505
+ else:
506
+ return True
507
+
508
+ def _init_wait_block(
509
+ self,
510
+ block: str | None,
511
+ value: WAITFOR_VALUE,
512
+ lowlimit: float | int | None,
513
+ highlimit: float | int | None,
514
+ quiet: bool = False,
515
+ timeout_msg: str = "",
516
+ ) -> None:
517
+ self.block = block
518
+ if self.block is None:
519
+ return
520
+ self.low, self.high = self._get_block_limits(value, lowlimit, highlimit)
521
+ if self.low is None and self.high is None:
522
+ raise Exception("No limit(s) set for {0}".format(block))
523
+ if self.low == self.high:
524
+ self._print_if_not_quiet(
525
+ "Waiting for {0}={1}{2}".format(str(block), str(self.low), timeout_msg), quiet
526
+ )
527
+ else:
528
+ self._print_if_not_quiet(
529
+ "Waiting for {0} (lowlimit={1}, highlimit={2}){3}".format(
530
+ str(block), str(self.low), str(self.high), timeout_msg
531
+ ),
532
+ quiet,
533
+ )
534
+
535
+ def _get_block_limits(
536
+ self, value: WAITFOR_VALUE, lowlimit: float | int | None, highlimit: float | int | None
537
+ ) -> tuple[WAITFOR_VALUE | None, WAITFOR_VALUE | None]:
538
+ low = None
539
+ high = None
540
+ if value is not None:
541
+ low = high = value
542
+ if isinstance(lowlimit, NUMERIC_TYPE):
543
+ low = lowlimit
544
+ if isinstance(highlimit, NUMERIC_TYPE):
545
+ high = highlimit
546
+ # Check low and high are round the correct way
547
+ if isinstance(low, NUMERIC_TYPE) and isinstance(high, NUMERIC_TYPE) and low > high:
548
+ low, high = high, low
549
+ print(
550
+ "WARNING: The highlimit and lowlimit have been \
551
+ swapped to lowlimit({}) and highlimit({})".format(low, high)
552
+ )
553
+ return low, high
554
+
555
+ def _block_has_waited_for_value(self) -> bool:
556
+ """
557
+ Return True if the block is above any low limit and below any high limit. In the case
558
+ of a string type it is if it is at the low limit.
559
+
560
+ :return: true of the block has the value that is being waited for; False otherwise
561
+ """
562
+ flag = True
563
+ currval = self.api.get_block_value(self.block)
564
+ if not isinstance(currval, NUMERIC_TYPE):
565
+ # pv is a string values so just test
566
+ flag = currval == self.low
567
+ else:
568
+ try:
569
+ if self.low is not None:
570
+ flag = currval >= float(self.low)
571
+ if self.high is not None:
572
+ flag = currval <= float(self.high) and flag
573
+ except ValueError:
574
+ # pv is a string values so just test
575
+ flag = currval == self.low
576
+ return not flag
File without changes