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.
- genie_python/.pylintrc +539 -0
- genie_python/__init__.py +1 -0
- genie_python/_version.py +16 -0
- genie_python/block_names.py +123 -0
- genie_python/channel_access_exceptions.py +45 -0
- genie_python/genie.py +2462 -0
- genie_python/genie_advanced.py +418 -0
- genie_python/genie_alerts.py +195 -0
- genie_python/genie_api_setup.py +451 -0
- genie_python/genie_blockserver.py +64 -0
- genie_python/genie_cachannel_wrapper.py +551 -0
- genie_python/genie_change_cache.py +151 -0
- genie_python/genie_dae.py +2219 -0
- genie_python/genie_epics_api.py +906 -0
- genie_python/genie_experimental_data.py +186 -0
- genie_python/genie_logging.py +200 -0
- genie_python/genie_p4p_wrapper.py +203 -0
- genie_python/genie_plot.py +77 -0
- genie_python/genie_pre_post_cmd_manager.py +21 -0
- genie_python/genie_pv_connection_protocol.py +36 -0
- genie_python/genie_script_checker.py +507 -0
- genie_python/genie_script_generator.py +212 -0
- genie_python/genie_simulate.py +69 -0
- genie_python/genie_simulate_impl.py +1265 -0
- genie_python/genie_startup.py +29 -0
- genie_python/genie_toggle_settings.py +58 -0
- genie_python/genie_wait_for_move.py +154 -0
- genie_python/genie_waitfor.py +576 -0
- genie_python/matplotlib_backend/__init__.py +0 -0
- genie_python/matplotlib_backend/ibex_websocket_backend.py +366 -0
- genie_python/mysql_abstraction_layer.py +272 -0
- genie_python/scanning_instrument_pylint_plugin.py +31 -0
- genie_python/testing_utils/__init__.py +4 -0
- genie_python/testing_utils/script_checker.py +63 -0
- genie_python/typings/CaChannel/CaChannel.pyi +893 -0
- genie_python/typings/CaChannel/__init__.pyi +9 -0
- genie_python/typings/CaChannel/_version.pyi +6 -0
- genie_python/typings/CaChannel/ca.pyi +31 -0
- genie_python/utilities.py +406 -0
- genie_python/version.py +6 -0
- genie_python-15.1.0.dist-info/LICENSE +28 -0
- genie_python-15.1.0.dist-info/METADATA +84 -0
- genie_python-15.1.0.dist-info/RECORD +45 -0
- genie_python-15.1.0.dist-info/WHEEL +5 -0
- genie_python-15.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import functools
|
|
5
|
+
import glob
|
|
6
|
+
import inspect
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
import traceback
|
|
12
|
+
from typing import Any, Callable, ParamSpec, TypeVar
|
|
13
|
+
|
|
14
|
+
import IPython
|
|
15
|
+
from decorator import getfullargspec
|
|
16
|
+
from IPython.core.completer import IPCompleter
|
|
17
|
+
|
|
18
|
+
from genie_python.utilities import get_correct_filepath_existing
|
|
19
|
+
|
|
20
|
+
P = ParamSpec("P")
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
# Determine whether to start in simulation mode
|
|
24
|
+
|
|
25
|
+
if "GENIE_SIMULATE" not in os.environ or os.environ["GENIE_SIMULATE"] != "1":
|
|
26
|
+
from genie_python.genie_epics_api import API
|
|
27
|
+
else:
|
|
28
|
+
print("\n=========== RUNNING IN SIMULATION MODE ===========\n")
|
|
29
|
+
from genie_python.genie_simulate_impl import API
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Windows specific stuff
|
|
33
|
+
if os.name == "nt":
|
|
34
|
+
# Needed for correcting file paths
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# INITIALISATION CODE - DO NOT DELETE
|
|
38
|
+
try:
|
|
39
|
+
# If __api does not exist or is None then we need to create it.
|
|
40
|
+
if __api is None: # noqa: F821 __api currently gets added to globals which is bad.
|
|
41
|
+
raise Exception("API does not exist")
|
|
42
|
+
except Exception:
|
|
43
|
+
# This should only get called the first time genie is imported
|
|
44
|
+
my_pv_prefix = None
|
|
45
|
+
if "MYPVPREFIX" in os.environ:
|
|
46
|
+
my_pv_prefix = os.environ["MYPVPREFIX"]
|
|
47
|
+
__api = API(my_pv_prefix, globals())
|
|
48
|
+
else:
|
|
49
|
+
print("No instrument specified - loading local instrument")
|
|
50
|
+
__api = API(None, globals())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# END INITIALISATION CODE
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def set_user_script_dir(*directory: str | list[str]) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Set the user script directory, ensuring it ends in a slash
|
|
59
|
+
Args:
|
|
60
|
+
directory: directory to set it to, or list of directories
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
global USER_SCRIPT_DIR
|
|
64
|
+
|
|
65
|
+
requested_dir = os.path.join(*directory)
|
|
66
|
+
directory_name = requested_dir
|
|
67
|
+
dirs_to_create = []
|
|
68
|
+
|
|
69
|
+
base_name = None
|
|
70
|
+
while base_name != "":
|
|
71
|
+
try:
|
|
72
|
+
directory_name = get_correct_filepath_existing(directory_name)
|
|
73
|
+
break
|
|
74
|
+
except OSError:
|
|
75
|
+
directory_name, base_name = os.path.split(directory_name.strip(r"\\/"))
|
|
76
|
+
dirs_to_create.append(base_name)
|
|
77
|
+
|
|
78
|
+
# Got to a single directory which does not exist
|
|
79
|
+
if base_name == "":
|
|
80
|
+
raise OSError("Script dir does not exist and can not be created: {}".format(requested_dir))
|
|
81
|
+
|
|
82
|
+
for dir_to_create in reversed(dirs_to_create):
|
|
83
|
+
directory_name = os.path.join(directory_name, dir_to_create)
|
|
84
|
+
os.mkdir(directory_name)
|
|
85
|
+
directory_name = get_correct_filepath_existing(directory_name)
|
|
86
|
+
|
|
87
|
+
if len(directory_name) > 1 and directory_name[-1] != "/":
|
|
88
|
+
directory_name += "/"
|
|
89
|
+
USER_SCRIPT_DIR = directory_name
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_user_script_dir() -> str:
|
|
93
|
+
"""
|
|
94
|
+
Returns: the user script directory
|
|
95
|
+
"""
|
|
96
|
+
global USER_SCRIPT_DIR
|
|
97
|
+
return USER_SCRIPT_DIR
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
set_user_script_dir("C:/scripts/")
|
|
102
|
+
except Exception:
|
|
103
|
+
USER_SCRIPT_DIR = ""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# TAB COMPLETE CODE
|
|
107
|
+
|
|
108
|
+
LOAD_SCRIPT_COMMAND = "load_script("
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class LoadScriptCompleter:
|
|
112
|
+
"""
|
|
113
|
+
A class holding a custom complete function which replaces the normal completion
|
|
114
|
+
function of the ipython completer. We are replacing it and not just adding it
|
|
115
|
+
as a custom completer because it allows us to return just paths after
|
|
116
|
+
load_script and no other completions from the IPython backend. If this
|
|
117
|
+
does not have load script in it will return what the original one did.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, original_complete_fn: Callable) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Initialise.
|
|
123
|
+
Args:
|
|
124
|
+
original_complete_fn: function which was originally used to complete
|
|
125
|
+
"""
|
|
126
|
+
self._original_complete = original_complete_fn
|
|
127
|
+
self.is_pydev = False
|
|
128
|
+
|
|
129
|
+
def _get_completion_paths(self, line_buffer: str) -> tuple[list[str], str, bool, bool]:
|
|
130
|
+
"""
|
|
131
|
+
Given a line buffer, get the relevant filename completions for a g.load_script command
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
line_buffer: the line to complete
|
|
135
|
+
Returns:
|
|
136
|
+
tuple (list[str], str, bool, bool) - a list of the completions,
|
|
137
|
+
the search path which was used, whether the path was already quoted,
|
|
138
|
+
whether the path was absolute (e.g. c:/scripts/myscript.py)
|
|
139
|
+
or relative (e.g. myscript.py)
|
|
140
|
+
"""
|
|
141
|
+
# cope with None as search path
|
|
142
|
+
if line_buffer is None:
|
|
143
|
+
line_buffer = ""
|
|
144
|
+
|
|
145
|
+
# get file path
|
|
146
|
+
search_path = line_buffer.split(LOAD_SCRIPT_COMMAND)[-1]
|
|
147
|
+
|
|
148
|
+
had_quotes = search_path.startswith("'") or search_path.startswith('"')
|
|
149
|
+
if had_quotes:
|
|
150
|
+
search_path = search_path[1:]
|
|
151
|
+
|
|
152
|
+
is_absolute = os.path.isabs(search_path)
|
|
153
|
+
if not is_absolute:
|
|
154
|
+
search_path = "{}{}".format(get_user_script_dir(), search_path)
|
|
155
|
+
|
|
156
|
+
# search and return results
|
|
157
|
+
paths = glob.glob("{}*".format(search_path))
|
|
158
|
+
|
|
159
|
+
return paths, search_path, had_quotes, is_absolute
|
|
160
|
+
|
|
161
|
+
def standalone_complete(self, completer: IPCompleter, text: str | None = None) -> list[str]:
|
|
162
|
+
"""
|
|
163
|
+
Find completions for the given text and line context.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
completer : IPyCompleter
|
|
168
|
+
The IPython completer instance being used to perform this completion.
|
|
169
|
+
|
|
170
|
+
text : string, optional
|
|
171
|
+
Text to perform the completion on. Line buffer
|
|
172
|
+
is always used except when using the default completer.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
completion : list
|
|
177
|
+
A list of completion matches.
|
|
178
|
+
"""
|
|
179
|
+
line_buffer = completer.line_buffer
|
|
180
|
+
if LOAD_SCRIPT_COMMAND not in line_buffer:
|
|
181
|
+
return []
|
|
182
|
+
else:
|
|
183
|
+
paths, search_path, had_quotes, was_absolute = self._get_completion_paths(line_buffer)
|
|
184
|
+
|
|
185
|
+
# add quote if it is missing
|
|
186
|
+
if had_quotes:
|
|
187
|
+
quote = ""
|
|
188
|
+
else:
|
|
189
|
+
quote = '"'
|
|
190
|
+
|
|
191
|
+
# if the path isn't absolute it should refer to script dir
|
|
192
|
+
if was_absolute:
|
|
193
|
+
len_added_user_script = 0
|
|
194
|
+
else:
|
|
195
|
+
len_added_user_script = len(get_user_script_dir())
|
|
196
|
+
|
|
197
|
+
# Console expects the whole path
|
|
198
|
+
# return / to avoid a quoting issue with \ in paths and do not include the script dir
|
|
199
|
+
completion = [
|
|
200
|
+
"{}{}".format(quote, path.replace("\\", "/")[len_added_user_script:])
|
|
201
|
+
for path in paths
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
return completion
|
|
205
|
+
|
|
206
|
+
def pydev_complete(
|
|
207
|
+
self, text: str | None = None, line_buffer: str | None = None, cursor_pos: int | None = None
|
|
208
|
+
) -> tuple[str, list[str]]:
|
|
209
|
+
"""
|
|
210
|
+
Find completions for the given text and line context.
|
|
211
|
+
Note that both the text and the line_buffer are optional, but at least
|
|
212
|
+
one of them must be given.
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
text : string, optional
|
|
216
|
+
Text to perform the completion on. Line buffer
|
|
217
|
+
is always used except when using the default completer.
|
|
218
|
+
line_buffer : string, optional
|
|
219
|
+
line to match
|
|
220
|
+
cursor_pos : int, optional
|
|
221
|
+
Index of the cursor in the full line buffer. Should be provided by
|
|
222
|
+
remote frontends where kernel has no access to frontend state.
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
text : str
|
|
226
|
+
Text that was actually used in the completion.
|
|
227
|
+
matches : list
|
|
228
|
+
A list of completion matches.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
if LOAD_SCRIPT_COMMAND not in line_buffer:
|
|
232
|
+
match, completion = self._original_complete(text, line_buffer, cursor_pos)
|
|
233
|
+
else:
|
|
234
|
+
paths, search_path, _, _ = self._get_completion_paths(line_buffer)
|
|
235
|
+
match = search_path
|
|
236
|
+
|
|
237
|
+
# PyDev expects just the end part of the expression back broken at the last punctuation
|
|
238
|
+
completion = []
|
|
239
|
+
for path in paths:
|
|
240
|
+
# py dev replaces back to the last splitting character.
|
|
241
|
+
# Find that in the line buffer. E.g.
|
|
242
|
+
# I have a script called play.py
|
|
243
|
+
# I type C:/script/pl
|
|
244
|
+
# This function will add only play.py to the list
|
|
245
|
+
# (back to the last splitting character, i.e. /)
|
|
246
|
+
# There is a special case when it is just load_script(
|
|
247
|
+
if line_buffer.endswith("load_script("):
|
|
248
|
+
line_buffer_back_to_last_splitting_character = '"'
|
|
249
|
+
else:
|
|
250
|
+
line_buffer_back_to_last_splitting_character = re.split(
|
|
251
|
+
r"[/:\\ .-]", line_buffer
|
|
252
|
+
)[-1]
|
|
253
|
+
|
|
254
|
+
# find extra path to add
|
|
255
|
+
len_of_overlap = len(search_path)
|
|
256
|
+
completion_path = path[len_of_overlap:].replace("\\", "/")
|
|
257
|
+
|
|
258
|
+
# assemble the auto replace
|
|
259
|
+
completion.append(
|
|
260
|
+
"{}{}".format(line_buffer_back_to_last_splitting_character, completion_path)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return match, completion
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class PyDevComplete:
|
|
267
|
+
"""
|
|
268
|
+
In PyDev the completer is at a higher level, it uses the ipython
|
|
269
|
+
completer above and adds extra completion
|
|
270
|
+
we need to override this to return just the paths and
|
|
271
|
+
not the other possible completions.
|
|
272
|
+
This is not nice code because I am manipulating
|
|
273
|
+
private methods but there is no other way to do this.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def __init__(self, original_function: Callable) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Initialise
|
|
279
|
+
Args:
|
|
280
|
+
original_function: the original completion function that we will call
|
|
281
|
+
"""
|
|
282
|
+
self.original_function = original_function
|
|
283
|
+
|
|
284
|
+
def just_path_on_load(self, text: str, act_tok: Any) -> list[str]:
|
|
285
|
+
"""
|
|
286
|
+
Returns completions for load on a path if load_script in path otherwise returns as before.
|
|
287
|
+
Will replace .metadata\.plugins\org.eclipse.pde.core\.bundle_pool\plugins\
|
|
288
|
+
org.python.pydev_5.9.2.201708151115\pysrc\_pydev_bundle\pydev_ipython_console_011.py
|
|
289
|
+
|
|
290
|
+
This functions will filter out the pydev completions if the line contains load_script.
|
|
291
|
+
It know which are the pydev ones because they are marked with the type '11'.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
text: text to complete
|
|
295
|
+
act_tok: token, used only in original completion
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
pydev completions
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
# This is the completion type assigned to all ipython completions in pydev
|
|
302
|
+
ipython_completer_completion_type = "11"
|
|
303
|
+
ans = self.original_function(text, act_tok)
|
|
304
|
+
# returns list of tuples (completion, py doc, ?, type)
|
|
305
|
+
if LOAD_SCRIPT_COMMAND in text:
|
|
306
|
+
ans = [an for an in ans if an[3] == ipython_completer_completion_type]
|
|
307
|
+
return ans
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
# replace the original completer in ipython with the one above
|
|
312
|
+
ipy_completer = IPython.get_ipython().Completer.complete
|
|
313
|
+
_load_script_completer = LoadScriptCompleter(ipy_completer)
|
|
314
|
+
IPython.get_ipython().set_custom_completer(_load_script_completer.standalone_complete, 0)
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
# Replace the old completer in pydev with the new completer above and turns on pydev
|
|
318
|
+
from _pydev_bundle.pydev_ipython_console_011 import _PyDevFrontEndContainer
|
|
319
|
+
|
|
320
|
+
_PyDevFrontEndContainer._instance.getCompletions = PyDevComplete(
|
|
321
|
+
_PyDevFrontEndContainer._instance.getCompletions
|
|
322
|
+
).just_path_on_load
|
|
323
|
+
_load_script_completer.is_pydev = True
|
|
324
|
+
# Pydev does not honor IPython custom_completer, so use an old-style function replace
|
|
325
|
+
# (this no longer works in standalone python window so cannot use same style for both cases)
|
|
326
|
+
IPython.get_ipython().Completer.complete = _load_script_completer.pydev_complete
|
|
327
|
+
except ImportError:
|
|
328
|
+
pass
|
|
329
|
+
# this means we are not in pydev
|
|
330
|
+
except AttributeError:
|
|
331
|
+
print("ERROR: IPython does not exist, auto complete not installed")
|
|
332
|
+
|
|
333
|
+
# END TAB COMPLETE
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def usercommand(func: Callable[P, T]) -> Callable[P, T]:
|
|
337
|
+
"""
|
|
338
|
+
Decorator that marks a function as a user command (e.g. for NICOS).
|
|
339
|
+
"""
|
|
340
|
+
func.is_usercommand = True
|
|
341
|
+
func.is_hidden = False
|
|
342
|
+
return func
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def helparglist(args: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
|
346
|
+
"""
|
|
347
|
+
Decorator that supplies a custom argument list to be displayed by
|
|
348
|
+
a help (e.g. for NICOS).
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
def deco(func: Callable[P, T]) -> Callable[P, T]:
|
|
352
|
+
func.help_arglist = args # type: ignore
|
|
353
|
+
return func
|
|
354
|
+
|
|
355
|
+
return deco
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def log_command_and_handle_exception(f: Callable[P, T]) -> Callable[P, T]:
|
|
359
|
+
"""
|
|
360
|
+
Decorator that will log the command when run and will
|
|
361
|
+
catch all exceptions to be handled by genie.
|
|
362
|
+
|
|
363
|
+
Note: _func_name_unlikely_to_be_reused must be called something that a user will not
|
|
364
|
+
accidentally put into a genie function
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
@functools.wraps(f)
|
|
368
|
+
def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
369
|
+
log_args = {"kwargs": kwargs}
|
|
370
|
+
arg_names = getfullargspec(f).args
|
|
371
|
+
# If we can get the argument names the include them in the log
|
|
372
|
+
if len(arg_names) >= len(args):
|
|
373
|
+
log_args["args"] = dict(zip(arg_names, args))
|
|
374
|
+
else:
|
|
375
|
+
log_args["args"] = args
|
|
376
|
+
|
|
377
|
+
command_exception = None
|
|
378
|
+
start = time.time()
|
|
379
|
+
try:
|
|
380
|
+
return_value = f(*args, **kwargs)
|
|
381
|
+
return return_value
|
|
382
|
+
except Exception as e:
|
|
383
|
+
command_exception = traceback.format_exc()
|
|
384
|
+
_handle_exception(e)
|
|
385
|
+
return None # type: ignore
|
|
386
|
+
finally:
|
|
387
|
+
end = time.time()
|
|
388
|
+
time_taken = end - start
|
|
389
|
+
# hack to allow tests to pass. linux has microsecond resolution,
|
|
390
|
+
# Windows only as millisecond but can be >1ms occasionaly and
|
|
391
|
+
# mock is expecting 0.0
|
|
392
|
+
if time_taken < 0.002:
|
|
393
|
+
time_taken = 0.0
|
|
394
|
+
__api.logger.log_command(f.__name__, log_args, command_exception, time_taken=time_taken)
|
|
395
|
+
if command_exception is not None:
|
|
396
|
+
__api.logger.log_command_error_msg(f.__name__, command_exception)
|
|
397
|
+
|
|
398
|
+
decorator.__signature__ = inspect.signature(f) # type: ignore
|
|
399
|
+
return decorator
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _print_error_message(message: str) -> None:
|
|
403
|
+
"""
|
|
404
|
+
Print the error message to screen.
|
|
405
|
+
"""
|
|
406
|
+
if os.name == "nt":
|
|
407
|
+
# Is windows
|
|
408
|
+
class ConsoleScreenBufferInfo(ctypes.Structure):
|
|
409
|
+
_fields_ = [
|
|
410
|
+
("dwSize", ctypes.wintypes._COORD), # type: ignore
|
|
411
|
+
("dwCursorPosition", ctypes.wintypes._COORD), # type: ignore
|
|
412
|
+
("wAttributes", ctypes.c_ushort), # type: ignore
|
|
413
|
+
("srWindow", ctypes.wintypes._SMALL_RECT), # type: ignore
|
|
414
|
+
("dwMaximumWindowSize", ctypes.wintypes._COORD), # type: ignore
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
std_output_handle = -11
|
|
418
|
+
stdout_handle = ctypes.windll.kernel32.GetStdHandle(std_output_handle)
|
|
419
|
+
csbi = ConsoleScreenBufferInfo()
|
|
420
|
+
ctypes.windll.kernel32.GetConsoleScreenBufferInfo(stdout_handle, ctypes.byref(csbi))
|
|
421
|
+
old_attrs = csbi.wAttributes
|
|
422
|
+
ctypes.windll.kernel32.SetConsoleTextAttribute(stdout_handle, 12)
|
|
423
|
+
print("ERROR: " + message)
|
|
424
|
+
ctypes.windll.kernel32.SetConsoleTextAttribute(stdout_handle, old_attrs)
|
|
425
|
+
else:
|
|
426
|
+
# Non-windows
|
|
427
|
+
print("\033[91m" + "ERROR: " + message + "\033[0m")
|
|
428
|
+
# Log it
|
|
429
|
+
__api.logger.log_error_msg(message)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
_exceptions_raised = False
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _handle_exception(exception: Exception | None = None, message: str | None = None) -> None:
|
|
436
|
+
"""
|
|
437
|
+
Handles any exception in the way we want.
|
|
438
|
+
"""
|
|
439
|
+
if exception is not None:
|
|
440
|
+
if _exceptions_raised:
|
|
441
|
+
raise exception
|
|
442
|
+
if message is not None:
|
|
443
|
+
_print_error_message(message)
|
|
444
|
+
else:
|
|
445
|
+
traceback.print_exc(file=sys.stderr)
|
|
446
|
+
elif message is not None:
|
|
447
|
+
_print_error_message(message)
|
|
448
|
+
if _exceptions_raised:
|
|
449
|
+
raise Exception(message)
|
|
450
|
+
else:
|
|
451
|
+
_print_error_message("UNSPECIFIED")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from builtins import object
|
|
5
|
+
|
|
6
|
+
from genie_python.utilities import compress_and_hex, dehex_decompress_and_dejson
|
|
7
|
+
|
|
8
|
+
# Prefix for block server pvs
|
|
9
|
+
PV_BLOCK_NAMES = "BLOCKNAMES"
|
|
10
|
+
BLOCK_SERVER_PREFIX = "CS:BLOCKSERVER:"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _blockserver_retry(func):
|
|
14
|
+
def wrapper(*args, **kwargs):
|
|
15
|
+
while True:
|
|
16
|
+
try:
|
|
17
|
+
return func(*args, **kwargs)
|
|
18
|
+
except Exception as e:
|
|
19
|
+
print(
|
|
20
|
+
"Exception thrown from {}: {}, will retry in 15 seconds".format(
|
|
21
|
+
func.__name__, e.__class__.__name__
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
time.sleep(15)
|
|
25
|
+
|
|
26
|
+
return wrapper
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BlockServer(object):
|
|
30
|
+
def __init__(self, api):
|
|
31
|
+
self.api = api
|
|
32
|
+
|
|
33
|
+
def _get_pv_value(self, pv, as_string=False):
|
|
34
|
+
"""Just a convenient wrapper for calling the api's get_pv_value method"""
|
|
35
|
+
return self.api.get_pv_value(self.api.prefix_pv_name(pv), as_string)
|
|
36
|
+
|
|
37
|
+
def _set_pv_value(self, pv, value, wait=False):
|
|
38
|
+
"""Just a convenient wrapper for calling the api's set_pv_value method"""
|
|
39
|
+
return self.api.set_pv_value(self.api.prefix_pv_name(pv), value, wait)
|
|
40
|
+
|
|
41
|
+
@_blockserver_retry
|
|
42
|
+
def get_sample_par_names(self):
|
|
43
|
+
"""Get the current sample parameter names as a list."""
|
|
44
|
+
# Get the names from the blockserver
|
|
45
|
+
raw = self._get_pv_value(BLOCK_SERVER_PREFIX + "SAMPLE_PARS", True)
|
|
46
|
+
return dehex_decompress_and_dejson(raw)
|
|
47
|
+
|
|
48
|
+
@_blockserver_retry
|
|
49
|
+
def get_beamline_par_names(self):
|
|
50
|
+
"""Get the current beamline parameter names as a list."""
|
|
51
|
+
# Get the names from the blockserver
|
|
52
|
+
raw = self._get_pv_value(BLOCK_SERVER_PREFIX + "BEAMLINE_PARS", True)
|
|
53
|
+
return dehex_decompress_and_dejson(raw)
|
|
54
|
+
|
|
55
|
+
@_blockserver_retry
|
|
56
|
+
def get_runcontrol_settings(self):
|
|
57
|
+
"""Get the current run-control settings."""
|
|
58
|
+
raw = self._get_pv_value(BLOCK_SERVER_PREFIX + "GET_RC_PARS", True)
|
|
59
|
+
return dehex_decompress_and_dejson(raw)
|
|
60
|
+
|
|
61
|
+
def reload_current_config(self):
|
|
62
|
+
"""Reload the current configuration."""
|
|
63
|
+
raw = compress_and_hex("1")
|
|
64
|
+
self._set_pv_value(BLOCK_SERVER_PREFIX + "RELOAD_CURRENT_CONFIG", raw, True)
|