Files
frappy/frappy_psi/scripts.py
T
zolliker 2c389082f2 implement frappy scripts
these are scripts accessing modules remote secnodes as in
frappy cli

includes a fix in frappy.client.interactive to be able to specify
the name space

Change-Id: Icaed2b24fa331584a33f0ef276917c894bae9e23
2026-08-11 15:52:34 +02:00

124 lines
4.3 KiB
Python

# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
import threading
from pathlib import Path
from frappy.core import Drivable, Property, Parameter, Command, StringType, \
IDLE, BUSY, WARN, ERROR
from frappy.errors import IsBusyError
from frappy.client.interactive import run_script, ClientEnvironment
from frappy.lib import formatExtendedTraceback
class ScriptThread(threading.Thread):
exc = None
def __init__(self, completer, *args):
super().__init__()
self.completer = completer
self.args = args
self.clenv = ClientEnvironment()
self.clenv.init()
def run(self):
try:
run_script(*self.args, clenv=self.clenv)
except BaseException as e:
# print(formatExtendedTraceback())
print(e)
self.exc = e
finally:
self.completer()
def stop(self):
self.clenv.trigger.shutdown()
class Scripts(Drivable):
scriptdir = Property('directory of scripts used as commands', StringType())
secnodes = Parameter('remote nodes, space separated', StringType(), readonly=False, default='')
target = Parameter('script to start', StringType(), readonly=False, default='')
value = Parameter(datatype=StringType(), default='')
_status = IDLE, ''
_thread = None
_script = ''
def __new__(cls, modname, logger, cfgdict, dispatcher):
scriptdir = cfgdict['scriptdir']['value']
scripts = Path(scriptdir).expanduser().glob('*.py')
attrs = {}
for script in scripts:
name = script.stem
print(name, script)
def cmdwrapper(self, target=name):
self.write_target(target)
attrs[name] = Command(description=f'script {name}')(cmdwrapper)
return super().__new__(type(f'Script_{modname}', (cls,), attrs))
def _start_script(self, script):
if self.isBusy():
raise IsBusyError('can not start a script while running')
self._script = script
self._thread = ScriptThread(self.read_status, str(script), *self.secnodes.split())
self._thread.start()
def read_status(self):
if self._thread is None:
return IDLE, ''
if self._thread.is_alive():
if self._stopping:
return BUSY, f'stopping {self.target}'
self.value = f'{self.target} running'
return BUSY, self.value
if self._stopping:
self.value = f'{self.target} stopped'
return WARN, self.value
if self._thread.exc is None:
self.value = f'{self.target} finished'
return IDLE, self.value
return ERROR, f'raised in {self.target}: {self._thread.exc!r}'
def write_target(self, script):
"""start script given as target"""
self._start_script(Path(self.scriptdir).expanduser() / f'{script}.py')
self._stopping = False
self.value = f'{script} started'
self.status = BUSY, self.value
self.setFastPoll(True)
@Command()
def stop(self):
"""interrupt script
a script can only be stopped during setting a parameter,
when starting a command or when waiting
"""
if self._thread:
self._stopping = True
self._thread.stop()
self.read_status()
@Command()
def reset(self):
"""clear error status"""
self._thread = None
self._stopping = False
self.read_status()