autopep8
Change-Id: I4e40e0ef8e80999832846eac3a415fdd767c6d98
This commit is contained in:
parent
241af728d6
commit
462b6a0a7e
@ -27,6 +27,7 @@ import code
|
|||||||
|
|
||||||
|
|
||||||
class NameSpace(dict):
|
class NameSpace(dict):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
dict.__init__(self)
|
dict.__init__(self)
|
||||||
self.__const = set()
|
self.__const = set()
|
||||||
@ -63,6 +64,7 @@ from os import path
|
|||||||
|
|
||||||
|
|
||||||
class ClientConsole(object):
|
class ClientConsole(object):
|
||||||
|
|
||||||
def __init__(self, cfgname, basepath):
|
def __init__(self, cfgname, basepath):
|
||||||
self.namespace = NameSpace()
|
self.namespace = NameSpace()
|
||||||
self.namespace.setconst('help', self.helpCmd)
|
self.namespace.setconst('help', self.helpCmd)
|
||||||
@ -98,6 +100,7 @@ from secop.protocol.messages import *
|
|||||||
|
|
||||||
|
|
||||||
class TCPConnection(object):
|
class TCPConnection(object):
|
||||||
|
|
||||||
def __init__(self, connect, port, encoding, framing, **kwds):
|
def __init__(self, connect, port, encoding, framing, **kwds):
|
||||||
self.log = mlzlog.log.getChild('connection', False)
|
self.log = mlzlog.log.getChild('connection', False)
|
||||||
self.encoder = ENCODERS[encoding]()
|
self.encoder = ENCODERS[encoding]()
|
||||||
@ -164,6 +167,7 @@ class TCPConnection(object):
|
|||||||
|
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
|
|
||||||
def __init__(self, opts):
|
def __init__(self, opts):
|
||||||
self.log = mlzlog.log.getChild('client', True)
|
self.log = mlzlog.log.getChild('client', True)
|
||||||
self._cache = dict()
|
self._cache = dict()
|
||||||
|
@ -481,9 +481,9 @@ class Client(object):
|
|||||||
def syncCommunicate(self, *msg):
|
def syncCommunicate(self, *msg):
|
||||||
res = self._communicate(*msg)
|
res = self._communicate(*msg)
|
||||||
try:
|
try:
|
||||||
res = self.encode_message(*res)
|
res = self.encode_message(*res)
|
||||||
except Exception:
|
except Exception:
|
||||||
res = str(res)
|
res = str(res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def ping(self, pingctr=[0]):
|
def ping(self, pingctr=[0]):
|
||||||
|
@ -46,6 +46,7 @@ EVENT_ONLY_ON_CHANGED_VALUES = False
|
|||||||
|
|
||||||
|
|
||||||
class PARAM(object):
|
class PARAM(object):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
description,
|
description,
|
||||||
validator=float,
|
validator=float,
|
||||||
@ -76,10 +77,10 @@ class PARAM(object):
|
|||||||
def as_dict(self, static_only=False):
|
def as_dict(self, static_only=False):
|
||||||
# used for serialisation only
|
# used for serialisation only
|
||||||
res = dict(
|
res = dict(
|
||||||
description=self.description,
|
description=self.description,
|
||||||
readonly=self.readonly,
|
readonly=self.readonly,
|
||||||
validator=validator_to_str(self.validator),
|
validator=validator_to_str(self.validator),
|
||||||
)
|
)
|
||||||
if self.unit:
|
if self.unit:
|
||||||
res['unit'] = self.unit
|
res['unit'] = self.unit
|
||||||
if self.group:
|
if self.group:
|
||||||
@ -93,6 +94,7 @@ class PARAM(object):
|
|||||||
|
|
||||||
# storage for CMDs settings (description + call signature...)
|
# storage for CMDs settings (description + call signature...)
|
||||||
class CMD(object):
|
class CMD(object):
|
||||||
|
|
||||||
def __init__(self, description, arguments, result):
|
def __init__(self, description, arguments, result):
|
||||||
# descriptive text for humans
|
# descriptive text for humans
|
||||||
self.description = description
|
self.description = description
|
||||||
@ -117,6 +119,7 @@ class CMD(object):
|
|||||||
|
|
||||||
|
|
||||||
class DeviceMeta(type):
|
class DeviceMeta(type):
|
||||||
|
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(mcs, name, bases, attrs):
|
||||||
newtype = type.__new__(mcs, name, bases, attrs)
|
newtype = type.__new__(mcs, name, bases, attrs)
|
||||||
if '__constructed__' in attrs:
|
if '__constructed__' in attrs:
|
||||||
@ -221,10 +224,10 @@ class Device(object):
|
|||||||
# static PROPERTIES, definitions in derived classes should overwrite earlier ones.
|
# static PROPERTIES, definitions in derived classes should overwrite earlier ones.
|
||||||
# how to configure some stuff which makes sense to take from configfile???
|
# how to configure some stuff which makes sense to take from configfile???
|
||||||
PROPERTIES = {
|
PROPERTIES = {
|
||||||
'group' : None, # some Modules may be grouped together
|
'group': None, # some Modules may be grouped together
|
||||||
'meaning' : None, # XXX: ???
|
'meaning': None, # XXX: ???
|
||||||
'priority' : None, # XXX: ???
|
'priority': None, # XXX: ???
|
||||||
'visibility' : None, # XXX: ????
|
'visibility': None, # XXX: ????
|
||||||
# what else?
|
# what else?
|
||||||
}
|
}
|
||||||
# PARAMS and CMDS are auto-merged upon subclassing
|
# PARAMS and CMDS are auto-merged upon subclassing
|
||||||
@ -251,7 +254,8 @@ class Device(object):
|
|||||||
self.PARAMS = params
|
self.PARAMS = params
|
||||||
|
|
||||||
# check and apply properties specified in cfgdict
|
# check and apply properties specified in cfgdict
|
||||||
# moduleproperties are to be specified as '.<propertyname>=<propertyvalue>'
|
# moduleproperties are to be specified as
|
||||||
|
# '.<propertyname>=<propertyvalue>'
|
||||||
for k, v in cfgdict.items():
|
for k, v in cfgdict.items():
|
||||||
if k[0] == '.':
|
if k[0] == '.':
|
||||||
if k[1:] in self.PROPERTIES:
|
if k[1:] in self.PROPERTIES:
|
||||||
@ -262,17 +266,17 @@ class Device(object):
|
|||||||
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
||||||
self.PROPERTIES['implementation'] = myclassname
|
self.PROPERTIES['implementation'] = myclassname
|
||||||
self.PROPERTIES['interfaces'] = [b.__name__ for b in mycls.__mro__
|
self.PROPERTIES['interfaces'] = [b.__name__ for b in mycls.__mro__
|
||||||
if b.__module__.startswith('secop.devices.core')]
|
if b.__module__.startswith('secop.devices.core')]
|
||||||
self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0]
|
self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0]
|
||||||
|
|
||||||
# remove unset (default) module properties
|
# remove unset (default) module properties
|
||||||
for k,v in self.PROPERTIES.items():
|
for k, v in self.PROPERTIES.items():
|
||||||
if v == None:
|
if v == None:
|
||||||
del self.PROPERTIES[k]
|
del self.PROPERTIES[k]
|
||||||
|
|
||||||
# check and apply parameter_properties
|
# check and apply parameter_properties
|
||||||
# specified as '<paramname>.<propertyname> = <propertyvalue>'
|
# specified as '<paramname>.<propertyname> = <propertyvalue>'
|
||||||
for k,v in cfgdict.items()[:]:
|
for k, v in cfgdict.items()[:]:
|
||||||
if '.' in k[1:]:
|
if '.' in k[1:]:
|
||||||
paramname, propname = k.split('.', 1)
|
paramname, propname = k.split('.', 1)
|
||||||
if paramname in self.PARAMS:
|
if paramname in self.PARAMS:
|
||||||
@ -338,15 +342,15 @@ class Readable(Device):
|
|||||||
'pollinterval': PARAM('sleeptime between polls', default=5,
|
'pollinterval': PARAM('sleeptime between polls', default=5,
|
||||||
readonly=False, validator=floatrange(0.1, 120), ),
|
readonly=False, validator=floatrange(0.1, 120), ),
|
||||||
'status': PARAM('current status of the device', default=(status.OK, ''),
|
'status': PARAM('current status of the device', default=(status.OK, ''),
|
||||||
validator=vector(
|
validator=vector(
|
||||||
enum(**{
|
enum(**{
|
||||||
'IDLE': status.OK,
|
'IDLE': status.OK,
|
||||||
'BUSY': status.BUSY,
|
'BUSY': status.BUSY,
|
||||||
'WARN': status.WARN,
|
'WARN': status.WARN,
|
||||||
'UNSTABLE': status.UNSTABLE,
|
'UNSTABLE': status.UNSTABLE,
|
||||||
'ERROR': status.ERROR,
|
'ERROR': status.ERROR,
|
||||||
'UNKNOWN': status.UNKNOWN
|
'UNKNOWN': status.UNKNOWN
|
||||||
}), str),
|
}), str),
|
||||||
readonly=True),
|
readonly=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +378,7 @@ class Driveable(Readable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'target': PARAM('target value of the device', default=0., readonly=False,
|
'target': PARAM('target value of the device', default=0., readonly=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
# XXX: CMDS ???? auto deriving working well enough?
|
# XXX: CMDS ???? auto deriving working well enough?
|
||||||
|
|
||||||
|
@ -30,9 +30,11 @@ from secop.protocol import status
|
|||||||
from secop.validators import floatrange, positive, enum, nonnegative, vector
|
from secop.validators import floatrange, positive, enum, nonnegative, vector
|
||||||
from secop.lib import clamp, mkthread
|
from secop.lib import clamp, mkthread
|
||||||
|
|
||||||
|
|
||||||
class CryoBase(Driveable):
|
class CryoBase(Driveable):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Cryostat(CryoBase):
|
class Cryostat(CryoBase):
|
||||||
"""simulated cryostat with:
|
"""simulated cryostat with:
|
||||||
|
|
||||||
@ -44,22 +46,22 @@ class Cryostat(CryoBase):
|
|||||||
jitter=PARAM("amount of random noise on readout values",
|
jitter=PARAM("amount of random noise on readout values",
|
||||||
validator=floatrange(0, 1), unit="K",
|
validator=floatrange(0, 1), unit="K",
|
||||||
default=0.1, readonly=False, export=False,
|
default=0.1, readonly=False, export=False,
|
||||||
),
|
),
|
||||||
T_start=PARAM("starting temperature for simulation",
|
T_start=PARAM("starting temperature for simulation",
|
||||||
validator=positive, default=10,
|
validator=positive, default=10,
|
||||||
export=False,
|
export=False,
|
||||||
),
|
),
|
||||||
looptime=PARAM("timestep for simulation",
|
looptime=PARAM("timestep for simulation",
|
||||||
validator=floatrange(0.01, 10), unit="s", default=1,
|
validator=floatrange(0.01, 10), unit="s", default=1,
|
||||||
readonly=False, export=False,
|
readonly=False, export=False,
|
||||||
),
|
),
|
||||||
ramp=PARAM("ramping speed of the setpoint",
|
ramp=PARAM("ramping speed of the setpoint",
|
||||||
validator=floatrange(0, 1e3), unit="K/min", default=1,
|
validator=floatrange(0, 1e3), unit="K/min", default=1,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
setpoint=PARAM("current setpoint during ramping else target",
|
setpoint=PARAM("current setpoint during ramping else target",
|
||||||
validator=float, default=1, unit='K',
|
validator=float, default=1, unit='K',
|
||||||
),
|
),
|
||||||
maxpower=PARAM("Maximum heater power",
|
maxpower=PARAM("Maximum heater power",
|
||||||
validator=nonnegative, default=1, unit="W",
|
validator=nonnegative, default=1, unit="W",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
@ -76,34 +78,35 @@ class Cryostat(CryoBase):
|
|||||||
target=PARAM("target temperature",
|
target=PARAM("target temperature",
|
||||||
validator=nonnegative, default=0, unit="K",
|
validator=nonnegative, default=0, unit="K",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
value=PARAM("regulation temperature",
|
value=PARAM("regulation temperature",
|
||||||
validator=nonnegative, default=0, unit="K",
|
validator=nonnegative, default=0, unit="K",
|
||||||
),
|
),
|
||||||
pid=PARAM("regulation coefficients",
|
pid=PARAM("regulation coefficients",
|
||||||
validator=vector(nonnegative, floatrange(0, 100), floatrange(0, 100)),
|
validator=vector(nonnegative, floatrange(
|
||||||
|
0, 100), floatrange(0, 100)),
|
||||||
default=(40, 10, 2), readonly=False,
|
default=(40, 10, 2), readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
p=PARAM("regulation coefficient 'p'",
|
p=PARAM("regulation coefficient 'p'",
|
||||||
validator=nonnegative, default=40, unit="%/K", readonly=False,
|
validator=nonnegative, default=40, unit="%/K", readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
i=PARAM("regulation coefficient 'i'",
|
i=PARAM("regulation coefficient 'i'",
|
||||||
validator=floatrange(0, 100), default=10, readonly=False,
|
validator=floatrange(0, 100), default=10, readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
d=PARAM("regulation coefficient 'd'",
|
d=PARAM("regulation coefficient 'd'",
|
||||||
validator=floatrange(0, 100), default=2, readonly=False,
|
validator=floatrange(0, 100), default=2, readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
mode=PARAM("mode of regulation",
|
mode=PARAM("mode of regulation",
|
||||||
validator=enum('ramp', 'pid', 'openloop'), default='ramp',
|
validator=enum('ramp', 'pid', 'openloop'), default='ramp',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
pollinterval=PARAM("polling interval",
|
pollinterval=PARAM("polling interval",
|
||||||
validator=positive, default=5,
|
validator=positive, default=5,
|
||||||
),
|
),
|
||||||
tolerance=PARAM("temperature range for stability checking",
|
tolerance=PARAM("temperature range for stability checking",
|
||||||
validator=floatrange(0, 100), default=0.1, unit='K',
|
validator=floatrange(0, 100), default=0.1, unit='K',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
|
@ -35,19 +35,19 @@ class Switch(Driveable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current state (on or off)',
|
'value': PARAM('current state (on or off)',
|
||||||
validator=enum(on=1, off=0), default=0,
|
validator=enum(on=1, off=0), default=0,
|
||||||
),
|
),
|
||||||
'target': PARAM('wanted state (on or off)',
|
'target': PARAM('wanted state (on or off)',
|
||||||
validator=enum(on=1, off=0), default=0,
|
validator=enum(on=1, off=0), default=0,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'switch_on_time': PARAM('seconds to wait after activating the switch',
|
'switch_on_time': PARAM('seconds to wait after activating the switch',
|
||||||
validator=floatrange(0, 60), unit='s',
|
validator=floatrange(0, 60), unit='s',
|
||||||
default=10, export=False,
|
default=10, export=False,
|
||||||
),
|
),
|
||||||
'switch_off_time': PARAM('cool-down time in seconds',
|
'switch_off_time': PARAM('cool-down time in seconds',
|
||||||
validator=floatrange(0, 60), unit='s',
|
validator=floatrange(0, 60), unit='s',
|
||||||
default=10, export=False,
|
default=10, export=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -100,22 +100,22 @@ class MagneticField(Driveable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current field in T',
|
'value': PARAM('current field in T',
|
||||||
unit='T', validator=floatrange(-15, 15), default=0,
|
unit='T', validator=floatrange(-15, 15), default=0,
|
||||||
),
|
),
|
||||||
'target': PARAM('target field in T',
|
'target': PARAM('target field in T',
|
||||||
unit='T', validator=floatrange(-15, 15), default=0,
|
unit='T', validator=floatrange(-15, 15), default=0,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'ramp': PARAM('ramping speed',
|
'ramp': PARAM('ramping speed',
|
||||||
unit='T/min', validator=floatrange(0, 1), default=0.1,
|
unit='T/min', validator=floatrange(0, 1), default=0.1,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'mode': PARAM('what to do after changing field',
|
'mode': PARAM('what to do after changing field',
|
||||||
default=1, validator=enum(persistent=1, hold=0),
|
default=1, validator=enum(persistent=1, hold=0),
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'heatswitch': PARAM('name of heat switch device',
|
'heatswitch': PARAM('name of heat switch device',
|
||||||
validator=str, export=False,
|
validator=str, export=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -184,10 +184,10 @@ class CoilTemp(Readable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('Coil temperatur',
|
'value': PARAM('Coil temperatur',
|
||||||
unit='K', validator=float, default=0,
|
unit='K', validator=float, default=0,
|
||||||
),
|
),
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True,
|
validator=str, readonly=True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
@ -200,14 +200,14 @@ class SampleTemp(Driveable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('Sample temperature',
|
'value': PARAM('Sample temperature',
|
||||||
unit='K', validator=float, default=10,
|
unit='K', validator=float, default=10,
|
||||||
),
|
),
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True,
|
validator=str, readonly=True,
|
||||||
),
|
),
|
||||||
'ramp': PARAM('moving speed in K/min',
|
'ramp': PARAM('moving speed in K/min',
|
||||||
validator=floatrange(0, 100), unit='K/min', default=0.1,
|
validator=floatrange(0, 100), unit='K/min', default=0.1,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -244,16 +244,16 @@ class Label(Readable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'system': PARAM("Name of the magnet system",
|
'system': PARAM("Name of the magnet system",
|
||||||
validator=str, export=False,
|
validator=str, export=False,
|
||||||
),
|
),
|
||||||
'subdev_mf': PARAM("name of subdevice for magnet status",
|
'subdev_mf': PARAM("name of subdevice for magnet status",
|
||||||
validator=str, export=False,
|
validator=str, export=False,
|
||||||
),
|
),
|
||||||
'subdev_ts': PARAM("name of subdevice for sample temp",
|
'subdev_ts': PARAM("name of subdevice for sample temp",
|
||||||
validator=str, export=False,
|
validator=str, export=False,
|
||||||
),
|
),
|
||||||
'value': PARAM("final value of label string",
|
'value': PARAM("final value of label string",
|
||||||
validator=str,
|
validator=str,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
@ -303,5 +303,5 @@ class ValidatorTest(Readable):
|
|||||||
validator=intrange(2, 9), readonly=False, default=4),
|
validator=intrange(2, 9), readonly=False, default=4),
|
||||||
'floatrange': PARAM('floatrange',
|
'floatrange': PARAM('floatrange',
|
||||||
validator=floatrange(-1, 1), readonly=False, default=0,
|
validator=floatrange(-1, 1), readonly=False, default=0,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -28,16 +28,20 @@ from secop.devices.core import Readable, Device, Driveable, PARAM
|
|||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pvaccess import Channel #import EPIVSv4 functionallity, PV access
|
from pvaccess import Channel # import EPIVSv4 functionallity, PV access
|
||||||
except ImportError:
|
except ImportError:
|
||||||
class Channel(object):
|
class Channel(object):
|
||||||
|
|
||||||
def __init__(self, pv_name):
|
def __init__(self, pv_name):
|
||||||
self.pv_name = pv_name
|
self.pv_name = pv_name
|
||||||
self.value = 0.0
|
self.value = 0.0
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def getDouble(self):
|
def getDouble(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def put(self, value):
|
def put(self, value):
|
||||||
try:
|
try:
|
||||||
self.value = value
|
self.value = value
|
||||||
@ -48,10 +52,12 @@ try:
|
|||||||
from epics import PV
|
from epics import PV
|
||||||
except ImportError:
|
except ImportError:
|
||||||
class PV(object):
|
class PV(object):
|
||||||
|
|
||||||
def __init__(self, pv_name):
|
def __init__(self, pv_name):
|
||||||
self.pv_name = pv_name
|
self.pv_name = pv_name
|
||||||
self.value = 0.0
|
self.value = 0.0
|
||||||
|
|
||||||
|
|
||||||
class EpicsReadable(Readable):
|
class EpicsReadable(Readable):
|
||||||
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
||||||
# Commmon PARAMS for all EPICS devices
|
# Commmon PARAMS for all EPICS devices
|
||||||
@ -64,16 +70,17 @@ class EpicsReadable(Readable):
|
|||||||
'value_pv': PARAM('EPICS pv_name of value', validator=str,
|
'value_pv': PARAM('EPICS pv_name of value', validator=str,
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'status_pv': PARAM('EPICS pv_name of status', validator=str,
|
'status_pv': PARAM('EPICS pv_name of status', validator=str,
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generic read and write functions
|
# Generic read and write functions
|
||||||
def _read_pv(self, pv_name):
|
def _read_pv(self, pv_name):
|
||||||
if self.epics_version == 'v4':
|
if self.epics_version == 'v4':
|
||||||
pv_channel = Channel(pv_name)
|
pv_channel = Channel(pv_name)
|
||||||
# TODO: cannot handle read of string (is there a .getText() or .getString() ?)
|
# TODO: cannot handle read of string (is there a .getText() or
|
||||||
|
# .getString() ?)
|
||||||
return_value = pv_channel.get().getDouble()
|
return_value = pv_channel.get().getDouble()
|
||||||
else: # Not EPICS v4
|
else: # Not EPICS v4
|
||||||
# TODO: fix this, it does not work
|
# TODO: fix this, it does not work
|
||||||
pv = PV(pv_name + ".VAL")
|
pv = PV(pv_name + ".VAL")
|
||||||
return_value = pv.value
|
return_value = pv.value
|
||||||
@ -91,11 +98,10 @@ class EpicsReadable(Readable):
|
|||||||
if self.epics_version == 'v4':
|
if self.epics_version == 'v4':
|
||||||
pv_channel = Channel(pv_name)
|
pv_channel = Channel(pv_name)
|
||||||
pv_channel.put(write_value)
|
pv_channel.put(write_value)
|
||||||
else: # Not EPICS v4
|
else: # Not EPICS v4
|
||||||
pv = PV(pv_name + ".VAL")
|
pv = PV(pv_name + ".VAL")
|
||||||
pv.value = write_value
|
pv.value = write_value
|
||||||
|
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
return self._read_pv(self.value_pv)
|
return self._read_pv(self.value_pv)
|
||||||
|
|
||||||
@ -109,7 +115,6 @@ class EpicsReadable(Readable):
|
|||||||
return (status.OK, 'no pv set')
|
return (status.OK, 'no pv set')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class EpicsDriveable(Driveable):
|
class EpicsDriveable(Driveable):
|
||||||
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
||||||
# Commmon PARAMS for all EPICS devices
|
# Commmon PARAMS for all EPICS devices
|
||||||
@ -126,16 +131,17 @@ class EpicsDriveable(Driveable):
|
|||||||
'value_pv': PARAM('EPICS pv_name of value', validator=str,
|
'value_pv': PARAM('EPICS pv_name of value', validator=str,
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'status_pv': PARAM('EPICS pv_name of status', validator=str,
|
'status_pv': PARAM('EPICS pv_name of status', validator=str,
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generic read and write functions
|
# Generic read and write functions
|
||||||
def _read_pv(self, pv_name):
|
def _read_pv(self, pv_name):
|
||||||
if self.epics_version == 'v4':
|
if self.epics_version == 'v4':
|
||||||
pv_channel = Channel(pv_name)
|
pv_channel = Channel(pv_name)
|
||||||
# TODO: cannot handle read of string (is there a .getText() or .getString() ?)
|
# TODO: cannot handle read of string (is there a .getText() or
|
||||||
|
# .getString() ?)
|
||||||
return_value = pv_channel.get().getDouble()
|
return_value = pv_channel.get().getDouble()
|
||||||
else: # Not EPICS v4
|
else: # Not EPICS v4
|
||||||
# TODO: fix this, it does not work
|
# TODO: fix this, it does not work
|
||||||
pv = PV(pv_name + ".VAL")
|
pv = PV(pv_name + ".VAL")
|
||||||
return_value = pv.value
|
return_value = pv.value
|
||||||
@ -153,7 +159,7 @@ class EpicsDriveable(Driveable):
|
|||||||
if self.epics_version == 'v4':
|
if self.epics_version == 'v4':
|
||||||
pv_channel = Channel(pv_name)
|
pv_channel = Channel(pv_name)
|
||||||
pv_channel.put(write_value)
|
pv_channel.put(write_value)
|
||||||
else: # Not EPICS v4
|
else: # Not EPICS v4
|
||||||
pv = PV(pv_name + ".VAL")
|
pv = PV(pv_name + ".VAL")
|
||||||
pv.value = write_value
|
pv.value = write_value
|
||||||
|
|
||||||
@ -177,9 +183,11 @@ class EpicsDriveable(Driveable):
|
|||||||
(status.BUSY, 'Moving')
|
(status.BUSY, 'Moving')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""Temperature control loop"""
|
"""Temperature control loop"""
|
||||||
# should also derive from secop.core.temperaturecontroller, once its features are agreed upon
|
# should also derive from secop.core.temperaturecontroller, once its
|
||||||
|
# features are agreed upon
|
||||||
|
|
||||||
|
|
||||||
class EpicsTempCtrl(EpicsDriveable):
|
class EpicsTempCtrl(EpicsDriveable):
|
||||||
|
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
@ -187,7 +195,7 @@ class EpicsTempCtrl(EpicsDriveable):
|
|||||||
'heaterrange': PARAM('Heater range', validator=str,
|
'heaterrange': PARAM('Heater range', validator=str,
|
||||||
default='Off', readonly=False,),
|
default='Off', readonly=False,),
|
||||||
'tolerance': PARAM('allowed deviation between value and target',
|
'tolerance': PARAM('allowed deviation between value and target',
|
||||||
validator=floatrange(1e-6,1e6), default=0.1,
|
validator=floatrange(1e-6, 1e6), default=0.1,
|
||||||
readonly=False,),
|
readonly=False,),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'heaterrange_pv': PARAM('EPICS pv_name of heater range',
|
'heaterrange_pv': PARAM('EPICS pv_name of heater range',
|
||||||
@ -209,14 +217,13 @@ class EpicsTempCtrl(EpicsDriveable):
|
|||||||
def read_status(self, maxage=0):
|
def read_status(self, maxage=0):
|
||||||
# XXX: comparison may need to collect a history to detect oscillations
|
# XXX: comparison may need to collect a history to detect oscillations
|
||||||
at_target = abs(self.read_value(maxage) - self.read_target(maxage)) \
|
at_target = abs(self.read_value(maxage) - self.read_target(maxage)) \
|
||||||
<= self.tolerance
|
<= self.tolerance
|
||||||
return (status.OK, 'at Target') if at_target else (status.BUSY, 'Moving')
|
return (status.OK, 'at Target') if at_target else (status.BUSY, 'Moving')
|
||||||
|
|
||||||
# TODO: add support for strings over epics pv
|
# TODO: add support for strings over epics pv
|
||||||
#def read_heaterrange(self, maxage=0):
|
# def read_heaterrange(self, maxage=0):
|
||||||
# return self._read_pv(self.heaterrange_pv)
|
# return self._read_pv(self.heaterrange_pv)
|
||||||
|
|
||||||
# TODO: add support for strings over epics pv
|
# TODO: add support for strings over epics pv
|
||||||
#def write_heaterrange(self, range_value):
|
# def write_heaterrange(self, range_value):
|
||||||
# self._write_pv(self.heaterrange_pv, range_value)
|
# self._write_pv(self.heaterrange_pv, range_value)
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ class Heater(Driveable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'maxheaterpower': PARAM('maximum allowed heater power',
|
'maxheaterpower': PARAM('maximum allowed heater power',
|
||||||
validator=floatrange(0, 100), unit='W',
|
validator=floatrange(0, 100), unit='W',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
@ -65,10 +65,10 @@ class Temp(Driveable):
|
|||||||
PARAMS = {
|
PARAMS = {
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True,
|
validator=str, readonly=True,
|
||||||
),
|
),
|
||||||
'target': PARAM("Target temperature",
|
'target': PARAM("Target temperature",
|
||||||
default=300.0, validator=positive, readonly=False, unit='K',
|
default=300.0, validator=positive, readonly=False, unit='K',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
|
@ -37,6 +37,7 @@ ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 2
|
|||||||
ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3
|
ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QSECNode(SECNode, QObject):
|
class QSECNode(SECNode, QObject):
|
||||||
newData = pyqtSignal(str, str, object) # module, parameter, data
|
newData = pyqtSignal(str, str, object) # module, parameter, data
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ class QSECNode(SECNode, QObject):
|
|||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(MainWindow, self).__init__(parent)
|
super(MainWindow, self).__init__(parent)
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ class ParameterButtons(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleCtrl(QWidget):
|
class ModuleCtrl(QWidget):
|
||||||
|
|
||||||
def __init__(self, node, module, parent=None):
|
def __init__(self, node, module, parent=None):
|
||||||
super(ModuleCtrl, self).__init__(parent)
|
super(ModuleCtrl, self).__init__(parent)
|
||||||
loadUi(self, 'modulectrl.ui')
|
loadUi(self, 'modulectrl.ui')
|
||||||
|
@ -32,6 +32,7 @@ from secop.protocol.errors import SECOPError
|
|||||||
|
|
||||||
|
|
||||||
class NodeCtrl(QWidget):
|
class NodeCtrl(QWidget):
|
||||||
|
|
||||||
def __init__(self, node, parent=None):
|
def __init__(self, node, parent=None):
|
||||||
super(NodeCtrl, self).__init__(parent)
|
super(NodeCtrl, self).__init__(parent)
|
||||||
loadUi(self, 'nodectrl.ui')
|
loadUi(self, 'nodectrl.ui')
|
||||||
|
@ -29,6 +29,7 @@ from secop.validators import validator_to_str
|
|||||||
|
|
||||||
|
|
||||||
class ParameterView(QWidget):
|
class ParameterView(QWidget):
|
||||||
|
|
||||||
def __init__(self, node, module, parameter, parent=None):
|
def __init__(self, node, module, parameter, parent=None):
|
||||||
super(ParameterView, self).__init__(parent)
|
super(ParameterView, self).__init__(parent)
|
||||||
loadUi(self, 'paramview.ui')
|
loadUi(self, 'paramview.ui')
|
||||||
|
@ -80,6 +80,7 @@ def format_time(timestamp=None):
|
|||||||
|
|
||||||
|
|
||||||
class Timezone(tzinfo):
|
class Timezone(tzinfo):
|
||||||
|
|
||||||
def __init__(self, offset, name='unknown timezone'):
|
def __init__(self, offset, name='unknown timezone'):
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -46,6 +46,7 @@ from secop.lib.parsing import format_time
|
|||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
|
|
||||||
def __init__(self, logger, options):
|
def __init__(self, logger, options):
|
||||||
self.equipment_id = options.pop('equipment_id')
|
self.equipment_id = options.pop('equipment_id')
|
||||||
self.log = logger
|
self.log = logger
|
||||||
@ -213,7 +214,7 @@ class Dispatcher(object):
|
|||||||
dd = {
|
dd = {
|
||||||
'parameters': self.list_module_params(modulename, only_static=True),
|
'parameters': self.list_module_params(modulename, only_static=True),
|
||||||
'commands': self.list_module_cmds(modulename),
|
'commands': self.list_module_cmds(modulename),
|
||||||
'properties' : module.PROPERTIES,
|
'properties': module.PROPERTIES,
|
||||||
}
|
}
|
||||||
result['modules'][modulename] = dd
|
result['modules'][modulename] = dd
|
||||||
result['equipment_id'] = self.equipment_id
|
result['equipment_id'] = self.equipment_id
|
||||||
|
@ -36,6 +36,7 @@ DEMO_RE = re.compile(
|
|||||||
|
|
||||||
|
|
||||||
class DemoEncoder(MessageEncoder):
|
class DemoEncoder(MessageEncoder):
|
||||||
|
|
||||||
def decode(sef, encoded):
|
def decode(sef, encoded):
|
||||||
# match [!][*|devicename][: *|paramname [: *|propname]] [=value]
|
# match [!][*|devicename][: *|paramname [: *|propname]] [=value]
|
||||||
match = DEMO_RE.match(encoded)
|
match = DEMO_RE.match(encoded)
|
||||||
|
@ -92,6 +92,7 @@ DEMO_RE_OTHER = re.compile(
|
|||||||
|
|
||||||
|
|
||||||
class DemoEncoder(MessageEncoder):
|
class DemoEncoder(MessageEncoder):
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
MessageEncoder.__init__(self, *args, **kwds)
|
MessageEncoder.__init__(self, *args, **kwds)
|
||||||
self.result = [] # for decoding
|
self.result = [] # for decoding
|
||||||
@ -321,6 +322,7 @@ DEMO_RE_MZ = re.compile(
|
|||||||
|
|
||||||
|
|
||||||
class DemoEncoder_MZ(MessageEncoder):
|
class DemoEncoder_MZ(MessageEncoder):
|
||||||
|
|
||||||
def decode(sef, encoded):
|
def decode(sef, encoded):
|
||||||
m = DEMO_RE_MZ.match(encoded)
|
m = DEMO_RE_MZ.match(encoded)
|
||||||
if m:
|
if m:
|
||||||
|
@ -135,15 +135,18 @@ class DemoEncoder(MessageEncoder):
|
|||||||
encode_cmd_result, ),
|
encode_cmd_result, ),
|
||||||
WriteRequest: (
|
WriteRequest: (
|
||||||
WRITEREQUEST,
|
WRITEREQUEST,
|
||||||
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module,
|
lambda msg: "%s:%s" % (
|
||||||
|
msg.module, msg.parameter) if msg.parameter else msg.module,
|
||||||
'value', ),
|
'value', ),
|
||||||
WriteReply: (
|
WriteReply: (
|
||||||
WRITEREPLY,
|
WRITEREPLY,
|
||||||
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module,
|
lambda msg: "%s:%s" % (
|
||||||
|
msg.module, msg.parameter) if msg.parameter else msg.module,
|
||||||
'value', ),
|
'value', ),
|
||||||
PollRequest: (
|
PollRequest: (
|
||||||
TRIGGERREQUEST,
|
TRIGGERREQUEST,
|
||||||
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module,
|
lambda msg: "%s:%s" % (
|
||||||
|
msg.module, msg.parameter) if msg.parameter else msg.module,
|
||||||
),
|
),
|
||||||
HeartbeatRequest: (
|
HeartbeatRequest: (
|
||||||
HEARTBEATREQUEST,
|
HEARTBEATREQUEST,
|
||||||
@ -158,7 +161,8 @@ class DemoEncoder(MessageEncoder):
|
|||||||
encode_error_msg, ),
|
encode_error_msg, ),
|
||||||
Value: (
|
Value: (
|
||||||
EVENT,
|
EVENT,
|
||||||
lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()')) if msg.parameter or msg.command else msg.module,
|
lambda msg: "%s:%s" % (msg.module, msg.parameter or (
|
||||||
|
msg.command + '()')) if msg.parameter or msg.command else msg.module,
|
||||||
encode_value_data, ),
|
encode_value_data, ),
|
||||||
}
|
}
|
||||||
DECODEMAP = {
|
DECODEMAP = {
|
||||||
|
@ -35,6 +35,7 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
class PickleEncoder(MessageEncoder):
|
class PickleEncoder(MessageEncoder):
|
||||||
|
|
||||||
def encode(self, messageobj):
|
def encode(self, messageobj):
|
||||||
"""msg object -> transport layer message"""
|
"""msg object -> transport layer message"""
|
||||||
return pickle.dumps(messageobj)
|
return pickle.dumps(messageobj)
|
||||||
|
@ -37,6 +37,7 @@ SCPMESSAGE = re.compile(
|
|||||||
|
|
||||||
|
|
||||||
class SCPEncoder(MessageEncoder):
|
class SCPEncoder(MessageEncoder):
|
||||||
|
|
||||||
def encode(self, msg):
|
def encode(self, msg):
|
||||||
"""msg object -> transport layer message"""
|
"""msg object -> transport layer message"""
|
||||||
# fun for Humans
|
# fun for Humans
|
||||||
|
@ -30,6 +30,7 @@ from secop.lib.parsing import *
|
|||||||
|
|
||||||
|
|
||||||
class TextEncoder(MessageEncoder):
|
class TextEncoder(MessageEncoder):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# build safe namespace
|
# build safe namespace
|
||||||
ns = dict()
|
ns = dict()
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
|
|
||||||
class SECOPError(RuntimeError):
|
class SECOPError(RuntimeError):
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
self.args = args
|
self.args = args
|
||||||
for k, v in kwds.items():
|
for k, v in kwds.items():
|
||||||
|
@ -35,6 +35,7 @@ from secop.protocol.messages import HelpMessage
|
|||||||
|
|
||||||
|
|
||||||
class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.log = self.server.log
|
self.log = self.server.log
|
||||||
self._queue = collections.deque(maxlen=100)
|
self._queue = collections.deque(maxlen=100)
|
||||||
|
@ -50,6 +50,7 @@ class Message(object):
|
|||||||
|
|
||||||
|
|
||||||
class Value(object):
|
class Value(object):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
module,
|
module,
|
||||||
parameter=None,
|
parameter=None,
|
||||||
|
@ -95,6 +95,7 @@ class Message(object):
|
|||||||
|
|
||||||
|
|
||||||
class Value(object):
|
class Value(object):
|
||||||
|
|
||||||
def __init__(self, value=Ellipsis, qualifiers=None, **kwds):
|
def __init__(self, value=Ellipsis, qualifiers=None, **kwds):
|
||||||
self.dev = ''
|
self.dev = ''
|
||||||
self.param = ''
|
self.param = ''
|
||||||
@ -166,6 +167,7 @@ class HelpMessage(Message):
|
|||||||
|
|
||||||
|
|
||||||
class NoSuchDeviceError(ErrorMessage):
|
class NoSuchDeviceError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, *devs):
|
def __init__(self, *devs):
|
||||||
ErrorMessage.__init__(
|
ErrorMessage.__init__(
|
||||||
self,
|
self,
|
||||||
@ -175,6 +177,7 @@ class NoSuchDeviceError(ErrorMessage):
|
|||||||
|
|
||||||
|
|
||||||
class NoSuchParamError(ErrorMessage):
|
class NoSuchParamError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, dev, *params):
|
def __init__(self, dev, *params):
|
||||||
ErrorMessage.__init__(
|
ErrorMessage.__init__(
|
||||||
self,
|
self,
|
||||||
@ -185,6 +188,7 @@ class NoSuchParamError(ErrorMessage):
|
|||||||
|
|
||||||
|
|
||||||
class ParamReadonlyError(ErrorMessage):
|
class ParamReadonlyError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, dev, *params):
|
def __init__(self, dev, *params):
|
||||||
ErrorMessage.__init__(
|
ErrorMessage.__init__(
|
||||||
self,
|
self,
|
||||||
@ -196,6 +200,7 @@ class ParamReadonlyError(ErrorMessage):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidParamValueError(ErrorMessage):
|
class InvalidParamValueError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, dev, param, value, e):
|
def __init__(self, dev, param, value, e):
|
||||||
ErrorMessage.__init__(
|
ErrorMessage.__init__(
|
||||||
self,
|
self,
|
||||||
@ -207,6 +212,7 @@ class InvalidParamValueError(ErrorMessage):
|
|||||||
|
|
||||||
|
|
||||||
class InternalError(ErrorMessage):
|
class InternalError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, err, **kwds):
|
def __init__(self, err, **kwds):
|
||||||
ErrorMessage.__init__(
|
ErrorMessage.__init__(
|
||||||
self, errorstring=str(err), errortype='InternalError', **kwds)
|
self, errorstring=str(err), errortype='InternalError', **kwds)
|
||||||
@ -217,7 +223,7 @@ MESSAGE = dict((cls.MSGTYPE, cls)
|
|||||||
HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
|
HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
|
||||||
UnsubscribeMessage, SubscribeMessage, PollMessage,
|
UnsubscribeMessage, SubscribeMessage, PollMessage,
|
||||||
CommandMessage, WriteMessage, ReadMessage, ListMessage
|
CommandMessage, WriteMessage, ReadMessage, ListMessage
|
||||||
])
|
])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Minimal testing of messages....")
|
print("Minimal testing of messages....")
|
||||||
|
@ -39,6 +39,7 @@ from secop.errors import ConfigError
|
|||||||
|
|
||||||
|
|
||||||
class Server(object):
|
class Server(object):
|
||||||
|
|
||||||
def __init__(self, name, workdir, parentLogger=None):
|
def __init__(self, name, workdir, parentLogger=None):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._workdir = workdir
|
self._workdir = workdir
|
||||||
|
@ -200,6 +200,7 @@ class oneof(Validator):
|
|||||||
|
|
||||||
|
|
||||||
class enum(Validator):
|
class enum(Validator):
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
self.mapping = {}
|
self.mapping = {}
|
||||||
# use given kwds directly
|
# use given kwds directly
|
||||||
|
Loading…
x
Reference in New Issue
Block a user