genie-python 15.1.0rc1__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/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 +545 -0
- genie_python/genie_change_cache.py +151 -0
- genie_python/genie_dae.py +2218 -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/run_tests.py +56 -0
- genie_python/scanning_instrument_pylint_plugin.py +31 -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 +1 -0
- genie_python-15.1.0rc1.dist-info/LICENSE +28 -0
- genie_python-15.1.0rc1.dist-info/METADATA +95 -0
- genie_python-15.1.0rc1.dist-info/RECORD +43 -0
- genie_python-15.1.0rc1.dist-info/WHEEL +5 -0
- genie_python-15.1.0rc1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import TYPE_CHECKING, Protocol, Tuple, runtime_checkable
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from genie_python.genie import PVValue
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@runtime_checkable
|
|
9
|
+
class GeniePvConnectionProtocol(Protocol):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def set_pv_value(
|
|
12
|
+
name: str, value: "PVValue", wait: bool, timeout: float, safe_not_quick: bool
|
|
13
|
+
) -> None: ...
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def clear_monitor(name: str, timeout: float) -> None: ...
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def get_pv_value(
|
|
20
|
+
name: str, to_string: bool, timeout: float, use_numpy: bool | None
|
|
21
|
+
) -> "PVValue": ...
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def get_pv_timestamp(name: str, timeout: float) -> Tuple[int, int]: ...
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def pv_exists(name: str, timeout: float) -> bool: ...
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def add_monitor(
|
|
31
|
+
name: str,
|
|
32
|
+
call_back_function: "Callable[[PVValue, str, str], None]",
|
|
33
|
+
link_alarm_on_disconnect: bool = True,
|
|
34
|
+
to_string: bool = False,
|
|
35
|
+
use_numpy: bool | None = None,
|
|
36
|
+
) -> Callable[[], None]: ...
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import site
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import sysconfig
|
|
11
|
+
import unicodedata
|
|
12
|
+
from builtins import object
|
|
13
|
+
from io import StringIO, open
|
|
14
|
+
from typing import Iterable
|
|
15
|
+
|
|
16
|
+
from astroid import MANAGER, nodes
|
|
17
|
+
from pylint.lint import Run
|
|
18
|
+
from pylint.reporters.text import TextReporter
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ScriptChecker(object):
|
|
22
|
+
"""
|
|
23
|
+
Check Scripts for common errors
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def _find_regex(self, variable: str) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Sets the function to find any of the symbols listed below
|
|
29
|
+
Args:
|
|
30
|
+
variable: the assigned string from the search function
|
|
31
|
+
Return:
|
|
32
|
+
the string to be used in the regex search function
|
|
33
|
+
"""
|
|
34
|
+
assignment_regex = "[\|\&\^\/\+\-\*\%]?=[^=]"
|
|
35
|
+
regex = r"\b{0}[.][\w\s]*" + assignment_regex + r"|\b{0}[\s]*" + assignment_regex
|
|
36
|
+
return regex.format(variable)
|
|
37
|
+
|
|
38
|
+
def _check_g_inst_name(self, line: str, line_no: int) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Checks a line of a script for assignments of variables named g or inst
|
|
41
|
+
Args:
|
|
42
|
+
line: the line to check
|
|
43
|
+
line_no: the line number
|
|
44
|
+
Return:
|
|
45
|
+
If an error is found appropriate warning string else
|
|
46
|
+
if no error found an empty string
|
|
47
|
+
"""
|
|
48
|
+
g_error = re.search(self._find_regex("g"), line)
|
|
49
|
+
if g_error:
|
|
50
|
+
return "W: {line_no}: 'g' assignment in line {line_no}".format(line_no=line_no)
|
|
51
|
+
|
|
52
|
+
inst_error = re.search(self._find_regex("inst"), line)
|
|
53
|
+
if inst_error:
|
|
54
|
+
return "W: {line_no}: 'inst' assignment in line {line_no}".format(line_no=line_no)
|
|
55
|
+
|
|
56
|
+
return ""
|
|
57
|
+
|
|
58
|
+
def check_script_lines(self, lines: Iterable[str]) -> list[str]:
|
|
59
|
+
"""
|
|
60
|
+
Check the lines of the script for possible errors
|
|
61
|
+
Args:
|
|
62
|
+
lines: iterable of lines to check
|
|
63
|
+
Returns: error in the script; empty list if none
|
|
64
|
+
"""
|
|
65
|
+
reassignment_warnings = []
|
|
66
|
+
line_no = 0
|
|
67
|
+
for line in lines:
|
|
68
|
+
line_no += 1
|
|
69
|
+
warning = self._check_g_inst_name(line, line_no)
|
|
70
|
+
if len(warning) != 0:
|
|
71
|
+
reassignment_warnings.append(warning)
|
|
72
|
+
|
|
73
|
+
return reassignment_warnings
|
|
74
|
+
|
|
75
|
+
def _can_cache_module(self, module_name: str, module: nodes.Module) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Determines whether a module can be cached or whether the linter
|
|
78
|
+
should re-examine it's contents each time.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
module_name: the name of the module
|
|
82
|
+
module: the astroid module object
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
True if the module can safely be cached, False otherwise
|
|
86
|
+
"""
|
|
87
|
+
# Always allow builtin modules to be cached.
|
|
88
|
+
if module_name in sys.builtin_module_names:
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
# Allow modules defined in the site packages directories to be cached as
|
|
92
|
+
# they are unlikely to change at runtime
|
|
93
|
+
if module.file is not None and any(
|
|
94
|
+
module.file.startswith(site_package_dir) for site_package_dir in site.getsitepackages()
|
|
95
|
+
):
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
# Other modules are probably user-defined e.g. inst scripts,
|
|
99
|
+
# shared scripts, user scripts. Don't cache these.
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def _clean_astroid_cache(self) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Cleans user-defined scripts out of the astroid cache.
|
|
105
|
+
"""
|
|
106
|
+
new_cache = {}
|
|
107
|
+
|
|
108
|
+
for module_name, module in MANAGER.astroid_cache.items():
|
|
109
|
+
if self._can_cache_module(module_name, module):
|
|
110
|
+
new_cache[module_name] = module
|
|
111
|
+
|
|
112
|
+
MANAGER.astroid_cache = new_cache
|
|
113
|
+
|
|
114
|
+
class _TemporaryPyrightConfig:
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
config_path: str,
|
|
118
|
+
instrument_name: str,
|
|
119
|
+
additional_include_paths: list[str] | None = None,
|
|
120
|
+
) -> None:
|
|
121
|
+
if additional_include_paths is None:
|
|
122
|
+
additional_include_paths = []
|
|
123
|
+
|
|
124
|
+
self.config_path = config_path
|
|
125
|
+
self.config_name = "pyrightconfig.json"
|
|
126
|
+
self.json_write = {
|
|
127
|
+
"include": [
|
|
128
|
+
".",
|
|
129
|
+
],
|
|
130
|
+
"extraPaths": [
|
|
131
|
+
os.path.join(sysconfig.get_paths()["purelib"]),
|
|
132
|
+
os.path.join(sysconfig.get_paths()["platlib"]),
|
|
133
|
+
os.path.join("C:\\", "Instrument", "scripts"),
|
|
134
|
+
os.path.join(
|
|
135
|
+
"C:\\", "Instrument", "settings", "config", instrument_name, "Python"
|
|
136
|
+
),
|
|
137
|
+
os.path.join("U:\\", "scripts"),
|
|
138
|
+
os.path.join("U:\\"),
|
|
139
|
+
],
|
|
140
|
+
"exclude": [
|
|
141
|
+
"**/node_modules",
|
|
142
|
+
"**/__pycache__",
|
|
143
|
+
],
|
|
144
|
+
"typeCheckingMode": "basic",
|
|
145
|
+
"reportUnusedVariable": False,
|
|
146
|
+
"reportOptionalMemberAccess": False,
|
|
147
|
+
"reportOptionalSubscript": False,
|
|
148
|
+
"reportOptionalCall": False,
|
|
149
|
+
"reportOptionalIterable": False,
|
|
150
|
+
"reportUnboundVariable": False,
|
|
151
|
+
"reportUndefinedVariable ": False,
|
|
152
|
+
# Errors such as these will be caught by pylint before pyright, so no need to report
|
|
153
|
+
"reportMissingImports": False,
|
|
154
|
+
# Errors such as these will be caught by pylint before pyright, so no need to report
|
|
155
|
+
"strictParameterNoneValue": False,
|
|
156
|
+
"reportOptionalOperand": False,
|
|
157
|
+
"pythonPlatform": "Windows",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for path in additional_include_paths:
|
|
161
|
+
self.json_write["extraPaths"].append(path)
|
|
162
|
+
|
|
163
|
+
def __enter__(self) -> None:
|
|
164
|
+
self._filename = os.path.join(self.config_path, self.config_name)
|
|
165
|
+
with open(self._filename, "w") as f:
|
|
166
|
+
f.write(json.dumps(self.json_write))
|
|
167
|
+
|
|
168
|
+
def __exit__(self, exc_type: None, exc_value: None, exc_traceback: None) -> None:
|
|
169
|
+
os.unlink(self._filename)
|
|
170
|
+
|
|
171
|
+
def pyright_script_checker(
|
|
172
|
+
self, script_path: str, instrument_name: str, pyright_additional_include: list[str]
|
|
173
|
+
) -> tuple[list[str], list[str]]:
|
|
174
|
+
"""
|
|
175
|
+
Makes a call to pyright to do a static analyis of the script.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
script_name: The path to the selected user script
|
|
179
|
+
instrument_name: The instrument name in
|
|
180
|
+
C:\\Instrument\\Settings\\config\\[instrument_name]\\Python
|
|
181
|
+
Returns:
|
|
182
|
+
A list of warnings and a list of errors.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
script_dir = os.path.dirname(script_path)
|
|
186
|
+
errors = []
|
|
187
|
+
warnings = []
|
|
188
|
+
|
|
189
|
+
# 1 - Checks to see if pyrightconfig.json is present
|
|
190
|
+
# under the same directory as the selected script
|
|
191
|
+
# 2 - if not then copy json into dir
|
|
192
|
+
# 3 - Run pyright --project C:/[path_to_script_dir] C:/[path_to_script_dir]/[script].py
|
|
193
|
+
# 4 - reads from json output and returns appropriate errors/warnings/nothing if OK
|
|
194
|
+
|
|
195
|
+
with self._TemporaryPyrightConfig(script_dir, instrument_name, pyright_additional_include):
|
|
196
|
+
cmd = [
|
|
197
|
+
sys.executable,
|
|
198
|
+
"-m",
|
|
199
|
+
"pyright",
|
|
200
|
+
"--project",
|
|
201
|
+
script_dir,
|
|
202
|
+
"--outputjson",
|
|
203
|
+
script_path,
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
pr_result = subprocess.run(args=cmd, capture_output=True, text=True, encoding="utf-8")
|
|
207
|
+
json_out = unicodedata.normalize("NFKD", pr_result.stdout)
|
|
208
|
+
|
|
209
|
+
json_data = json.loads(json_out)
|
|
210
|
+
|
|
211
|
+
# for each diagnostic, if severity is error then
|
|
212
|
+
# add message to error array else add to warning array
|
|
213
|
+
for diagnostic in json_data["generalDiagnostics"]:
|
|
214
|
+
start = diagnostic["range"]["start"]
|
|
215
|
+
if not diagnostic["rule"] == "reportUndefinedVariable": ### CHANGE
|
|
216
|
+
if diagnostic["severity"] == "error":
|
|
217
|
+
errors += [
|
|
218
|
+
f"E: {start['line'] + 1}: "
|
|
219
|
+
f"{diagnostic['message']} [{diagnostic['rule']}]"
|
|
220
|
+
]
|
|
221
|
+
else:
|
|
222
|
+
warnings += [
|
|
223
|
+
f"W: {start['line'] + 1}: "
|
|
224
|
+
f"{diagnostic['message']} [{diagnostic['rule']}]"
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
return warnings, errors
|
|
228
|
+
|
|
229
|
+
def check_for_tabs(
|
|
230
|
+
self,
|
|
231
|
+
lines_list: list[str],
|
|
232
|
+
error_line_numbers: list[int],
|
|
233
|
+
lines_containing_errors: list[str],
|
|
234
|
+
) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Searches for tabs in script file. Tells user to convert tabs to 4 space characters.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
lines_list: List of all lines in script.
|
|
240
|
+
error_line_numbers: List of line numbers which have errors.
|
|
241
|
+
lines_containing_errors: List of lines that contain errors.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
tab_found = False
|
|
245
|
+
for i, line in enumerate(lines_list, start=1):
|
|
246
|
+
if i in error_line_numbers:
|
|
247
|
+
lines_containing_errors.append(line.strip())
|
|
248
|
+
|
|
249
|
+
tab_line = re.search(r"\t", line)
|
|
250
|
+
if tab_line:
|
|
251
|
+
tab_found = True
|
|
252
|
+
|
|
253
|
+
if tab_found:
|
|
254
|
+
print(
|
|
255
|
+
"Tab characters found in file, for portability convert tabs to 4 space characters."
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def check_script(
|
|
259
|
+
self,
|
|
260
|
+
script_name: str,
|
|
261
|
+
instrument_name: str,
|
|
262
|
+
warnings_as_error: bool = False,
|
|
263
|
+
no_pyright: bool = False,
|
|
264
|
+
no_pylint: bool = False,
|
|
265
|
+
pyright_additional_include: list[str] | None = None,
|
|
266
|
+
) -> list[str]:
|
|
267
|
+
"""
|
|
268
|
+
Check a script for common errors.
|
|
269
|
+
Args:
|
|
270
|
+
script_name: filename of the script
|
|
271
|
+
instrument_name: Full instrument name
|
|
272
|
+
warnings_as_error: True treat warnings as errors; False otherwise
|
|
273
|
+
|
|
274
|
+
Returns: error messages list; empty list if there are no errors
|
|
275
|
+
"""
|
|
276
|
+
errors_output = StringIO()
|
|
277
|
+
|
|
278
|
+
# We need to clean the cache so that we pick up changes in instrument scripts
|
|
279
|
+
self._clean_astroid_cache()
|
|
280
|
+
|
|
281
|
+
warnings = []
|
|
282
|
+
errors = []
|
|
283
|
+
|
|
284
|
+
dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
285
|
+
|
|
286
|
+
if no_pylint and no_pyright:
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
elif not no_pylint:
|
|
290
|
+
pylint_path = os.path.join(dir_path, ".pylintrc")
|
|
291
|
+
|
|
292
|
+
with open(script_name) as f:
|
|
293
|
+
reassignment_warnings = self.check_script_lines(f)
|
|
294
|
+
warnings.extend(reassignment_warnings)
|
|
295
|
+
|
|
296
|
+
inst_file_path = os.path.join(
|
|
297
|
+
"C:\\", "Instrument", "Settings", "config", instrument_name, "Python"
|
|
298
|
+
)
|
|
299
|
+
init_hook = (
|
|
300
|
+
"import sys;"
|
|
301
|
+
'sys.path.append("{}");'
|
|
302
|
+
'sys.path.append("C:\\Instrument\\scripts");'.format(inst_file_path)
|
|
303
|
+
)
|
|
304
|
+
init_hook = init_hook.replace("\\", "\\\\")
|
|
305
|
+
inst_scripts_file_path = os.path.join(inst_file_path, "inst")
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
functions = self.get_inst_attributes(inst_scripts_file_path)
|
|
309
|
+
except Exception as e:
|
|
310
|
+
match = re.search(r"\((.*?),", str(e))
|
|
311
|
+
assert match is not None, "Regex searching for expression failed."
|
|
312
|
+
e_filename = match.group(1)
|
|
313
|
+
return [
|
|
314
|
+
"Error while getting attributes of instrument scripts. Please check "
|
|
315
|
+
+ os.path.join(inst_scripts_file_path, e_filename)
|
|
316
|
+
+ ": {}".format(e)
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
# C = Convention related checks,
|
|
320
|
+
# R = Refactoring Related Checks,
|
|
321
|
+
# W = various warnings,
|
|
322
|
+
# E = Errors,
|
|
323
|
+
# F = fatal
|
|
324
|
+
# --msg-template={msg_id}:{line:3d},{column}: {obj}: {msg} for more specific message
|
|
325
|
+
Run(
|
|
326
|
+
[
|
|
327
|
+
"--rcfile={}".format(pylint_path),
|
|
328
|
+
"--init-hook={}".format(init_hook),
|
|
329
|
+
"--msg-template={C}:{line:3d}: {msg} ({symbol})",
|
|
330
|
+
"--generated-members={}".format(functions),
|
|
331
|
+
"--score=n",
|
|
332
|
+
script_name,
|
|
333
|
+
],
|
|
334
|
+
reporter=TextReporter(errors_output),
|
|
335
|
+
exit=False,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
new_warnings, errors = self.split_warning_errors(errors_output)
|
|
339
|
+
warnings += new_warnings
|
|
340
|
+
|
|
341
|
+
if errors == [] and not no_pyright: # Don't run pryight if pylint goes wrong
|
|
342
|
+
pyright_warnings, pyright_errors = self.pyright_script_checker(
|
|
343
|
+
script_name, instrument_name, pyright_additional_include or []
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
errors += pyright_errors
|
|
347
|
+
warnings += pyright_warnings
|
|
348
|
+
|
|
349
|
+
error_line_numbers = []
|
|
350
|
+
|
|
351
|
+
if warnings_as_error:
|
|
352
|
+
errors += warnings
|
|
353
|
+
else:
|
|
354
|
+
for warning in warnings:
|
|
355
|
+
print(warning)
|
|
356
|
+
|
|
357
|
+
error_line = re.search(r"W:\s+(\d+):", warning)
|
|
358
|
+
if error_line:
|
|
359
|
+
selected_number = error_line.group(1)
|
|
360
|
+
|
|
361
|
+
error_line_numbers.append(int(selected_number))
|
|
362
|
+
|
|
363
|
+
lines_containing_errors = []
|
|
364
|
+
with open(script_name) as f:
|
|
365
|
+
content = f.read()
|
|
366
|
+
|
|
367
|
+
lines_list = content.splitlines()
|
|
368
|
+
|
|
369
|
+
self.check_for_tabs(lines_list, error_line_numbers, lines_containing_errors)
|
|
370
|
+
|
|
371
|
+
line_numbers_position = 0
|
|
372
|
+
|
|
373
|
+
for line in lines_containing_errors:
|
|
374
|
+
g_error_line = re.search(r"g\.", line)
|
|
375
|
+
if g_error_line:
|
|
376
|
+
errors.append(
|
|
377
|
+
f"An error has been found on line {error_line_numbers[line_numbers_position]} "
|
|
378
|
+
f"within the file {script_name}"
|
|
379
|
+
)
|
|
380
|
+
errors.append(
|
|
381
|
+
'Please check the "g." statement and ensure that the brackets are not missing.'
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return errors
|
|
385
|
+
|
|
386
|
+
def split_warning_errors(self, errors_outputs: Iterable[str]) -> tuple[list[str], list[str]]:
|
|
387
|
+
"""
|
|
388
|
+
takes in errors and warning lists and split in two separate list i.e.
|
|
389
|
+
(errors and warnings)
|
|
390
|
+
:param errors_outputs: list of errors and warnings
|
|
391
|
+
:return: two separate lists for errors and warnings
|
|
392
|
+
"""
|
|
393
|
+
warnings = []
|
|
394
|
+
errors = []
|
|
395
|
+
errors_outputs = errors_outputs.getvalue().split("\n")
|
|
396
|
+
verbose_warning = [
|
|
397
|
+
"Redefining name 'g' from outer scope",
|
|
398
|
+
"Redefining name 'inst' from outer scope",
|
|
399
|
+
]
|
|
400
|
+
verbose_warning = [
|
|
401
|
+
error
|
|
402
|
+
for error in errors_outputs
|
|
403
|
+
if any(warning in error for warning in verbose_warning)
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
for message in errors_outputs:
|
|
407
|
+
if message.startswith("W") and (message not in verbose_warning):
|
|
408
|
+
warnings.append(message)
|
|
409
|
+
elif message.startswith("E"):
|
|
410
|
+
errors.append(message)
|
|
411
|
+
|
|
412
|
+
return warnings, errors
|
|
413
|
+
|
|
414
|
+
def get_inst_attributes(self, instrument_scripts_paths: str) -> str | list[str]:
|
|
415
|
+
"""
|
|
416
|
+
gets attributes such as Global variables, Functions, Classes defined
|
|
417
|
+
in instrument scripts
|
|
418
|
+
:param instrument_scripts_paths: path to instrument scripts
|
|
419
|
+
:return: string representation of attributes present in
|
|
420
|
+
instrument scripts with comma separated
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
attributes = ""
|
|
424
|
+
for filename in os.listdir(instrument_scripts_paths):
|
|
425
|
+
if filename.endswith(".py") and not filename.startswith("__"):
|
|
426
|
+
with open(os.path.join(instrument_scripts_paths, filename)) as f:
|
|
427
|
+
src = f.read()
|
|
428
|
+
tree = ast.parse(src, filename)
|
|
429
|
+
attributes += self.get_all_attributes(tree)
|
|
430
|
+
attributes = attributes[:-1]
|
|
431
|
+
return attributes
|
|
432
|
+
except OSError:
|
|
433
|
+
return ""
|
|
434
|
+
|
|
435
|
+
def get_all_attributes(self, tree: nodes.Module) -> str | list[str]:
|
|
436
|
+
"""
|
|
437
|
+
gets all the attributes of instrument scripts
|
|
438
|
+
:param tree: abstract syntax tree representation of instrument script
|
|
439
|
+
:return: string of all the useful attributes
|
|
440
|
+
"""
|
|
441
|
+
attributes = self.get_names_of_functions_classes_global_variables(tree.body)
|
|
442
|
+
return attributes
|
|
443
|
+
|
|
444
|
+
def get_names_of_functions_classes_global_variables(self, body: Iterable) -> str | list[str]:
|
|
445
|
+
"""
|
|
446
|
+
gets the name of function, class and global variable names
|
|
447
|
+
:param body: body to iterate through
|
|
448
|
+
:return: string of function names, class, global variables (comma separated)
|
|
449
|
+
"""
|
|
450
|
+
attributes = ""
|
|
451
|
+
for item in body:
|
|
452
|
+
# getting functions in global scope
|
|
453
|
+
if isinstance(item, ast.FunctionDef) and not item.name.startswith("_"):
|
|
454
|
+
attributes += "inst.{function_name},".format(function_name=item.name)
|
|
455
|
+
# getting class and its attributes
|
|
456
|
+
elif isinstance(item, ast.ClassDef):
|
|
457
|
+
class_name = "inst.{class_name}".format(class_name=item.name)
|
|
458
|
+
attributes += "{class_name},".format(class_name=class_name)
|
|
459
|
+
attributes += self.get_class_member_names(item.body, class_name)
|
|
460
|
+
elif isinstance(item, ast.Assign):
|
|
461
|
+
for target in item.targets:
|
|
462
|
+
attributes += self.parse_assignment_target(target)
|
|
463
|
+
|
|
464
|
+
return attributes
|
|
465
|
+
|
|
466
|
+
def parse_assignment_target(self, target: ast.AST, descendants: str = "") -> list[str]:
|
|
467
|
+
if isinstance(target, ast.Name):
|
|
468
|
+
return "inst.{}{},".format(target.id, descendants)
|
|
469
|
+
|
|
470
|
+
if sys.version_info[0] >= 3 and isinstance(target, ast.Starred):
|
|
471
|
+
return self.parse_assignment_target(target.value, descendants=descendants)
|
|
472
|
+
|
|
473
|
+
if isinstance(target, ast.Attribute):
|
|
474
|
+
descendants = ".{}{}".format(target.attr, descendants)
|
|
475
|
+
return self.parse_assignment_target(target.value, descendants=descendants)
|
|
476
|
+
|
|
477
|
+
if isinstance(target, (ast.List, ast.Tuple)):
|
|
478
|
+
attributes = ""
|
|
479
|
+
for nested_target in target.elts:
|
|
480
|
+
attributes += self.parse_assignment_target(nested_target)
|
|
481
|
+
return attributes
|
|
482
|
+
|
|
483
|
+
# Ignore all other nodes
|
|
484
|
+
return ""
|
|
485
|
+
|
|
486
|
+
def get_class_member_names(self, body: Iterable, class_name: str) -> list[str]:
|
|
487
|
+
"""
|
|
488
|
+
gets the name of all the class members
|
|
489
|
+
:param body: body to iterate through
|
|
490
|
+
:param class_name: name of class to prepend
|
|
491
|
+
:return: string of class member names (comma separated)
|
|
492
|
+
"""
|
|
493
|
+
attributes = ""
|
|
494
|
+
for function_body in body:
|
|
495
|
+
if isinstance(function_body, ast.FunctionDef):
|
|
496
|
+
if "__" not in function_body.name:
|
|
497
|
+
attributes += "{class_name}.{function_name},".format(
|
|
498
|
+
class_name=class_name, function_name=function_body.name
|
|
499
|
+
)
|
|
500
|
+
else:
|
|
501
|
+
# variables defined inside __init__
|
|
502
|
+
for variables in function_body.body:
|
|
503
|
+
if isinstance(variables, ast.Assign):
|
|
504
|
+
attributes += "{class_name}.{variable_name},".format(
|
|
505
|
+
class_name=class_name, variable_name=variables.targets[0].attr
|
|
506
|
+
)
|
|
507
|
+
return attributes
|