improve frappy server management
- do connect in background when the frappy server is not running on startup
This commit is contained in:
45
commands.py
45
commands.py
@ -22,7 +22,8 @@
|
|||||||
from nicos import session, config
|
from nicos import session, config
|
||||||
from nicos.utils import printTable
|
from nicos.utils import printTable
|
||||||
from nicos.commands import helparglist, usercommand
|
from nicos.commands import helparglist, usercommand
|
||||||
from servicemanager import FrappyManager
|
from .devices import get_frappy_config, all_info
|
||||||
|
from servicemanager import FrappyManager, SeaManager
|
||||||
|
|
||||||
|
|
||||||
SERVICES = FrappyManager.services
|
SERVICES = FrappyManager.services
|
||||||
@ -30,8 +31,9 @@ SERVICES = FrappyManager.services
|
|||||||
|
|
||||||
@usercommand
|
@usercommand
|
||||||
def set_se_list():
|
def set_se_list():
|
||||||
frappy_config = session.devices['frappy_config']
|
fc = get_frappy_config()
|
||||||
frappy_config.set_envlist()
|
if fc:
|
||||||
|
fc.set_envlist()
|
||||||
|
|
||||||
|
|
||||||
@usercommand
|
@usercommand
|
||||||
@ -47,7 +49,11 @@ def frappy(*args, main=None, stick=None, addons=None, force=False):
|
|||||||
- frappy('restart') # restart all frappy servers
|
- frappy('restart') # restart all frappy servers
|
||||||
- frappy(stick='restart') # restart stick frappy server
|
- frappy(stick='restart') # restart stick frappy server
|
||||||
"""
|
"""
|
||||||
confirmed = FrappyManager().cfg_from_sea(config.instrument).get('confirmed')
|
fc = get_frappy_config()
|
||||||
|
if not fc:
|
||||||
|
return
|
||||||
|
stickarg = stick
|
||||||
|
confirmed = SeaManager().get_cfg(config.instrument, 'sea', True).split('/', 1)[0]
|
||||||
if args:
|
if args:
|
||||||
if main is not None:
|
if main is not None:
|
||||||
raise TypeError('got multiple values for main')
|
raise TypeError('got multiple values for main')
|
||||||
@ -60,9 +66,8 @@ def frappy(*args, main=None, stick=None, addons=None, force=False):
|
|||||||
if main == '':
|
if main == '':
|
||||||
stick = '' # remove stick with main
|
stick = '' # remove stick with main
|
||||||
else:
|
else:
|
||||||
allsticks = FrappyManager().all_cfg(config.instrument, 'stick')
|
|
||||||
stickcfg = main + 'stick'
|
stickcfg = main + 'stick'
|
||||||
if stickcfg in allsticks:
|
if FrappyManager().is_cfg(config.instrument, 'stick', stickcfg):
|
||||||
# if a default stick is available, start this also
|
# if a default stick is available, start this also
|
||||||
stick = stickcfg
|
stick = stickcfg
|
||||||
else:
|
else:
|
||||||
@ -75,12 +80,30 @@ def frappy(*args, main=None, stick=None, addons=None, force=False):
|
|||||||
if addons is not None:
|
if addons is not None:
|
||||||
raise TypeError('got multiple values for addons')
|
raise TypeError('got multiple values for addons')
|
||||||
addons = ','.join(alist)
|
addons = ','.join(alist)
|
||||||
|
elif main is None and stick is None and addons is None: # bare frappy() command
|
||||||
|
fc.show_config(fc.check_services())
|
||||||
|
return
|
||||||
if confirmed and confirmed != main and main not in (None, 'restart') and not force:
|
if confirmed and confirmed != main and main not in (None, 'restart') and not force:
|
||||||
session.log.warning('%r is plugged to the cryostat control rack', confirmed)
|
session.log.warning('%r is plugged to the cryostat control rack', confirmed)
|
||||||
session.log.warning('if you are sure, use frappy(..., force=True)', confirmed)
|
cmd = all_info({'main': main, 'stick': stickarg, 'addons': addons}, '')[:-1] + ', force=True)'
|
||||||
|
session.log.warning(f'if you are sure, use: %s', cmd)
|
||||||
raise TypeError('refuse to override plugged device')
|
raise TypeError('refuse to override plugged device')
|
||||||
frappy_config = session.devices['frappy_config']
|
fc.show_config(fc.start_services(main, stick, addons))
|
||||||
frappy_config.show_config(*frappy_config.check_or_start(main, stick, addons))
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
def frappy_main(*args):
|
||||||
|
raise NameError('frappy_main(<cfg>) is no longer avaiable, use frappy(<cfg>) instead')
|
||||||
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
def frappy_stick(*args):
|
||||||
|
raise NameError('frappy_stick(<cfg>) is no longer avaiable, use frappy(stick=<cfg>) instead')
|
||||||
|
|
||||||
|
|
||||||
|
@usercommand
|
||||||
|
def frappy_addons(*args):
|
||||||
|
raise NameError('frappy_addons(<cfg>) is no longer avaiable, use frappy(addons=<cfg>) instead')
|
||||||
|
|
||||||
|
|
||||||
@usercommand
|
@usercommand
|
||||||
@ -108,4 +131,6 @@ def frappy_list(service=None):
|
|||||||
|
|
||||||
@usercommand
|
@usercommand
|
||||||
def frappy_changed():
|
def frappy_changed():
|
||||||
session.devices['frappy_config'].changed()
|
fc = get_frappy_config()
|
||||||
|
if fc:
|
||||||
|
fc.changed()
|
||||||
|
222
devices.py
222
devices.py
@ -86,32 +86,31 @@ def cleanup_defunct():
|
|||||||
|
|
||||||
|
|
||||||
def all_info(all_cfg, prefix='currently configured: '):
|
def all_info(all_cfg, prefix='currently configured: '):
|
||||||
info = []
|
|
||||||
addkwd = False
|
addkwd = False
|
||||||
|
info = []
|
||||||
for srv in SERVICES:
|
for srv in SERVICES:
|
||||||
cfginfo = all_cfg.get(srv)
|
cfglist = all_cfg.get(srv)
|
||||||
if cfginfo is None:
|
if cfglist is None:
|
||||||
addkwd = True
|
addkwd = True
|
||||||
elif addkwd:
|
|
||||||
info.append('%s=%r' % (srv, cfginfo))
|
|
||||||
else:
|
else:
|
||||||
info.append(repr(cfginfo))
|
if isinstance(cfglist, str):
|
||||||
|
cfglist = [cfglist]
|
||||||
|
cfginfo = ','.join(c if isinstance(c, str)
|
||||||
|
else f"<one of {', '.join(repr(v) for v in c)}>"
|
||||||
|
for c in cfglist)
|
||||||
|
if addkwd:
|
||||||
|
info.append('%s=%r' % (srv, cfginfo))
|
||||||
|
else:
|
||||||
|
info.append(repr(cfginfo))
|
||||||
return f"{prefix}frappy({', '.join(info)})"
|
return f"{prefix}frappy({', '.join(info)})"
|
||||||
|
|
||||||
|
|
||||||
def get_guess(allcfg):
|
def get_frappy_config():
|
||||||
guess = {}
|
try:
|
||||||
seacfg = FrappyManager().cfg_from_sea(config.instrument)
|
return session.devices['frappy_config']
|
||||||
guess.pop('confirmed', '')
|
except KeyError:
|
||||||
for s in ('main', 'stick', 'addons'):
|
session.log.error("'frappy_config' is not available - 'frappy' setup is not loaded")
|
||||||
info = allcfg.get(s, '')
|
return None
|
||||||
prev = info.split(' ', 1)[0]
|
|
||||||
if prev != info:
|
|
||||||
guess[s] = prev
|
|
||||||
fromsea = seacfg.get(s)
|
|
||||||
if fromsea and fromsea != prev:
|
|
||||||
guess[s] = fromsea
|
|
||||||
return guess
|
|
||||||
|
|
||||||
|
|
||||||
class FrappyConfig(Device):
|
class FrappyConfig(Device):
|
||||||
@ -141,10 +140,16 @@ class FrappyConfig(Device):
|
|||||||
meanings.remove('nodes')
|
meanings.remove('nodes')
|
||||||
_trigger_change = None
|
_trigger_change = None
|
||||||
_previous_shown = None
|
_previous_shown = None
|
||||||
|
_initial_config = None
|
||||||
|
_servers_loaded = False
|
||||||
|
|
||||||
def doInit(self, mode):
|
def doInit(self, mode):
|
||||||
if mode != SIMULATION and session.sessiontype != POLLER:
|
if mode != SIMULATION and session.sessiontype != POLLER:
|
||||||
self._trigger_change = threading.Event()
|
self._trigger_change = threading.Event()
|
||||||
|
for name in self.nodes:
|
||||||
|
secnode = session.devices.get(name)
|
||||||
|
if secnode:
|
||||||
|
secnode.uri = ''
|
||||||
createThread('frappy change notification', self.handle_notifications)
|
createThread('frappy change notification', self.handle_notifications)
|
||||||
|
|
||||||
def handle_notifications(self):
|
def handle_notifications(self):
|
||||||
@ -156,17 +161,42 @@ class FrappyConfig(Device):
|
|||||||
if self._trigger_change.is_set():
|
if self._trigger_change.is_set():
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
current = self.check_or_start()
|
cfgs = self.check_services()
|
||||||
if current != self._previous_shown:
|
guess_info = self.to_consider(cfgs)
|
||||||
|
if (cfgs, guess_info) != self._previous_shown and (guess_info or not self._servers_loaded):
|
||||||
cmd = 'frappy_changed()'
|
cmd = 'frappy_changed()'
|
||||||
controller.new_request(ScriptRequest(cmd, None, User('guest', USER)))
|
controller.new_request(ScriptRequest(cmd, None, User('guest', USER)))
|
||||||
except RequestError as e:
|
except RequestError as e:
|
||||||
session.log.error(f'can not queue request {e!r}')
|
session.log.error(f'can not queue request {e!r}')
|
||||||
|
|
||||||
def check_or_start(self, main=None, stick=None, addons=None):
|
def to_consider(self, cfgs):
|
||||||
|
"""return info from sea and frappy servers
|
||||||
|
|
||||||
|
for a potential "please consider calling frappy(...)" message
|
||||||
|
"""
|
||||||
|
guess_info = {}
|
||||||
|
for service, guess in FrappyManager().guess_cfgs(config.instrument, cfgs).items():
|
||||||
|
proposed = guess.get('proposed')
|
||||||
|
if proposed:
|
||||||
|
guess_info[service] = proposed
|
||||||
|
else:
|
||||||
|
missing = guess.get('missing')
|
||||||
|
if missing:
|
||||||
|
guess_info[service] = [m + '?' for m in missing]
|
||||||
|
return guess_info
|
||||||
|
|
||||||
|
def check_services(self):
|
||||||
|
cfgs = {}
|
||||||
|
for secnodename in self.nodes:
|
||||||
|
secnode = session.devices.get(secnodename)
|
||||||
|
if secnode:
|
||||||
|
cfgs[secnode.service] = secnode.get_info()
|
||||||
|
return cfgs
|
||||||
|
|
||||||
|
def start_services(self, main=None, stick=None, addons=None):
|
||||||
"""start/stop frappy servers
|
"""start/stop frappy servers
|
||||||
|
|
||||||
for example: check_or_start(main='xy', stick='')
|
for example: start_services(main='xy', stick='')
|
||||||
- restart main server with cfg='xy'
|
- restart main server with cfg='xy'
|
||||||
- stop stick server
|
- stop stick server
|
||||||
- do not touch addons server
|
- do not touch addons server
|
||||||
@ -231,26 +261,78 @@ class FrappyConfig(Device):
|
|||||||
self.set_envlist()
|
self.set_envlist()
|
||||||
for secnode in remove_cfg:
|
for secnode in remove_cfg:
|
||||||
secnode.disable()
|
secnode.disable()
|
||||||
return all_cfg, get_guess(all_cfg)
|
self._cache.put(self, 'config', all_cfg)
|
||||||
|
return all_cfg
|
||||||
|
|
||||||
def show_config(self, allcfg, guess):
|
def show_config(self, allcfg):
|
||||||
|
guess_info = self.to_consider(allcfg)
|
||||||
# remove 'frappy_changed()' commands in script queue
|
# remove 'frappy_changed()' commands in script queue
|
||||||
controller = session.daemon_device._controller
|
controller = session.daemon_device._controller
|
||||||
controller.block_requests(r['reqid'] for r in controller.get_queue() if r['script'] == 'frappy_changed()')
|
controller.block_requests(r['reqid'] for r in controller.get_queue() if r['script'] == 'frappy_changed()')
|
||||||
self._previous_shown = allcfg, guess
|
self._previous_shown = allcfg, guess_info
|
||||||
session.log.info(all_info(allcfg))
|
session.log.info(all_info(allcfg))
|
||||||
if guess:
|
|
||||||
info = all_info(guess, '')
|
if guess_info:
|
||||||
|
info = all_info(guess_info, '')
|
||||||
session.log.warning('please consider to call:')
|
session.log.warning('please consider to call:')
|
||||||
session.log.info(info)
|
session.log.info(info)
|
||||||
if '?' in info:
|
if '?' in info:
|
||||||
session.log.warning("but create cfg files first for items marked with '?'")
|
session.log.warning("but create cfg files first for items marked with '?'")
|
||||||
|
|
||||||
|
def initial_restart_cfg(self, service):
|
||||||
|
"""get cfg for (re)start of the service
|
||||||
|
|
||||||
|
returns:
|
||||||
|
cfg, when the server has to (re)started with a new cfg
|
||||||
|
True, when the server is running and does not need a restart
|
||||||
|
None, when the server is not running, but does not need a restart
|
||||||
|
"""
|
||||||
|
if self._servers_loaded:
|
||||||
|
return None
|
||||||
|
if self._initial_config is None:
|
||||||
|
success = True
|
||||||
|
fm = FrappyManager()
|
||||||
|
running = fm.get_cfg(config.instrument, None)
|
||||||
|
cache = self._getCache()
|
||||||
|
cfgs = {}
|
||||||
|
for serv, secnode in zip(fm.services, self.nodes):
|
||||||
|
cfg = running.get(serv)
|
||||||
|
if not cfg and cache:
|
||||||
|
cfg = cache.get(secnode, 'value')
|
||||||
|
if cfg:
|
||||||
|
cfgs[serv] = cfg
|
||||||
|
running_main = running.get('main')
|
||||||
|
if running_main and running_main != cfgs.get('main'):
|
||||||
|
# new main device: clear old stick
|
||||||
|
running_stick = running.get('stick')
|
||||||
|
if running_stick:
|
||||||
|
cfgs['stick'] = running_stick
|
||||||
|
else:
|
||||||
|
cfgs.pop('stick', None)
|
||||||
|
for serv, guess in fm.guess_cfgs(config.instrument, cfgs).items():
|
||||||
|
ok = guess.get('ok', [])
|
||||||
|
for proposed in guess.get('proposed', []):
|
||||||
|
if isinstance(proposed, str): # start only when ambiguous
|
||||||
|
ok.append(proposed)
|
||||||
|
else:
|
||||||
|
success = False
|
||||||
|
if ok:
|
||||||
|
cfgs[serv] = ','.join(ok)
|
||||||
|
if not success:
|
||||||
|
cfgs = {}
|
||||||
|
result = {}
|
||||||
|
for serv, cfg in cfgs.items():
|
||||||
|
runcfg = running.get(serv)
|
||||||
|
if runcfg != cfg:
|
||||||
|
result[serv] = cfg
|
||||||
|
elif runcfg:
|
||||||
|
result[serv] = True # restart not needed as cfg has not changed
|
||||||
|
self._initial_config = result
|
||||||
|
return self._initial_config.get(service)
|
||||||
|
|
||||||
def changed(self):
|
def changed(self):
|
||||||
current = self.check_or_start()
|
self._servers_loaded = True
|
||||||
#if current == self._previous_shown:
|
self.show_config(self.check_services())
|
||||||
# return
|
|
||||||
self.show_config(*current)
|
|
||||||
|
|
||||||
def remove_aliases(self):
|
def remove_aliases(self):
|
||||||
for meaning in self.meanings:
|
for meaning in self.meanings:
|
||||||
@ -279,7 +361,7 @@ class FrappyConfig(Device):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def set_envlist(self):
|
def set_envlist(self):
|
||||||
"""create aliases for SECoP devices
|
"""create aliases and envlist for SECoP devices
|
||||||
|
|
||||||
depending on their meaning
|
depending on their meaning
|
||||||
"""
|
"""
|
||||||
@ -355,7 +437,7 @@ class FrappyConfig(Device):
|
|||||||
newenv[devname] = aliasname
|
newenv[devname] = aliasname
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
to_remove.union(aliasnames)
|
to_remove.update(aliasnames)
|
||||||
|
|
||||||
for aliasname in previous_aliases:
|
for aliasname in previous_aliases:
|
||||||
session.destroyDevice(aliasname)
|
session.destroyDevice(aliasname)
|
||||||
@ -397,17 +479,30 @@ class FrappyNode(SecNodeDevice, Moveable):
|
|||||||
def doStart(self, value):
|
def doStart(self, value):
|
||||||
if value == 'None':
|
if value == 'None':
|
||||||
value = None
|
value = None
|
||||||
self.restart(value, True) # frappy server will be restarted even when unchanged
|
self.restart(value)
|
||||||
|
|
||||||
|
def doInit(self, mode):
|
||||||
|
if mode == SIMULATION or session.sessiontype == POLLER:
|
||||||
|
super().doInit(mode)
|
||||||
|
else:
|
||||||
|
fc = session.devices.get('frappy_config')
|
||||||
|
if fc:
|
||||||
|
cfg = fc.initial_restart_cfg(self.service)
|
||||||
|
if isinstance(cfg, str): # may also be None or True
|
||||||
|
self.restart(cfg)
|
||||||
|
if cfg is None: # None means: server is not running, and does not need to be restarted
|
||||||
|
# connect in background, as the server might be started later
|
||||||
|
createThread('connect', self._connect)
|
||||||
|
# TODO: check if it is not better to add a try_period argument to SecNode._connect()
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._connect()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def doStop(self):
|
def doStop(self):
|
||||||
"""never busy"""
|
"""never busy"""
|
||||||
|
|
||||||
def doInit(self, mode):
|
|
||||||
if mode != SIMULATION and session.sessiontype != POLLER:
|
|
||||||
self._lastcfg = self.doRead()
|
|
||||||
self.restart(self._lastcfg, False) # do not restart when not changed
|
|
||||||
super().doInit(mode)
|
|
||||||
|
|
||||||
def doRead(self, maxage=0):
|
def doRead(self, maxage=0):
|
||||||
return self._cfgvalue or ''
|
return self._cfgvalue or ''
|
||||||
|
|
||||||
@ -415,7 +510,9 @@ class FrappyNode(SecNodeDevice, Moveable):
|
|||||||
cfg = self.read()
|
cfg = self.read()
|
||||||
super().createDevices()
|
super().createDevices()
|
||||||
if cfg != self._lastcfg:
|
if cfg != self._lastcfg:
|
||||||
session.devices.get('frappy_config')._trigger_change.set()
|
fc = get_frappy_config()
|
||||||
|
if fc:
|
||||||
|
fc._trigger_change.set()
|
||||||
if self.param_category:
|
if self.param_category:
|
||||||
for devname, (_, devcfg) in self.setup_info.items():
|
for devname, (_, devcfg) in self.setup_info.items():
|
||||||
params_cfg = devcfg['params_cfg']
|
params_cfg = devcfg['params_cfg']
|
||||||
@ -437,10 +534,9 @@ class FrappyNode(SecNodeDevice, Moveable):
|
|||||||
cfg = self.read()
|
cfg = self.read()
|
||||||
if self._lastcfg != cfg:
|
if self._lastcfg != cfg:
|
||||||
self._lastcfg = cfg
|
self._lastcfg = cfg
|
||||||
try:
|
fc = get_frappy_config()
|
||||||
session.devices.get('frappy_config')._trigger_change.set()
|
if fc:
|
||||||
except AttributeError: # frappy_config does not exist
|
fc._trigger_change.set()
|
||||||
pass
|
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
seaconn = session.devices.get('sea_%s' % self.service)
|
seaconn = session.devices.get('sea_%s' % self.service)
|
||||||
@ -453,13 +549,10 @@ class FrappyNode(SecNodeDevice, Moveable):
|
|||||||
code, text = status.DISABLED, 'disabled'
|
code, text = status.DISABLED, 'disabled'
|
||||||
SecNodeDevice._set_status(self, code, text)
|
SecNodeDevice._set_status(self, code, text)
|
||||||
|
|
||||||
def restart(self, cfg=None, forced=True):
|
def restart(self, cfg=None):
|
||||||
"""restart frappy server
|
"""restart frappy server
|
||||||
|
|
||||||
:param cfg: config for frappy server, if not given, restart with the same config
|
:param cfg: config for frappy server, if not given, restart with the same config
|
||||||
:param forced: True: restart anyway, force using cfg
|
|
||||||
False: try to get cfg (1) from sea, (2) from running frappy server, (3) from given cfg
|
|
||||||
when cfg has not changed, do not restart
|
|
||||||
"""
|
"""
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
cfg = self._cfgvalue
|
cfg = self._cfgvalue
|
||||||
@ -467,25 +560,8 @@ class FrappyNode(SecNodeDevice, Moveable):
|
|||||||
fm = FrappyManager()
|
fm = FrappyManager()
|
||||||
info = fm.get_ins_info(ins)
|
info = fm.get_ins_info(ins)
|
||||||
running_cfg = fm.get_cfg(ins, self.service) or ''
|
running_cfg = fm.get_cfg(ins, self.service) or ''
|
||||||
if not forced or cfg is None:
|
|
||||||
sea_cfg = fm.cfg_from_sea(ins).get(self.service, '')
|
|
||||||
if '?' in sea_cfg:
|
|
||||||
if sea_cfg == '?':
|
|
||||||
self.log.warning('undefined sea device')
|
|
||||||
else:
|
|
||||||
self.log.warning(f"missing frappy cfg file for {sea_cfg.replace('?', '')}")
|
|
||||||
cfg = '' # stop server
|
|
||||||
elif sea_cfg:
|
|
||||||
cfg = sea_cfg
|
|
||||||
elif running_cfg:
|
|
||||||
self._cfgvalue = running_cfg
|
|
||||||
return
|
|
||||||
if cfg == running_cfg:
|
|
||||||
self._cfgvalue = running_cfg
|
|
||||||
return
|
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
if forced:
|
self.log.error('can not restart - previous cfg unknown')
|
||||||
self.log.error('can not restart - previous cfg unknown')
|
|
||||||
return
|
return
|
||||||
if cfg != running_cfg:
|
if cfg != running_cfg:
|
||||||
self.disable()
|
self.disable()
|
||||||
@ -494,15 +570,15 @@ class FrappyNode(SecNodeDevice, Moveable):
|
|||||||
fm.do_stop(ins, self.service)
|
fm.do_stop(ins, self.service)
|
||||||
is_cfg = cfg and ':' not in cfg
|
is_cfg = cfg and ':' not in cfg
|
||||||
if is_cfg:
|
if is_cfg:
|
||||||
available_cfg = FrappyManager().all_cfg(config.instrument, self.service)
|
available_cfg = None
|
||||||
failed = False
|
|
||||||
for cfgitem in cfg.split(','):
|
for cfgitem in cfg.split(','):
|
||||||
if cfgitem not in available_cfg:
|
if not fm.is_cfg(config.instrument, self.service, cfgitem):
|
||||||
failed = True
|
if available_cfg is None:
|
||||||
|
available_cfg = fm.all_cfg(config.instrument, self.service)
|
||||||
suggestions = suggest(cfgitem, available_cfg)
|
suggestions = suggest(cfgitem, available_cfg)
|
||||||
if suggestions:
|
if suggestions:
|
||||||
session.log.error('%s unknown, did you mean: %s' % (cfgitem, ', '.join(suggestions)))
|
session.log.error('%s unknown, did you mean: %s' % (cfgitem, ', '.join(suggestions)))
|
||||||
if failed:
|
if available_cfg is not None:
|
||||||
raise ValueError('use "frappy_list()" to get a list of valid frappy configurations')
|
raise ValueError('use "frappy_list()" to get a list of valid frappy configurations')
|
||||||
uri = 'localhost:%d' % info[self.service]
|
uri = 'localhost:%d' % info[self.service]
|
||||||
else:
|
else:
|
||||||
|
Reference in New Issue
Block a user