diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..faf4ef78 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: Python package + +on: [push, pull_request] + +jobs: + test_code: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + max-parallel: 12 + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + pip install -r test-requirements.txt + - name: Install package and dependencies + run: | + pip install . + - name: Build package from source + run: | + python -m build + - name: Run tests + run: | + py.test --capture=sys --cov=lightlab --cov-config .coveragerc + - name: Run linting + run: | + py.test --pylint --flake8 --pylint-rcfile=pylintrc lightlab + continue-on-error: true \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 4947a4db..00000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -jupyter==1.0.0 -mock==2.0.0 -nbstripout==0.3.1 -pipdeptree==0.13.0 -pyserial==3.4 -python-dateutil==2.6.1 -pytz==2017.3 -pyusb==1.0.2 -recommonmark==0.4.0 -texttable==1.4.0 -wheel==0.31.1 diff --git a/.gitlab-ci.yml b/legacy/.gitlab-ci.yml similarity index 100% rename from .gitlab-ci.yml rename to legacy/.gitlab-ci.yml diff --git a/.travis.yml b/legacy/.travis.yml similarity index 100% rename from .travis.yml rename to legacy/.travis.yml diff --git a/lightlab/equipment/abstract_drivers/TekScopeAbstract.py b/lightlab/equipment/abstract_drivers/TekScopeAbstract.py index faff90ff..2a6e919a 100644 --- a/lightlab/equipment/abstract_drivers/TekScopeAbstract.py +++ b/lightlab/equipment/abstract_drivers/TekScopeAbstract.py @@ -67,10 +67,8 @@ def timebaseConfig(self, avgCnt=None, duration=None, position=None, nPts=None): self.setConfigParam('DATA:START', 1) self.setConfigParam('DATA:STOP', nPts) - presentSettings = { - 'avgCnt': self.getConfigParam('ACQUIRE:NUMAVG', forceHardware=True) - } - + presentSettings = dict() + presentSettings['avgCnt'] = self.getConfigParam('ACQUIRE:NUMAVG', forceHardware=True) presentSettings['duration'] = self.getConfigParam('HORIZONTAL:MAIN:SCALE', forceHardware=True) presentSettings['position'] = self.getConfigParam('HORIZONTAL:MAIN:POSITION', forceHardware=True) presentSettings['nPts'] = self.getConfigParam(self._recLenParam, forceHardware=True) @@ -103,19 +101,13 @@ def acquire(self, chans=None, timeout=None, **kwargs): for c in chans: if c > self.totalChans: - raise Exception( - ( - f'Received channel: {str(c)}' - + '. Max channels of this scope is ' - ) - + str(self.totalChans) - ) - + raise Exception('Received channel: ' + str(c) + + '. Max channels of this scope is ' + str(self.totalChans)) # Channel select for ich in range(1, 1 + self.totalChans): thisState = 1 if ich in chans else 0 - self.setConfigParam(f'SELECT:CH{str(ich)}', thisState) + self.setConfigParam('SELECT:CH' + str(ich), thisState) isSampling = kwargs.get('avgCnt', 0) == 1 self._setupSingleShot(isSampling) @@ -184,11 +176,12 @@ def __transferData(self, chan): Todo: Make this binary transfer to go even faster ''' - chStr = f'CH{str(chan)}' + chStr = 'CH' + str(chan) self.setConfigParam('DATA:ENCDG', 'ASCII') self.setConfigParam('DATA:SOURCE', chStr) - return self.query_ascii_values('CURV?') + voltRaw = self.query_ascii_values('CURV?') + return voltRaw def __scaleData(self, voltRaw): ''' Scale to second and voltage units. @@ -209,10 +202,7 @@ def __scaleData(self, voltRaw): YZERO, the reference voltage, YOFF, the offset position, and YSCALE, the conversion factor between position and voltage. ''' - get = lambda param: float( - self.getConfigParam(f'WFMOUTPRE:{param}', forceHardware=True) - ) - + get = lambda param: float(self.getConfigParam('WFMOUTPRE:' + param, forceHardware=True)) voltage = (np.array(voltRaw) - get('YOFF')) \ * get(self._yScaleParam) \ + get('YZERO') @@ -273,13 +263,10 @@ def setMeasurement(self, measIndex, chan, measType): ''' if measIndex == 0: raise ValueError('measIndex is 1-indexed') - measSubmenu = f'MEASUREMENT:MEAS{str(measIndex)}:' - self.setConfigParam( - measSubmenu + self._measurementSourceParam, f'CH{str(chan)}' - ) - - self.setConfigParam(f'{measSubmenu}TYPE', measType.upper()) - self.setConfigParam(f'{measSubmenu}STATE', 1) + measSubmenu = 'MEASUREMENT:MEAS' + str(measIndex) + ':' + self.setConfigParam(measSubmenu + self._measurementSourceParam, 'CH' + str(chan)) + self.setConfigParam(measSubmenu + 'TYPE', measType.upper()) + self.setConfigParam(measSubmenu + 'STATE', 1) def measure(self, measIndex): ''' @@ -289,8 +276,8 @@ def measure(self, measIndex): Returns: (float) ''' - measSubmenu = f'MEASUREMENT:MEAS{str(measIndex)}:' - return float(self.getConfigParam(f'{measSubmenu}VALUE', forceHardware=True)) + measSubmenu = 'MEASUREMENT:MEAS' + str(measIndex) + ':' + return float(self.getConfigParam(measSubmenu + 'VALUE', forceHardware=True)) def autoAdjust(self, chans): ''' Adjusts offsets and scaling so that waveforms are not clipped ''' @@ -298,7 +285,7 @@ def autoAdjust(self, chans): self.saveConfig(dest='+autoAdjTemp', subgroup='MEASUREMENT') for ch in chans: - chStr = f'CH{str(ch)}' + chStr = 'CH' + str(ch) # Set up measurements self.setMeasurement(1, ch, 'pk2pk') @@ -312,8 +299,8 @@ def autoAdjust(self, chans): pk2pk = self.measure(1) mean = self.measure(2) - span = float(self.getConfigParam(f'{chStr}:SCALE')) - offs = float(self.getConfigParam(f'{chStr}:OFFSET')) + span = float(self.getConfigParam(chStr + ':SCALE')) + offs = float(self.getConfigParam(chStr + ':OFFSET')) # Check if scale is correct within the tolerance newSpan = None @@ -323,7 +310,7 @@ def autoAdjust(self, chans): elif pk2pk > 0.8 * span: newSpan = 2 * span if newSpan < 0.1 or newSpan > 100: - raise Exception(f'Scope channel {chStr} could not be adjusted.') + raise Exception('Scope channel ' + chStr + ' could not be adjusted.') # Check if offset is correct within the tolerance if abs(mean) > 0.05 * span: @@ -334,8 +321,8 @@ def autoAdjust(self, chans): break # Adjust settings - self.setConfigParam(f'{chStr}:SCALE', newSpan / 10) - self.setConfigParam(f'{chStr}:OFFSET', newOffs) + self.setConfigParam(chStr + ':SCALE', newSpan / 10) + self.setConfigParam(chStr + ':OFFSET', newOffs) # Recover the measurement setup from before adjustment self.loadConfig(source='+autoAdjTemp', subgroup='MEASUREMENT') diff --git a/lightlab/equipment/abstract_drivers/configurable.py b/lightlab/equipment/abstract_drivers/configurable.py index 85931e80..821026e7 100644 --- a/lightlab/equipment/abstract_drivers/configurable.py +++ b/lightlab/equipment/abstract_drivers/configurable.py @@ -1,7 +1,7 @@ from lightlab import visalogger as logger from pyvisa import VisaIOError from contextlib import contextmanager -import dpath.util +import dpath import json from numpy import floor from pathlib import Path @@ -32,7 +32,7 @@ class TekConfig(object): def __init__(self, initDict=None): if initDict is None: - initDict = {} + initDict = dict() self.dico = initDict.copy() def __str__(self): @@ -55,18 +55,21 @@ def get(self, cStr, asCmd=True): asCmd (bool): if true, returns a tuple representing a command. Otherwise returns just the value ''' try: - val = dpath.util.get(self.dico, cStr, separator=self.separator) + val = dpath.get(self.dico, cStr, separator=self.separator) except KeyError: - raise KeyError(f'{cStr} is not present in this TekConfig instance') + raise KeyError(cStr + ' is not present in this TekConfig instance') if type(val) is dict and '&' in val.keys(): val = val['&'] - return (cStr, str(val)) if asCmd else val + if not asCmd: + return val + else: + return (cStr, str(val)) def set(self, cStr, val): ''' Takes the value only, not a dictionary ''' # First check that it does not exist as a subdir try: - ex = dpath.util.get(self.dico, cStr, separator=self.separator) + ex = dpath.get(self.dico, cStr, separator=self.separator) except KeyError: # doesn't exist, we are good to go pass @@ -76,20 +79,20 @@ def set(self, cStr, val): cStr = cStr + self.separator + '&' cmd = (cStr, val) - success = dpath.util.set(self.dico, *cmd, separator=self.separator) + success = dpath.set(self.dico, *cmd, separator=self.separator) if success != 1: # it doesn't exist yet try: - dpath.util.new(self.dico, *cmd, separator=self.separator) + dpath.new(self.dico, *cmd, separator=self.separator) except (ValueError, dpath.exceptions.PathNotFound): # We probably have an integer leaf where we would also like to have a directory parent = self.separator.join(cmd[0].split(self.separator)[:-1]) try: oldV = self.get(parent, asCmd=False) except KeyError: - print(f'dpath did not take {cmd}') + print('dpath did not take ' + str(cmd)) raise - dpath.util.set(self.dico, parent, {'&': oldV}, separator=self.separator) - dpath.util.new(self.dico, *cmd, separator=self.separator) + dpath.set(self.dico, parent, {'&': oldV}, separator=self.separator) + dpath.new(self.dico, *cmd, separator=self.separator) def getList(self, subgroup='', asCmd=True): ''' Deep crawler that goes in and generates a command for every leaf. @@ -102,7 +105,7 @@ def getList(self, subgroup='', asCmd=True): list: list of valid commands (cstr, val) on the subgroup subdirectory ''' cList = [] - children = dpath.util.search( + children = dpath.search( self.dico, f'{subgroup}*', yielded=True, separator=self.separator ) @@ -116,13 +119,14 @@ def getList(self, subgroup='', asCmd=True): cList += self.getList(subgroup=cmd[0] + self.separator) if asCmd: return cList - writeList = [None] * len(cList) - for i, cmd in enumerate(cList): - cStr, val = cmd - if cStr[-1] == '&': # check for tokens - cStr = cStr[:-2] - writeList[i] = f'{cStr} {str(val)}' - return writeList + else: + writeList = [None] * len(cList) + for i, cmd in enumerate(cList): + cStr, val = cmd + if cStr[-1] == '&': # check for tokens + cStr = cStr[:-2] + writeList[i] = cStr + ' ' + str(val) + return writeList def setList(self, cmdList): ''' The inverse of getList ''' @@ -144,7 +148,7 @@ def transfer(self, source, subgroup=''): elif type(source) is type(self): sCon = source else: - raise Exception(f'Invalid source for transfer. Got {str(type(source))}') + raise Exception('Invalid source for transfer. Got ' + str(type(source))) commands = sCon.getList(subgroup=subgroup) self.setList(commands) return self @@ -169,7 +173,7 @@ def __parseShorthand(cls, setResponse): cmdGrp = None for i in range(len(pairs)): words = pairs[i].split(' ') - cmdLeaf, val = words[:2] + cmdLeaf, val = words[0:2] if len(words) > 2: print('Warning 2-value returns not handled by TekConfig class. Ignoring...') print(*words) @@ -192,9 +196,10 @@ def fromSETresponse(cls, setResponse, subgroup=''): full.setList(commandList) if subgroup == '': return full - ret = cls() - ret.transfer(full, subgroup=subgroup) - return ret + else: + ret = cls() + ret.transfer(full, subgroup=subgroup) + return ret def save(self, fname, subgroup='', overwrite=False): ''' Saves dictionary parameters in json format. Merges if there's something already there, unless overwrite is True. @@ -247,7 +252,8 @@ def __init__(self, headerIsOptional=True, verboseIsOptional=False, precedingColo self.colon = precedingColon self.space = interveningSpace - self.config = {'default': None} + self.config = dict() + self.config['default'] = None self.config['init'] = TekConfig() self.config['live'] = TekConfig() self.separator = self.config['live'].separator @@ -347,7 +353,8 @@ def getDefaultFilename(self): (str): the default filename ''' info = self.instrID().split(',') - return defaultFileDir / '-'.join(info[:3]) + '.json' + deffile = defaultFileDir / '-'.join(info[:3]) + '.json' + return deffile def saveConfig(self, dest='+user', subgroup='', overwrite=False): ''' @@ -441,14 +448,17 @@ def _getHardwareConfig(self, cStrList): cStr = cStr[:-2] try: - ret = self.query(f'{cStr}?') + ret = self.query(cStr + '?') except VisaIOError: logger.error('Problematic parameter was %s.\n' 'Likely it does not exist in this instrument command structure.', cStr) raise logger.debug('Queried %s, got %s', cStr, ret) - val = ret.split(' ')[-1] if self.header else ret + if self.header: + val = ret.split(' ')[-1] + else: + val = ret # Type detection try: val = float(val) @@ -501,9 +511,12 @@ def generateDefaults(self, filename=None, overwrite=False): cfgBuild = TekConfig() for cmd in allSetCmds: - cStr = cmd[0] if cmd[0][-1] != '&' else cmd[0][:-2] + if cmd[0][-1] != '&': # handle the sibling subdir token + cStr = cmd[0] + else: + cStr = cmd[0][:-2] try: - val = self.query(f'{cStr}?', withTimeout=1000) + val = self.query(cStr + '?', withTimeout=1000) cfgBuild.set(cStr, val) logger.info(cStr, '<--', val) except VisaIOError: diff --git a/lightlab/equipment/abstract_drivers/electrical_sources.py b/lightlab/equipment/abstract_drivers/electrical_sources.py index e48357a0..04130bc5 100644 --- a/lightlab/equipment/abstract_drivers/electrical_sources.py +++ b/lightlab/equipment/abstract_drivers/electrical_sources.py @@ -42,7 +42,7 @@ def _checkMode(cls, mode): ''' Returns mode in lower case ''' if mode not in cls.supportedModes: - raise TypeError(f'Invalid mode: {str(mode)}. Valid: {str(cls.supportedModes)}') + raise TypeError('Invalid mode: ' + str(mode) + '. Valid: ' + str(cls.supportedModes)) return mode.lower() @@ -58,7 +58,7 @@ def val2baseUnit(cls, value, mode): valueWasDict = isinstance(value, dict) if not valueWasDict: value = {-1: value} - baseVal = {} + baseVal = dict() for ch, vEl in value.items(): if mode == 'baseunit': baseVal[ch] = vEl @@ -72,7 +72,10 @@ def val2baseUnit(cls, value, mode): baseVal[ch] = cls.val2baseUnit(np.sign(vEl) * np.sqrt(abs(vEl)), 'amp') elif mode == 'mwperohm': baseVal[ch] = cls.val2baseUnit(vEl / 1e3, 'wattperohm') - return baseVal if valueWasDict else baseVal[-1] + if valueWasDict: + return baseVal + else: + return baseVal[-1] @classmethod def baseUnit2val(cls, baseVal, mode): @@ -86,7 +89,7 @@ def baseUnit2val(cls, baseVal, mode): baseValWasDict = isinstance(baseVal, dict) if not baseValWasDict: baseVal = {-1: baseVal} - value = {} + value = dict() for ch, bvEl in baseVal.items(): if mode == 'baseunit': value[ch] = bvEl @@ -100,7 +103,10 @@ def baseUnit2val(cls, baseVal, mode): value[ch] = np.sign(bvEl) * (cls.baseUnit2val(bvEl, 'amp')) ** 2 elif mode == 'mwperohm': value[ch] = cls.baseUnit2val(bvEl, 'wattperohm') * 1e3 - return value if baseValWasDict else value[-1] + if baseValWasDict: + return value + else: + return value[-1] class MultiChannelSource(object): @@ -115,16 +121,15 @@ class MultiChannelSource(object): def __init__(self, useChans=None, **kwargs): if useChans is None: logger.warning('No useChans specified for MultichannelSource') - useChans = [] + useChans = list() self.useChans = useChans self.stateDict = dict([ch, 0] for ch in self.useChans) # Check that the requested channels are available to be blocked out - if self.maxChannel is not None and any( - ch > self.maxChannel - 1 for ch in self.useChans - ): - raise ChannelError( - 'Requested channel is more than there are available') + if self.maxChannel is not None: + if any(ch > self.maxChannel - 1 for ch in self.useChans): + raise ChannelError( + 'Requested channel is more than there are available') super().__init__(**kwargs) @property diff --git a/lightlab/equipment/abstract_drivers/multimodule_configurable.py b/lightlab/equipment/abstract_drivers/multimodule_configurable.py index b272e995..502fbce8 100644 --- a/lightlab/equipment/abstract_drivers/multimodule_configurable.py +++ b/lightlab/equipment/abstract_drivers/multimodule_configurable.py @@ -32,7 +32,7 @@ def __selectSelf(self): ''' Writes a selector message that contains its prefix and its channel number ''' - self.bank.write(f'{self.selectPrefix} {self.channel}') + self.bank.write('{} {}'.format(self.selectPrefix, self.channel)) def write(self, writeStr): ''' Regular write in the enclosing bank, except preceded by select self @@ -78,18 +78,16 @@ def __init__(self, useChans=None, configModule_klass=Configurable, **kwargs): ''' if useChans is None: logger.warning('No useChans specified for MultiModuleConfigurable') - useChans = [] + useChans = list() self.useChans = useChans # Check that the requested channels are available to be blocked out - if self.maxChannel is not None and any( - ch > self.maxChannel - 1 for ch in self.useChans - ): - raise ChannelError('Requested channel is more than there are available') - - self.modules = [ - configModule_klass(channel=chan, bank=self) for chan in self.useChans - ] + if self.maxChannel is not None: + if any(ch > self.maxChannel - 1 for ch in self.useChans): + raise ChannelError('Requested channel is more than there are available') + self.modules = [] + for chan in self.useChans: + self.modules.append(configModule_klass(channel=chan, bank=self)) super().__init__(**kwargs) def getConfigArray(self, cStr): @@ -102,8 +100,11 @@ def getConfigArray(self, cStr): (np.ndarray): values for all modules, ordered based on the ordering of ``useChans`` ''' - retVals = [module.getConfigParam(cStr) for module in self.modules] - return np.array(retVals) + retVals = [] + for module in self.modules: + retVals.append(module.getConfigParam(cStr)) + retArr = np.array(retVals) + return retArr def setConfigArray(self, cStr, newValArr, forceHardware=False): ''' Iterate over modules setting the parameter to @@ -121,16 +122,9 @@ def setConfigArray(self, cStr, newValArr, forceHardware=False): (bool): did any require hardware write? ''' if len(newValArr) != len(self.modules): - raise ChannelError( - ( - ( - 'Wrong number of channels in array. ' - + f'Got {len(newValArr)}, ' - ) - + f'Expected {len(self.useChans)}.' - ) - ) - + raise ChannelError('Wrong number of channels in array. ' + + 'Got {}, '.format(len(newValArr)) + + 'Expected {}.'.format(len(self.useChans))) bankWroteToHardware = False for module, val in zip(self.modules, newValArr): moduleWroteToHardware = module.setConfigParam(cStr, val, forceHardware=forceHardware) @@ -146,7 +140,7 @@ def getConfigDict(self, cStr): (dict): parameter on all the channels, keyed by channel number ''' stateArr = self.getConfigArray(cStr) - dictOfStates = {} + dictOfStates = dict() for ch in self.useChans: virtualIndex = self.useChans.index(ch) dictOfStates[ch] = stateArr[virtualIndex] @@ -164,13 +158,9 @@ def setConfigDict(self, cStr, newValDict, forceHardware=False): ''' for chan in newValDict.keys(): if chan not in self.useChans: - raise ChannelError( - ( - ('Channel index not blocked out. ' + f'Requested {chan}, ') - + f'Available {self.useChans}.' - ) - ) - + raise ChannelError('Channel index not blocked out. ' + + 'Requested {}, '.format(chan) + + 'Available {}.'.format(self.useChans)) setArrayBuilder = self.getConfigArray(cStr) for iCh, chan in enumerate(self.useChans): if chan in newValDict.keys(): diff --git a/lightlab/equipment/abstract_drivers/power_meters.py b/lightlab/equipment/abstract_drivers/power_meters.py index b85837cd..b58310d5 100644 --- a/lightlab/equipment/abstract_drivers/power_meters.py +++ b/lightlab/equipment/abstract_drivers/power_meters.py @@ -15,11 +15,9 @@ def validateChannel(self, channel): ''' if channel not in self.channelDescriptions.keys(): raise ChannelError( - ( - 'Not a valid PowerMeter channel. Use ' - + ' '.join(f'{k} ({v})' for k, v in self.channelDescriptions) - ) - ) + 'Not a valid PowerMeter channel. Use ' + ' '.join( + ('{} ({})'.format(k, v) + for k, v in self.channelDescriptions))) def powerLin(self, channel=1): return 10 ** (self.powerDbm(channel) / 10) diff --git a/lightlab/equipment/lab_instruments/Advantest_Q8221_PM.py b/lightlab/equipment/lab_instruments/Advantest_Q8221_PM.py index 4e150067..d329f8d9 100644 --- a/lightlab/equipment/lab_instruments/Advantest_Q8221_PM.py +++ b/lightlab/equipment/lab_instruments/Advantest_Q8221_PM.py @@ -44,6 +44,6 @@ def powerDbm(self, channel=1): (double): Power in dB or dBm ''' self.validateChannel(channel) - self.write(f'CH{str(channel)}') - powStr = self.query(f'CH{str(channel)}') + self.write('CH' + str(channel)) + powStr = self.query('CH' + str(channel)) return float(powStr[3:]) diff --git a/lightlab/equipment/lab_instruments/Agilent_33220_FG.py b/lightlab/equipment/lab_instruments/Agilent_33220_FG.py index ffde5583..ed56aff0 100644 --- a/lightlab/equipment/lab_instruments/Agilent_33220_FG.py +++ b/lightlab/equipment/lab_instruments/Agilent_33220_FG.py @@ -28,9 +28,9 @@ def startup(self): # self.write('D0') # enable output def enable(self, enaState=None): + wordMap = {True: 'ON', False: 'OFF'} trueWords = [True, 1, '1', 'ON'] if enaState is not None: - wordMap = {True: 'ON', False: 'OFF'} self.setConfigParam('OUTP', wordMap[enaState]) return self.getConfigParam('OUTP') in trueWords @@ -50,7 +50,7 @@ def waveform(self, newWave=None): self.setConfigParam('FUNC', tok.upper()) break else: - raise ValueError(f'{newWave} is not a valid waveform: {tokens}') + raise ValueError(newWave + ' is not a valid waveform: ' + str(tokens)) return self.getConfigParam('FUNC').lower() def setArbitraryWaveform(self, wfm): @@ -109,6 +109,5 @@ def duty(self, duty=None): self.setConfigParam('FUNC:RAMP:SYMMETRY', duty) return self.getConfigParam('FUNC:RAMP:SYMMETRY') else: - raise ValueError( - f'Duty cycles are not supported with the currently selected type of waveform ({self.waveform()})' - ) + raise ValueError('Duty cycles are not supported with the currently selected ' + 'type of waveform ({})'.format(self.waveform())) diff --git a/lightlab/equipment/lab_instruments/Agilent_N5183A_VG.py b/lightlab/equipment/lab_instruments/Agilent_N5183A_VG.py index c4051c60..371bbab3 100644 --- a/lightlab/equipment/lab_instruments/Agilent_N5183A_VG.py +++ b/lightlab/equipment/lab_instruments/Agilent_N5183A_VG.py @@ -32,12 +32,12 @@ def amplitude(self, amp=None): ''' if amp is not None: if amp > 15: - print(f'Warning: Agilent N5183 ony goes up to +15dBm, given {amp}dBm.') + print('Warning: Agilent N5183 ony goes up to +15dBm, given {}dBm.'.format(amp)) amp = 15 if amp < -20: - print(f'Warning: Agilent N5183 ony goes down to -20dBm, given {amp}dBm.') + print('Warning: Agilent N5183 ony goes down to -20dBm, given {}dBm.'.format(amp)) amp = -20 - self.setConfigParam('POW:AMPL', f'{amp} dBm') + self.setConfigParam('POW:AMPL', '{} dBm'.format(amp)) retStr = self.getConfigParam('POW:AMPL') return float(retStr.split(' ')[0]) diff --git a/lightlab/equipment/lab_instruments/Agilent_N5222A_NA.py b/lightlab/equipment/lab_instruments/Agilent_N5222A_NA.py index f64d68ba..f5544640 100644 --- a/lightlab/equipment/lab_instruments/Agilent_N5222A_NA.py +++ b/lightlab/equipment/lab_instruments/Agilent_N5222A_NA.py @@ -52,10 +52,10 @@ def amplitude(self, amp=None): ''' if amp is not None: if amp > 30: - print(f'Warning: PNA ony goes up to +30dBm, given {amp}dBm.') + print('Warning: PNA ony goes up to +30dBm, given {}dBm.'.format(amp)) amp = 30 if amp < -30: - print(f'Warning: R&S ony goes down to -30dBm, given {amp}dBm.') + print('Warning: R&S ony goes down to -30dBm, given {}dBm.'.format(amp)) amp = -30 self.setConfigParam('SOUR:POW', amp) return self.getConfigParam('SOUR:POW') @@ -74,7 +74,7 @@ def frequency(self, freq=None): ''' if freq is not None: if freq > 26e9: - print(f'Warning: Agilent N5183 ony goes up to 40GHz, given {freq / 1e9}GHz.') + print('Warning: Agilent N5183 ony goes up to 40GHz, given {}GHz.'.format(freq / 1e9)) freq = 26e9 if freq == self.getConfigParam('SENS:FREQ:CW'): return freq @@ -145,11 +145,11 @@ def normalize(self): pass def triggerSetup(self, useAux=None, handshake=None, isSlave=False): - prefix = f'TRIG:CHAN{self.chanNum}:AUX{self.auxTrigNum}' - self.setConfigParam(f'{prefix}:INT', 'SWE') - self.setConfigParam(f'{prefix}:POS', 'BEF') + prefix = 'TRIG:CHAN{}:AUX{}'.format(self.chanNum, self.auxTrigNum) + self.setConfigParam(prefix + ':INT', 'SWE') + self.setConfigParam(prefix + ':POS', 'BEF') self.setConfigParam('TRIG:SOUR', 'EXT' if isSlave else 'IMM') - self.__enaBlock(f'{prefix}:HAND', handshake) + self.__enaBlock(prefix + ':HAND', handshake) return self.__enaBlock(prefix, useAux) def getSwpDuration(self, forceHardware=False): @@ -160,7 +160,7 @@ def measurementSetup(self, measType='S21', chanNum=None): chanNum = self.chanNum traceNum = chanNum # First let's see the measurements already on this channel - retStr = self.query(f'CALC{chanNum}:PAR:CAT:EXT?').strip('"') + retStr = self.query('CALC{}:PAR:CAT:EXT?'.format(chanNum)).strip('"') if retStr == 'NO CATALOG': activeMeasTypes = [] activeMeasNames = [] @@ -168,31 +168,24 @@ def measurementSetup(self, measType='S21', chanNum=None): activeMeasTypes = retStr.split(',')[1::2] activeMeasNames = retStr.split(',')[::2] - newMeasName = f'ANT{chanNum}_{measType}' + newMeasName = 'ANT{}_{}'.format(chanNum, measType) if len(activeMeasTypes) == 1 and measType == activeMeasTypes[0] and newMeasName == activeMeasNames[0]: # It is already set up changed = False else: # Clear them for mName in activeMeasNames: - self.write(f"CALC{chanNum}:PAR:DEL '{mName}'") + self.write("CALC{}:PAR:DEL '{}'".format(chanNum, mName)) # make a new measurement - self.setConfigParam( - f"CALC{chanNum}:PAR:EXT", - f"'{newMeasName}', '{measType}'", - forceHardware=True, - ) - - self.setConfigParam( - f'DISP:WIND:TRACE{traceNum}:FEED', - f"'{newMeasName}'", - forceHardware=True, - ) - + self.setConfigParam("CALC{}:PAR:EXT".format(chanNum), "'{}', '{}'".format( + newMeasName, measType), forceHardware=True) + self.setConfigParam('DISP:WIND:TRACE{}:FEED'.format(traceNum), + "'{}'".format(newMeasName), forceHardware=True) changed = True - self.setConfigParam( - f'CALC{self.chanNum}:PAR:MNUM', self.chanNum, forceHardware=changed - ) + self.setConfigParam('CALC{}:PAR:MNUM'.format(self.chanNum), + self.chanNum, forceHardware=changed) + + retStr = self.query('CALC{}:PAR:CAT:EXT?'.format(chanNum)).strip('"') # self.setConfigParam('CALC{}:PAR:SEL'.format(self.chanNum), self.chanNum, forceHardware=changed) # wait for changes to take effect # This could be improved by something like *OPC? corresponding to the end @@ -211,7 +204,7 @@ def spectrum(self): self.setConfigParam('FORM', 'ASC') self.open() - dbm = self.query_ascii_values(f'CALC{self.chanNum}:DATA? FDATA') + dbm = self.query_ascii_values('CALC{}:DATA? FDATA'.format(self.chanNum)) self.close() fStart = float(self.getConfigParam('SENS:FREQ:STAR')) @@ -231,7 +224,7 @@ def multiSpectra(self, nSpect=1, livePlot=False): display.clear_output() display.display(plt.gcf()) else: - print(f'Took spectrum {iSpect + 1} of {nSpect}') + print('Took spectrum {} of {}'.format(iSpect + 1, nSpect)) print('done.') return bund diff --git a/lightlab/equipment/lab_instruments/Anritsu_MP1763B_PPG.py b/lightlab/equipment/lab_instruments/Anritsu_MP1763B_PPG.py index d40bc745..d936a571 100644 --- a/lightlab/equipment/lab_instruments/Anritsu_MP1763B_PPG.py +++ b/lightlab/equipment/lab_instruments/Anritsu_MP1763B_PPG.py @@ -54,14 +54,17 @@ def setPattern(self, bitArray): intVal = np.sum(bt * 2 ** np.arange(8)) pStr += chr(intVal) # Switch to other endian - ind = i + 1 if i % 2 == 0 else i - 1 + if i % 2 == 0: + ind = i + 1 + else: + ind = i - 1 intList[ind] = intVal byteList = bytes(intList) self.setConfigParam('PTS', 1) # We only care to set data patterns # self.setConfigParam('DLN', 8*nBytes, forceHardware=True) self.setConfigParam('DLN', len(bitArray), forceHardware=True) - preamble = f'WRT {nBytes}, 0' + preamble = 'WRT {}, 0'.format(nBytes) self.write(preamble) self.open() self.mbSession.write_raw(byteList) @@ -97,7 +100,8 @@ def syncSource(self, src=None): try: iTok = tokens.index(src) except ValueError: - raise ValueError(f'{src} is not a valid sync source: {tokens}') + raise ValueError( + src + ' is not a valid sync source: ' + str(tokens)) self.setConfigParam('SOP', iTok) return tokens[int(self.getConfigParam('SOP'))] @@ -226,15 +230,15 @@ def PRBS_pattern(cls, order, mark_ratio=0.5): polynomial = 0b1000000000000011 # 1+X14+X15 seed = 0b000000000000001 elif order == 20: - polynomial = (1 << 20) + (1 << 20 - 3) + (1 << 0) + polynomial = (1 << 20) + (1 << 20 - 3) + (1 << 20 - 20) # 1+X3+X20 seed = 0b00111000111000111000 elif order == 23: - polynomial = (1 << 23) + (1 << 23 - 18) + (1 << 0) + polynomial = (1 << 23) + (1 << 23 - 18) + (1 << 23 - 23) # 1+X18+X23 seed = 0b1111100 << 16 elif order == 31: - polynomial = (1 << 31) + (1 << 31 - 28) + (1 << 0) + polynomial = (1 << 31) + (1 << 31 - 28) + (1 << 31 - 31) # 1+X28+X31 seed = 0b1110000 << 24 else: - raise NotImplementedError(f"PRBS{order} not implemented.") + raise NotImplementedError("PRBS{} not implemented.".format(order)) return prbs_pattern(polynomial, seed) diff --git a/lightlab/equipment/lab_instruments/Apex_AP2440A_OSA.py b/lightlab/equipment/lab_instruments/Apex_AP2440A_OSA.py index 19486ee9..215e6727 100644 --- a/lightlab/equipment/lab_instruments/Apex_AP2440A_OSA.py +++ b/lightlab/equipment/lab_instruments/Apex_AP2440A_OSA.py @@ -99,7 +99,10 @@ def _query(self, queryStr): def query(self, queryStr, expected_talker=None): ret = self._query(queryStr) if expected_talker is not None: - log_function = logger.warning if ret != expected_talker else logger.debug + if ret != expected_talker: + log_function = logger.warning + else: + log_function = logger.debug log_function("'%s' returned '%s', expected '%s'", queryStr, ret, str(expected_talker)) else: logger.debug("'%s' returned '%s'", queryStr, ret) @@ -155,8 +158,8 @@ def wlRange(self, newRange): newRangeClipped = np.clip(newRange, a_min=1505.765, a_max=1572.418) if np.any(newRange != newRangeClipped): print('Warning: Requested OSA wlRange out of range. Got', newRange) - self.write(f'SPSTRTWL{str(np.max(newRangeClipped))}') - self.write(f'SPSTOPWL{str(np.min(newRangeClipped))}') + self.write('SPSTRTWL' + str(np.max(newRangeClipped))) + self.write('SPSTOPWL' + str(np.min(newRangeClipped))) self.__wlRange = newRangeClipped def triggerAcquire(self): @@ -202,17 +205,18 @@ def spectrum(self, average_count=1): """Take a new sweep and return the new data. This is the primary user function of this class """ - if type(average_count) != int or average_count <= 0: - raise RuntimeError( - f'average_count must be positive integer, used {average_count}' - ) - + if not (type(average_count) == int and average_count > 0): + raise RuntimeError('average_count must be positive integer, used {}'.format(average_count)) for i in range(average_count): self.triggerAcquire() nm, dbm = self.transferData() - dbmAvg = dbm / average_count if i == 0 else dbmAvg + dbm / average_count + if i == 0: + dbmAvg = dbm / average_count + else: + dbmAvg = dbmAvg + dbm / average_count + return Spectrum(nm, dbmAvg, inDbm=True) # TLS access methods currently not implemented @@ -233,7 +237,7 @@ def tlsEnable(self, newState=None): if isinstance(newState, bool): newState = (newState != 0) writeVal = '1' if newState else '0' - self.write(f'TLSON {writeVal}') + self.write('TLSON ' + writeVal) @property def tlsWl(self): @@ -246,4 +250,4 @@ def tlsWl(self, newState=None): """newState is a float in units of nm """ if newState: - self.write(f'TLSwl {str(newState)}') + self.write('TLSwl ' + str(newState)) diff --git a/lightlab/equipment/lab_instruments/Aragon_BOSA_400.py b/lightlab/equipment/lab_instruments/Aragon_BOSA_400.py index c0a54023..f3a75c9d 100644 --- a/lightlab/equipment/lab_instruments/Aragon_BOSA_400.py +++ b/lightlab/equipment/lab_instruments/Aragon_BOSA_400.py @@ -2,7 +2,7 @@ import numpy as np from lightlab.util.data import Spectrum -import visa +import pyvisa as visa import time import logging import struct @@ -30,12 +30,12 @@ def patch_startup(func): def new_func(self, *args, **kwargs): func(self, *args, **kwargs) with self.connected(): - self.send('++eot_enable 1') # Append user defined character when EOI detected + self.send('++eot_enable 1') # Append user defined character when EOI detected self.send('++eot_char 10') # Append \n (ASCII 10) when EOI is detected return new_func class Aragon_BOSA_400 (VISAInstrumentDriver): - + __apps = np.array(['BOSA', 'TLS', 'CA', 'MAIN']) __wlRange = None __currApp = None @@ -51,10 +51,11 @@ def __init__(self, name='BOSA 400 OSA', address=None, **kwargs): kwargs['tempSess'] = kwargs.pop('tempSess', True) VISAInstrumentDriver.__init__(self, name=name, address=address, **kwargs) self.interface = self._session_object - - if True: # TODO: detect if using prologix - old_startup = self.interface._prologix_rm.startup - self.interface._prologix_rm.startup = patch_startup(old_startup) + if address: + using_prologix = address.startswith('prologix://') + if using_prologix: + old_startup = self.interface._prologix_rm.startup + self.interface._prologix_rm.startup = patch_startup(old_startup) def stop(self): self.__currApp = str(self.ask('INST:STAT:MODE?')) @@ -62,7 +63,7 @@ def stop(self): self.write('SENS:SWITCH OFF') else: self.write('INST:STAT:RUN 0') - + def start(self): self.__currApp = str(self.ask('INST:STAT:MODE?')) if (self.__currApp == 'TLS'): @@ -100,7 +101,7 @@ def read(self): log.debug("All data readed!") log.debug("Data received: " + message) return message - + def ask(self, command): """ writes and reads data""" @@ -108,12 +109,12 @@ def ask(self, command): data = "" data = self.query(command) return data - + def application(self, app=None): if app is not None and app in self.__apps: try: if app != 'MAIN': - if self.__currApp is not 'MAIN': + if self.__currApp != 'MAIN': self.application('MAIN') self.write('INST:STAT:MODE ' + str(app)) time.sleep(1) @@ -163,7 +164,7 @@ def ask_TRACE_ASCII(self): data = self.query("TRAC?", withTimeout) # data = self.query("IDN?") return data - + def ask_TRACE_REAL(self): data = "" self.write("FORM REAL") @@ -171,11 +172,11 @@ def ask_TRACE_REAL(self): self.write("TRAC?") data = self.read_TRACE_REAL_GPIB(NumPoints) return data - + def read_TRACE_ASCII(self): """ read something from device""" - + log.debug("Reading data using GPIB interface...") while(1): try: @@ -208,7 +209,7 @@ def read_TRACE_REAL_GPIB(self,numPoints): print(e) raise e return Trace - + def spectrum(self, form='REAL'): x=list() y=list() @@ -242,7 +243,7 @@ def CAParam(self, avgCount='CONT', sMode='HR', noiseZero=False): raise e else: log.exception("\nFor average count, please choose from ['4','8','12','32','CONT']\nFor speed mode, please choose from ['HR','HS']") - + def CAInput(self, meas='IL', pol='1'): if meas in self.__CAmeasurement and pol in self.__CAPolarization: try: @@ -254,7 +255,7 @@ def CAInput(self, meas='IL', pol='1'): raise e else: print("\nFor measurement type, please choose from ['IL', 'RL', 'IL&RL']\nFor polarization, please choose from ['1', '2', 'INDEP', 'SIMUL']") - + def TLSwavelength(self, waveLength=None): if waveLength is not None and self.__currApp == 'TLS': try: diff --git a/lightlab/equipment/lab_instruments/Aragon_BOSA_400_Queens.py b/lightlab/equipment/lab_instruments/Aragon_BOSA_400_Queens.py index 19edc36e..2cb6c8bb 100644 --- a/lightlab/equipment/lab_instruments/Aragon_BOSA_400_Queens.py +++ b/lightlab/equipment/lab_instruments/Aragon_BOSA_400_Queens.py @@ -2,7 +2,7 @@ import numpy as np from lightlab.util.data import Spectrum -import visa +import pyvisa as visa import time import logging import struct @@ -25,7 +25,7 @@ log.addHandler(ch) class Aragon_BOSA_400_Queens (VISAInstrumentDriver): - + __apps = np.array(['BOSA', 'TLS', 'CA', 'MAIN']) __wlRange = None __currApp = None @@ -44,14 +44,14 @@ def __del__(self): self.interface.close() except Exception as e: logger.warning("Could not close instrument correctly: exception %r", e.message) - + def stop(self): self.__currApp = str(self.ask('INST:STAT:MODE?')) if (self.__currApp == 'TLS'): self.write('SENS:SWITCH OFF') else: self.write('INST:STAT:RUN 0') - + def start(self): self.__currApp = str(self.ask('INST:STAT:MODE?')) if (self.__currApp == 'TLS'): @@ -90,7 +90,7 @@ def read(self): log.debug("All data readed!") log.debug("Data received: " + message) return message - + def ask(self, command): """ writes and reads data""" @@ -99,7 +99,7 @@ def ask(self, command): self.write(command) data = self.read() return data - + def application(self, app=None): if app is not None and app in self.__apps: try: @@ -152,7 +152,7 @@ def ask_TRACE_ASCII(self): self.write("TRAC?") data = self.read_TRACE_ASCII() return data - + # def ask_TRACE_REAL(self): # data = "" # self.write("FORM REAL") @@ -160,11 +160,11 @@ def ask_TRACE_ASCII(self): # self.write("TRAC?") # data = self.read_TRACE_REAL_GPIB(NumPoints) # return data - + def read_TRACE_ASCII(self): """ read something from device""" - + log.debug("Reading data using GPIB interface...") while(1): try: @@ -199,7 +199,7 @@ def read_TRACE_ASCII(self): # print(e) # raise e # return Trace - + def spectrum(self, form='ASCII'): x=list() y=list() @@ -231,7 +231,7 @@ def CAParam(self, avgCount='CONT', sMode='HR', noiseZero=False): raise e else: log.exception("\nFor average count, please choose from ['4','8','12','32','CONT']\nFor speed mode, please choose from ['HR','HS']") - + def CAInput(self, meas='IL', pol='1'): if meas in self.__CAmeasurement and pol in self.__CAPolarization: try: @@ -243,7 +243,7 @@ def CAInput(self, meas='IL', pol='1'): raise e else: print("\nFor measurement type, please choose from ['IL', 'RL', 'IL&RL']\nFor polarization, please choose from ['1', '2', 'INDEP', 'SIMUL']") - + def TLSwavelength(self, waveLength=None): if waveLength is not None and self.__currApp == 'TLS': try: diff --git a/lightlab/equipment/lab_instruments/HP_8116A_FG.py b/lightlab/equipment/lab_instruments/HP_8116A_FG.py index a9faf12e..3d03dbbc 100644 --- a/lightlab/equipment/lab_instruments/HP_8116A_FG.py +++ b/lightlab/equipment/lab_instruments/HP_8116A_FG.py @@ -73,7 +73,8 @@ def waveform(self, newWave=None): try: iTok = tokens.index(newWave) except ValueError: - raise ValueError(f'{newWave} is not a valid sync source: {tokens}') + raise ValueError( + newWave + ' is not a valid sync source: ' + str(tokens)) self.setConfigParam('W', iTok) return tokens[int(self.getConfigParam('W'))] @@ -98,14 +99,14 @@ def amplAndOffs(self, amplOffs=None): amplitude = np.clip(amplitude, *self.amplitudeRange) if amplitude is not None: - self.setConfigParam('AMP', f'{amplitude} V') + self.setConfigParam('AMP', '{} V'.format(amplitude)) try: ampl = float(self.getConfigParam('AMP').split(' ')[0]) except VisaIOError: logger.error('unable to get the amplitude') ampl = None if offset is not None: - self.setConfigParam('OFS', f'{offset} V') + self.setConfigParam('OFS', '{} V'.format(offset)) try: offs = float(self.getConfigParam('OFS').split(' ')[0]) except VisaIOError: @@ -116,6 +117,6 @@ def amplAndOffs(self, amplOffs=None): def duty(self, duty=None): ''' duty is in percentage ''' if duty is not None: - self.setConfigParam('DTY', f'{duty} %') + self.setConfigParam('DTY', '{} %'.format(duty)) return self.getConfigParam('DTY') diff --git a/lightlab/equipment/lab_instruments/HP_8152A_PM.py b/lightlab/equipment/lab_instruments/HP_8152A_PM.py index 40f005ce..4a3f0651 100644 --- a/lightlab/equipment/lab_instruments/HP_8152A_PM.py +++ b/lightlab/equipment/lab_instruments/HP_8152A_PM.py @@ -55,9 +55,9 @@ def proccessWeirdRead(readString): # there is an ambiguity when there are two digits, so we assume they are both intended onesHundredthsVals = [0] * 2 for i, s in enumerate(onesHundredthsStrs): - if len(s) in {1, 2}: + if len(s) in [1, 2]: onesHundredthsVals[i] = float(s) - elif len(s) in {3, 4}: # at least one has been repeated, so skip the middle (3) or seconds (4) + elif len(s) in [3, 4]: # at least one has been repeated, so skip the middle (3) or seconds (4) onesHundredthsVals[i] = float(s[::2]) else: raise ValueError('Too many digits one one side of the decimal point') @@ -67,11 +67,14 @@ def proccessWeirdRead(readString): val *= -1 return str(val) - def robust_query(self, *args, **kwargs): # pylint: disable=arguments-differ + def robust_query(self, *args, **kwargs): # pylint: disable=arguments-differ ''' Conditionally check for read character doubling ''' retRaw = self.query(*args, **kwargs) # pylint: disable=arguments-differ - return self.proccessWeirdRead(retRaw) if self.doReadDoubleCheck else retRaw + if self.doReadDoubleCheck: + return self.proccessWeirdRead(retRaw) + else: + return retRaw def powerDbm(self, channel=1): ''' The detected optical power in dB on the specified channel @@ -85,7 +88,7 @@ def powerDbm(self, channel=1): self.validateChannel(channel) trial = 0 while trial < 10: # Sometimes it gets out of range, so we have to try a few times - self.write(f'CH{str(channel)}') + self.write('CH' + str(channel)) powStr = self.robust_query('TRG') v = float(powStr) if abs(v) < 999: # check if it's reasonable @@ -94,5 +97,6 @@ def powerDbm(self, channel=1): # continue trial += 1 else: - raise Exception(f'Power meter values are unreasonable. Got {v}') + raise Exception('Power meter values are unreasonable.' + ' Got {}'.format(v)) return v diff --git a/lightlab/equipment/lab_instruments/HP_8156A_VA.py b/lightlab/equipment/lab_instruments/HP_8156A_VA.py index 1f7d8f56..a4706041 100644 --- a/lightlab/equipment/lab_instruments/HP_8156A_VA.py +++ b/lightlab/equipment/lab_instruments/HP_8156A_VA.py @@ -63,7 +63,7 @@ def attenLin(self, newAttenLin): def sendToHardware(self, sleepTime=None): if sleepTime is None: sleepTime = self.safeSleepTime - self.write(f'INP:ATT {str(self.attenDB)}DB') + self.write('INP:ATT ' + str(self.attenDB) + 'DB') time.sleep(sleepTime) # Let it settle @property diff --git a/lightlab/equipment/lab_instruments/HP_8157A_VA.py b/lightlab/equipment/lab_instruments/HP_8157A_VA.py index de1e30ce..aca52331 100644 --- a/lightlab/equipment/lab_instruments/HP_8157A_VA.py +++ b/lightlab/equipment/lab_instruments/HP_8157A_VA.py @@ -78,19 +78,19 @@ def attenLin(self, newAttenLin): def sendToHardware(self, sleepTime=None): if sleepTime is None: sleepTime = self.safeSleepTime - self.write(f'ATT {str(self.attenDB)}DB') + self.write('ATT ' + str(self.attenDB) + 'DB') time.sleep(sleepTime) # Let it settle @calibration.setter def calibration(self, cal_factor, sleepTime=None): # cal_factor is in dB if sleepTime is None: sleepTime = self.safeSleepTime - self.write(f'CAL {str(cal_factor)}DB') + self.write('CAL ' + str(cal_factor) + 'DB') time.sleep(sleepTime) # Let it settle @wavelength.setter def wavelength(self, wl, sleepTime=None): # wl can be in m, mm, um, or nm. here we choose nm. if sleepTime is None: sleepTime = self.safeSleepTime - self. write(f'WVL{str(wl)}NM') + self. write('WVL' + str(wl) + 'NM') time.sleep(sleepTime) diff --git a/lightlab/equipment/lab_instruments/ILX_7900B_LS.py b/lightlab/equipment/lab_instruments/ILX_7900B_LS.py index 9b829b16..fb0cfd26 100644 --- a/lightlab/equipment/lab_instruments/ILX_7900B_LS.py +++ b/lightlab/equipment/lab_instruments/ILX_7900B_LS.py @@ -56,7 +56,7 @@ def __init__(self, name='The laser source', address=None, useChans=None, **kwarg useChans = kwargs.pop('dfbChans') if useChans is None: logger.warning('No useChans specified for ILX_7900B_LS') - useChans = [] + useChans = list() VISAInstrumentDriver.__init__(self, name=name, address=address, **kwargs) MultiModuleConfigurable.__init__(self, useChans=useChans, configModule_klass=ILX_Module) @@ -83,9 +83,8 @@ def setConfigArray(self, cStr, newValArr, forceHardware=False): This adds sleep functionality, only when there is a change, for an amount determined by the ``sleepOn`` class attribute. ''' - if wroteToHardware := super().setConfigArray( - cStr, newValArr, forceHardware=forceHardware - ): + wroteToHardware = super().setConfigArray(cStr, newValArr, forceHardware=forceHardware) + if wroteToHardware: print('DFB settling for', self.sleepOn[cStr], 'seconds.') time.sleep(self.sleepOn[cStr]) print('done.') @@ -109,7 +108,8 @@ def enableState(self, newState): for ena in newState: if ena not in [0, 1]: - raise ValueError(('Laser states can only be 0 or 1. ' + f'Got {newState}')) + raise ValueError('Laser states can only be 0 or 1. ' + + 'Got {}'.format(newState)) self.setConfigArray('OUT', newState) def setChannelEnable(self, chanEnableDict): diff --git a/lightlab/equipment/lab_instruments/Keithley_2400_SM.py b/lightlab/equipment/lab_instruments/Keithley_2400_SM.py index 1f79f4fc..d7f547cf 100644 --- a/lightlab/equipment/lab_instruments/Keithley_2400_SM.py +++ b/lightlab/equipment/lab_instruments/Keithley_2400_SM.py @@ -49,12 +49,15 @@ def setPort(self, port): self.setConfigParam('ROUT:TERM', 'REAR') def __setSourceMode(self, isCurrentSource): - sourceStr, meterStr = ('CURR', 'VOLT') if isCurrentSource else ('VOLT', 'CURR') + if isCurrentSource: + sourceStr, meterStr = ('CURR', 'VOLT') + else: + sourceStr, meterStr = ('VOLT', 'CURR') self.setConfigParam('SOURCE:FUNC', sourceStr) - self.setConfigParam(f'SOURCE:{sourceStr}:MODE', 'FIXED') + self.setConfigParam('SOURCE:{}:MODE'.format(sourceStr), 'FIXED') self.setConfigParam('SENSE:FUNCTION:OFF:ALL') - self.setConfigParam('SENSE:FUNCTION:ON', f'"{meterStr}"') - self.setConfigParam(f'SENSE:{meterStr}:RANGE:AUTO', 'ON') + self.setConfigParam('SENSE:FUNCTION:ON', '"{}"'.format(meterStr)) + self.setConfigParam('SENSE:{}:RANGE:AUTO'.format(meterStr), 'ON') self.setConfigParam('RES:MODE', 'MAN') # Manual resistance ranging def setVoltageMode(self, protectionCurrent=0.05): diff --git a/lightlab/equipment/lab_instruments/Keithley_2606B_SMU.py b/lightlab/equipment/lab_instruments/Keithley_2606B_SMU.py index bd4f2ab4..baa0cb64 100644 --- a/lightlab/equipment/lab_instruments/Keithley_2606B_SMU.py +++ b/lightlab/equipment/lab_instruments/Keithley_2606B_SMU.py @@ -141,7 +141,10 @@ def _query(self, queryStr): def query(self, queryStr, expected_talker=None): ret = self._query(queryStr) if expected_talker is not None: - log_function = logger.warning if ret != expected_talker else logger.debug + if ret != expected_talker: + log_function = logger.warning + else: + log_function = logger.debug log_function( "'%s' returned '%s', expected '%s'", queryStr, ret, str(expected_talker) ) @@ -164,7 +167,9 @@ def smu_string(self): elif self.channel.upper() == "B": return "smub" else: - raise RuntimeError(f"Unexpected channel: {self.channel}, should be 'A' or 'B'") + raise RuntimeError( + "Unexpected channel: {}, should be 'A' or 'B'".format(self.channel) + ) @property def smu_full_string(self): @@ -172,7 +177,7 @@ def smu_full_string(self): def query_print(self, query_string, expected_talker=None): time.sleep(0.01) - query_string = f"print({query_string})" + query_string = "print(" + query_string + ")" return self.query(query_string, expected_talker=expected_talker) def smu_reset(self): @@ -232,7 +237,13 @@ def startup(self): def set_sense_mode(self, sense_mode="local"): ''' Set sense mode. Defaults to local sensing. ''' - sense_mode = 1 if sense_mode == "remote" else 0 + if sense_mode == "remote": + sense_mode = 1 # 1 or smuX.SENSE_REMOTE: Selects remote sense (4-wire) + elif sense_mode == "local": + sense_mode = 0 # 0 or smuX.SENSE_LOCAL: Selects local sense (2-wire) + else: + sense_mode = 0 # 0 or smuX.SENSE_LOCAL: Selects local sense (2-wire) + self.write("{smuX}.sense = {sense_mode}".format(smuX=self.smu_full_string, sense_mode=sense_mode)) # SourceMeter Essential methods diff --git a/lightlab/equipment/lab_instruments/Lakeshore_Model336.py b/lightlab/equipment/lab_instruments/Lakeshore_Model336.py index 5fa373f3..b28dde50 100644 --- a/lightlab/equipment/lab_instruments/Lakeshore_Model336.py +++ b/lightlab/equipment/lab_instruments/Lakeshore_Model336.py @@ -102,10 +102,10 @@ def instrID(self): except socket.error: logger.error('Lakeshore communication test failed. Sent: \'*IDN?\'') raise - + def reset(self): self.write('*RST') - + # def setHeaterRange(self, output, range_val): # outputs = [1,2,3,4] # range_vals_12 = [0,1,2,3] @@ -119,43 +119,43 @@ def reset(self): # else: # print('RANGE {},{}\r\n'.format(output, range_val)) # return self.write('RANGE {},{}\r\n'.format(output, range_val)) - + def getHeaterRange(self, output): outputs = [1,2,3,4] if output not in outputs: raise ValueError('output is {} but must be integer 1, 2, 3, or 4'.format(output)) else: return self.query('RANGE? {0}'.format(output)) - + def analogOutParamQuery(self, output): outputs = [3,4] if output not in outputs: raise ValueError('output is {} but must be integer 3, or 4'.format(output)) else: return self.query('ANALOG? {0}'.format(output)) - + def getHeaterOutputQuery(self, output): outputs = [1,2] if output not in outputs: raise ValueError('output is {} but must be integer 1, or 2'.format(output)) else: return self.query('HTR? {}'.format(output)) - + def heaterSetup(self, output): outputs = [1,2] if output not in outputs: raise ValueError('output is {} but must be integer 1, or 2'.format(output)) else: return self.query('HTRSET? {}'.format(output)) - - def heaterSet(self, output, resistance, maxcurrent, maxcurrentuser, currentPower): - outputs = [1,2] - resistance = [1,2] - maxcurrent = [0,1,2,3,4] - currentPower = [1,2] - - - if output not in outputs: + + def heaterSet(self, output, resistance, maxcurrent, maxcurrentuser, currentPower): + outputs = [1,2] + resistance = [1,2] + maxcurrent = [0,1,2,3,4] + currentPower = [1,2] + + + if output not in outputs: raise ValueError('output is {} but must be integer 1, or 2'.format(output)) elif resistance not in resistance: raise ValueError('resistance is {} but must be integer 1, or 2'.format(output)) @@ -163,59 +163,58 @@ def heaterSet(self, output, resistance, maxcurrent, maxcurrentuser, currentPower raise ValueError('resistance is {} but must be integer 0, 1, 2, 3, or 4'.format(output)) elif currentPower not in currentPower: raise ValueError('resistance is {} but must be integer 1, or 2'.format(output)) - - elif (maxcurrent not 0): + elif (maxcurrent != 0): maxcurrentuser = 0 - elif (maxcurrent = 0) - maxcurrentuser = maxcurrentuser - else: + elif (maxcurrent == 0): + maxcurrentuser = maxcurrentuser + else: return self.query('HTRSET {} {} {} {} {}'.format(output, resistance, maxcurrent, maxcurrentuser, currentPower)) - + def heaterStatus(self, output): outputs = [1,2] if output not in outputs: raise ValueError('output is {} but must be integer 1, or 2'.format(output)) else: return self.query('HTRST? {}'.format(output)) - + def getOutputMode(self, output): outputs = [1,2,3,4] if output not in outputs: raise ValueError('output is {} but must be integer 1, 2, 3, or 4'.format(output)) else: return self.query('OUTMODE? {}'.format(output)) - + def getAnalogOutData(self, output): outputs = [3,4] if output not in outputs: raise ValueError('output is {} but must be integer 3, or 4'.format(output)) else: return self.query('AOUT? {}'.format(output)) - + def getCelciusReading(self, output): outputs = ["A","B","C","D"] if output not in outputs: raise ValueError('output is {} but must be A, B, C, D'.format(output)) else: return self.query('CRDG? {}'.format(output)) - + def tempLimitQuery(self, output): outputs = ["A","B","C","D"] if output not in outputs: raise ValueError('output is {} but must be A, B, C, D'.format(output)) else: return self.query('TLIMIT? {}'.format(output)) - + #page 130,, data points curve? - - - + + + def warmupSupply(self, output): outputs = [3,4] if output not in outputs: raise ValueError('output is {} but must be integer 3, or 4'.format(output)) else: return self.query('WARMUP? {}'.format(output)) - + def junctionTempQuery(self): self.query('TEMP?') \ No newline at end of file diff --git a/lightlab/equipment/lab_instruments/NI_PCI_6723.py b/lightlab/equipment/lab_instruments/NI_PCI_6723.py index 85d4cb0e..9c9d54dc 100644 --- a/lightlab/equipment/lab_instruments/NI_PCI_6723.py +++ b/lightlab/equipment/lab_instruments/NI_PCI_6723.py @@ -93,7 +93,10 @@ def _query(self, queryStr): def query(self, queryStr, expected_talker=None): ret = self._query(queryStr) if expected_talker is not None: - log_function = logger.warning if ret != expected_talker else logger.debug + if ret != expected_talker: + log_function = logger.warning + else: + log_function = logger.debug log_function("'%s' returned '%s', expected '%s'", queryStr, ret, str(expected_talker)) else: logger.debug("'%s' returned '%s'", queryStr, ret) @@ -112,19 +115,19 @@ def instrID(self): return 'Current Source' def tcpTest(self, num=2): - print(f'x = {str(num)}') + print('x = ' + str(num)) # self.open() - ret = self.query(f'Test: {str(num)} {str(num + .5)}') + ret = self.query('Test: ' + str(num) + ' ' + str(num + .5)) # self.close() retNum = [0] * len(ret.split()) for i, s in enumerate(ret.split(' ')): retNum[i] = float(s) + .01 - print(f'[x+1, x+1.5] = {str(retNum)}') + print('[x+1, x+1.5] = ' + str(retNum)) def setChannelTuning(self, chanValDict, mode, waitTime=None): # pylint: disable=arguments-differ oldState = self.getChannelTuning(mode) # Check range and convert to base units - chanBaseDict = {} + chanBaseDict = dict() for ch, val in chanValDict.items(): try: enforced = self.enforceRange(val, mode) @@ -137,7 +140,7 @@ def setChannelTuning(self, chanValDict, mode, waitTime=None): # pylint: disable super().setChannelTuning(chanBaseDict) # Was there a change - if oldState != self.getChannelTuning(mode): + if not oldState == self.getChannelTuning(mode): self.sendToHardware(waitTime) else: self.wake() @@ -170,7 +173,7 @@ def sendToHardware(self, waitTime=None): # Then write a TCPIP packet writeStr = 'Set:' for v in fullVoltageState: - writeStr += f' {str(v)}' + writeStr += ' ' + str(v) # self.open() retStr = self.query(writeStr) # self.close() diff --git a/lightlab/equipment/lab_instruments/RandS_SMBV100A_VG.py b/lightlab/equipment/lab_instruments/RandS_SMBV100A_VG.py index b0c8f49f..a32bf6ac 100644 --- a/lightlab/equipment/lab_instruments/RandS_SMBV100A_VG.py +++ b/lightlab/equipment/lab_instruments/RandS_SMBV100A_VG.py @@ -41,10 +41,10 @@ def amplitude(self, amp=None): ''' if amp is not None: if amp > 30: - print(f'Warning: R&S ony goes up to +30dBm, given {amp}dBm.') + print('Warning: R&S ony goes up to +30dBm, given {}dBm.'.format(amp)) amp = 15 if amp < -145: - print(f'Warning: R&S ony goes down to -145dBm, given {amp}dBm.') + print('Warning: R&S ony goes down to -145dBm, given {}dBm.'.format(amp)) amp = -145 self.setConfigParam('POW', amp) return self.getConfigParam('POW') @@ -134,7 +134,7 @@ def setPattern(self, bitArray): self.setConfigParam('BB:DM:SOUR', 'PATT') bitArray = np.array(bitArray, dtype=int) onesAndZeros = ''.join([str(b) for b in bitArray]) - pStr = f'#B{onesAndZeros},{len(bitArray)}' + pStr = '#B' + onesAndZeros + ',' + str(len(bitArray)) self.setConfigParam('BB:DM:PATT', pStr) def digiMod(self, enaState=True, symbRate=None, amExtinct=None): @@ -199,14 +199,11 @@ def carrierMod(self, enaState=True, typMod=None, deviation=None, modFreq=None): typMod = typMod.upper() if typMod not in allMods: - raise Exception( - f'{typMod} is not a valid kind of carrier modulation: am, fm, or pm' - ) - + raise Exception(typMod + ' is not a valid kind of carrier modulation: am, fm, or pm') # Generic parameter setup - self.setConfigParam(f'{typMod}:SOUR', 'INT') + self.setConfigParam(typMod + ':SOUR', 'INT') if typMod in ['PM', 'FM']: - self.setConfigParam(f'{typMod}:MODE', 'HDEV') + self.setConfigParam(typMod + ':MODE', 'HDEV') # Specifiable parameter setup if deviation is not None: self.setConfigParam(typMod, deviation) @@ -216,9 +213,9 @@ def carrierMod(self, enaState=True, typMod=None, deviation=None, modFreq=None): if enaState is not None: for mod in allMods: if mod != typMod: - self.__enaBlock(f'{mod}:STAT', False) + self.__enaBlock(mod + ':STAT', False) # Enabling - return self.__enaBlock(f'{typMod}:STAT', enaState) + return self.__enaBlock(typMod + ':STAT', enaState) def listEnable(self, enaState=True, freqs=None, amps=None, isSlave=False, dwell=None): ''' Sets up list mode. @@ -260,9 +257,10 @@ def listEnable(self, enaState=True, freqs=None, amps=None, isSlave=False, dwell= amps = amps * np.ones(len(freqs)) elif np.isscalar(freqs): freqs = freqs * np.ones(len(amps)) - elif len(amps) != len(freqs): - raise ValueError( - 'amps and freqs must have equal lengths, or one/both can be scalars or None') + else: + if len(amps) != len(freqs): + raise ValueError( + 'amps and freqs must have equal lengths, or one/both can be scalars or None') self.setConfigParam('LIST:FREQ', ', '.join([str(int(f)) for f in freqs])) self.setConfigParam('LIST:POW', ', '.join([str(int(a)) for a in amps])) diff --git a/lightlab/equipment/lab_instruments/Tektronix_DPO4032_Oscope.py b/lightlab/equipment/lab_instruments/Tektronix_DPO4032_Oscope.py index 8505f663..b2e7039d 100644 --- a/lightlab/equipment/lab_instruments/Tektronix_DPO4032_Oscope.py +++ b/lightlab/equipment/lab_instruments/Tektronix_DPO4032_Oscope.py @@ -32,10 +32,8 @@ def timebaseConfig(self, avgCnt=None, duration=None): self.setConfigParam('DATA:START', 1) self.setConfigParam('DATA:STOP', int(duration * 2.5e9)) - presentSettings = { - 'avgCnt': self.getConfigParam('ACQUIRE:NUMAVG', forceHardware=True) - } - + presentSettings = dict() + presentSettings['avgCnt'] = self.getConfigParam('ACQUIRE:NUMAVG', forceHardware=True) presentSettings['duration'] = self.getConfigParam( 'HORIZONTAL:MAIN:SCALE', forceHardware=True) # presentSettings['position'] = self.getConfigParam('HORIZONTAL:MAIN:POSITION', forceHardware=True) @@ -61,10 +59,7 @@ def __scaleData(self, voltRaw): YZERO, the reference voltage, YOFF, the offset position, and YSCALE, the conversion factor between position and voltage. ''' - get = lambda param: float( - self.getConfigParam(f'WFMOUTPRE:{param}', forceHardware=True) - ) - + get = lambda param: float(self.getConfigParam('WFMOUTPRE:' + param, forceHardware=True)) voltage = (np.array(voltRaw) - get('YOFF')) \ * get(self._yScaleParam) \ + get('YZERO') @@ -103,19 +98,13 @@ def acquire(self, chans=None, timeout=None, **kwargs): for c in chans: if c > self.totalChans: - raise Exception( - ( - f'Received channel: {str(c)}' - + '. Max channels of this scope is ' - ) - + str(self.totalChans) - ) - + raise Exception('Received channel: ' + str(c) + + '. Max channels of this scope is ' + str(self.totalChans)) # Channel select for ich in range(1, 1 + self.totalChans): thisState = 1 if ich in chans else 0 - self.setConfigParam(f'SELECT:CH{str(ich)}', thisState) + self.setConfigParam('SELECT:CH' + str(ich), thisState) isSampling = kwargs.get('avgCnt', 0) == 1 self._setupSingleShot(isSampling) @@ -150,7 +139,7 @@ def __transferData(self, chan): Todo: Make this binary transfer to go even faster ''' - chStr = f'CH{str(chan)}' + chStr = 'CH' + str(chan) self.setConfigParam('DATA:ENCDG', 'ASCII') self.setConfigParam('DATA:SOURCE', chStr) self.open() diff --git a/lightlab/equipment/lab_instruments/Tektronix_DSA8300_Oscope.py b/lightlab/equipment/lab_instruments/Tektronix_DSA8300_Oscope.py index 0d7808cd..62f3c67b 100644 --- a/lightlab/equipment/lab_instruments/Tektronix_DSA8300_Oscope.py +++ b/lightlab/equipment/lab_instruments/Tektronix_DSA8300_Oscope.py @@ -68,5 +68,5 @@ def histogramStats(self, chan, nWfms=3, untriggered=False): stdDev = self.query('HIS:STAT:STD?') sigmaStats = np.zeros(3) for iSigma in range(3): - sigmaStats[iSigma] = self.query(f'HIS:STAT:SIGMA{iSigma + 1}?') + sigmaStats[iSigma] = self.query('HIS:STAT:SIGMA{}?'.format(iSigma + 1)) return stdDev, sigmaStats diff --git a/lightlab/equipment/lab_instruments/Tektronix_PPG3202.py b/lightlab/equipment/lab_instruments/Tektronix_PPG3202.py index 803514b1..2cede0f2 100644 --- a/lightlab/equipment/lab_instruments/Tektronix_PPG3202.py +++ b/lightlab/equipment/lab_instruments/Tektronix_PPG3202.py @@ -32,11 +32,11 @@ def __setVoltage(self, chan=None, amp=None, offset=None): ''' if amp is not None and chan in self.__channels: time.sleep(self.__waitTime) - cmd = str(f':VOLT{str(chan)}:POS {str(amp)}V') + cmd = str(':VOLT' + str(chan) + ':POS ' + str(amp) + 'V') self.setConfigParam(cmd, None, True) if offset is not None: time.sleep(self.__waitTime) - cmd = str(f':VOLT{str(chan)}:POS:OFFS {str(offset)}V') + cmd = str(':VOLT' + str(chan) + ':POS:OFFS ' + str(offset) + 'V') self.setConfigParam(cmd, None, True) def __setPatternType(self, chan=None, ptype=None): @@ -48,7 +48,7 @@ def __setPatternType(self, chan=None, ptype=None): logger.exception('Wrong Pattern Type!') else: time.sleep(self.__waitTime) - cmd = str(f':DIG{str(chan)}:PATT:TYPE {str(ptype)}') + cmd = str(':DIG' + str(chan) + ':PATT:TYPE ' + str(ptype)) self.setConfigParam(cmd, None, True) def setDataRate(self, rate=None): @@ -60,7 +60,7 @@ def setDataRate(self, rate=None): logger.exception('Invalid Data Rate!') else: time.sleep(self.__waitTime) - cmd = str(f':FREQ {str(rate)}e9') + cmd = str(':FREQ ' + str(rate) + 'e9') self.setConfigParam(cmd, None, True) def setMainParam(self, chan=None, amp=None, offset=None, ptype=None): @@ -76,7 +76,7 @@ def setClockDivider(self, div=None): if div is not None: if (div in self.__ClockDivider): time.sleep(self.__waitTime) - cmd = str(f':OUTP:CLOC:DIV {str(div)}') + cmd = str(':OUTP:CLOC:DIV ' + str(div)) self.setConfigParam(cmd, None, True) else: logger.exception('Wrong Clock Divider Value!') @@ -84,7 +84,7 @@ def setClockDivider(self, div=None): def setDataMemory(self, chan=None, startAddr=None, bit=None, data=None): if chan is not None and chan in self.__channels: time.sleep(self.__waitTime) - cmd = str(f':DIG{str(chan)}:PATT:DATA {str(startAddr)},{str(bit)},{str(data)}') + cmd = str(':DIG' + str(chan) + ':PATT:DATA ' + str(startAddr) + ',' + str(bit) + ',' + str(data)) self.setConfigParam(cmd, None, True) else: logger.exception('Please choose Channel 1 or 2!') @@ -92,10 +92,7 @@ def setDataMemory(self, chan=None, startAddr=None, bit=None, data=None): def setHexDataMemory(self, chan=None, startAddr=None, bit=None, Hdata=None): if chan is not None and chan in self.__channels: time.sleep(self.__waitTime) - cmd = str( - f':DIG{str(chan)}:PATT:HDAT {str(startAddr)},{str(bit)},{str(Hdata)}' - ) - + cmd = str(':DIG' + str(chan) + ':PATT:HDAT ' + str(startAddr) + ',' + str(bit) + ',' + str(Hdata)) self.setConfigParam(cmd, None, True) else: logger.exception('Please choose Channel 1 or 2!') @@ -103,7 +100,7 @@ def setHexDataMemory(self, chan=None, startAddr=None, bit=None, Hdata=None): def channelOn(self, chan=None): if chan is not None and chan in self.__channels: time.sleep(self.__waitTime) - cmd = str(f':OUTP{str(chan)} ON') + cmd = str(':OUTP' + str(chan) + ' ON') self.setConfigParam(cmd, None, True) else: logger.exception('Please choose Channel 1 or 2!') @@ -111,25 +108,25 @@ def channelOn(self, chan=None): def channelOff(self, chan=None): if chan is not None and chan in self.__channels: time.sleep(self.__waitTime) - cmd = str(f':OUTP{str(chan)} OFF') + cmd = str(':OUTP' + str(chan) + ' OFF') self.setConfigParam(cmd, None, True) else: logger.exception('Please choose Channel 1 or 2!') def getAmplitude(self, chan=None): if chan is not None and chan in self.__channels: - return self.query(f':VOLT{str(chan)}:POS?') + return self.query(':VOLT' + str(chan) + ':POS?') def getOffset(self, chan=None): if chan is not None and chan in self.__channels: - return self.query(f':VOLT{str(chan)}:POS:OFFS?') + return self.query(':VOLT' + str(chan) + ':POS:OFFS?') def getDataRate(self): return self.query(':FREQ?') def getPatternType(self, chan=None): if chan is not None and chan in self.__channels: - return self.query(f':DIG{str(chan)}:PATT:TYPE?') + return self.query(':DIG' + str(chan) + ':PATT:TYPE?') def getClockDivider(self): return self.query(':OUTP:CLOC:DIV?') diff --git a/lightlab/equipment/lab_instruments/Tektronix_RSA6120B_RFSA.py b/lightlab/equipment/lab_instruments/Tektronix_RSA6120B_RFSA.py index a130ac58..6c2e5d23 100644 --- a/lightlab/equipment/lab_instruments/Tektronix_RSA6120B_RFSA.py +++ b/lightlab/equipment/lab_instruments/Tektronix_RSA6120B_RFSA.py @@ -36,7 +36,10 @@ def getMeasurements(self): (list[str]): tokens of currently active measurements ''' actWinStr = self.getConfigParam('DISP:WIND:ACT:MEAS', forceHardware=True) - return [aws.strip('" ') for aws in actWinStr.split(',')] + activeWindows = [] + for aws in actWinStr.split(','): + activeWindows.append(aws.strip('" ')) + return activeWindows def setMeasurement(self, measType='SPEC', append=False): ''' Turns on a measurement type @@ -89,6 +92,10 @@ def sgramTransfer(self, duration=1., nLines=100): raise Exception( 'Spectrogram is not being recorded. Did you forget to call sgramInit()?') + # def qg(subParam): + # for Quick Get ''' + # return float(self.getConfigParam('SGR:' + subParam, forceHardware=True)) + # Create data structure with proper size trialLine = self.__sgramLines([0])[0] nFreqs = len(trialLine) @@ -106,7 +113,7 @@ def sgramTransfer(self, duration=1., nLines=100): downsample = int(duration / estTimePerLine / nLines) if downsample < 1: nLines = int(duration / estTimePerLine) - print(f'Warning: line density too high. You will get {nLines} lines.') + print('Warning: line density too high. You will get {} lines.'.format(nLines)) downsample = 1 lineNos = np.arange(nLines, dtype=int) * downsample @@ -126,7 +133,10 @@ def sgramTransfer(self, duration=1., nLines=100): fBasis = np.linspace(fStart, fStop, nFreqs) tBasis = np.linspace(0, duration, nLines) - return Spectrogram([fBasis, tBasis], sgramMat) + # Put this in some kind of 2-D measured function structure. + gram = Spectrogram([fBasis, tBasis], sgramMat) + + return gram def __sgramLines(self, lineNos, container=None, debugEvery=None): if container is None: @@ -135,7 +145,7 @@ def __sgramLines(self, lineNos, container=None, debugEvery=None): for i, lno in enumerate(lineNos): if debugEvery is not None and i % debugEvery == 0: logger.debug('Transferring %s / %s', lno, lineNos[-1]) - self.write(f'TRAC:SGR:SEL:LINE {lno}') + self.write('TRAC:SGR:SEL:LINE {}'.format(lno)) for _ in range(2): # Sometimes the query just fails so we try again rawLine = self.mbSession.query_binary_values('FETCH:SGR?') if len(rawLine) > 0: @@ -215,5 +225,5 @@ def __setupMultiSpectrum(self, typAvg='average', nAvg=None): self.write('TRACE1:SPEC:COUN:RESET') self.write('TRACE1:SPEC:COUN:ENABLE') elif typAvg != 'NONE': - raise Exception(f'{typAvg} is not a valid type of averaging') + raise Exception(typAvg + ' is not a valid type of averaging') self.setConfigParam('TRACE1:SPEC:FUNC', typAvg) diff --git a/lightlab/equipment/lab_instruments/__init__.py b/lightlab/equipment/lab_instruments/__init__.py index f01767b1..6c8267d1 100644 --- a/lightlab/equipment/lab_instruments/__init__.py +++ b/lightlab/equipment/lab_instruments/__init__.py @@ -10,7 +10,10 @@ class BuggyHardware(Exception): ''' Not all instruments behave as they are supposed to. This might be lab specific. atait is not sure exactly how to deal with that. ''' -for _, modname, _ in pkgutil.walk_packages(path=__path__, prefix=f'{__name__}.'): + + +for _, modname, _ in pkgutil.walk_packages(path=__path__, # noqa + prefix=__name__ + '.'): _temp = importlib.import_module(modname) for k, v in _temp.__dict__.items(): if k[0] != '_' and type(v) is not type: @@ -20,3 +23,9 @@ class BuggyHardware(Exception): continue if VISAInstrumentDriver in mro: globals()[k] = v + +# Disable tests for the following packages +experimental_instruments = [ + 'Aragon_BOSA_400_Queens', + 'Lakeshore_Model336', +] \ No newline at end of file diff --git a/lightlab/equipment/visa_bases/driver_base.py b/lightlab/equipment/visa_bases/driver_base.py index ea997d3a..809ca598 100644 --- a/lightlab/equipment/visa_bases/driver_base.py +++ b/lightlab/equipment/visa_bases/driver_base.py @@ -105,8 +105,9 @@ def __init__(self, ip_address, port, timeout=2, termination=LF): self._termination = termination def _send(self, socket, value): - encoded_value = (f'{value}' + self._termination).encode('ascii') - return socket.sendall(encoded_value) + encoded_value = (('%s' % value) + self._termination).encode('ascii') + sent = socket.sendall(encoded_value) + return sent def _recv(self, socket, msg_length=2048): received_value = socket.recv(msg_length) @@ -139,7 +140,9 @@ def connect(self): elapsed_time_ms = 1e3 * (final_time - init_time) logger.debug("Connected. Time elapsed: %s msec", '{:.2f}'.format(elapsed_time_ms)) self._socket = s - return self._socket + return self._socket + else: + return self._socket def disconnect(self): ''' If connected, disconnects and kills the socket.''' diff --git a/lightlab/equipment/visa_bases/prologix_gpib.py b/lightlab/equipment/visa_bases/prologix_gpib.py index bec91cec..b8ac0cf0 100644 --- a/lightlab/equipment/visa_bases/prologix_gpib.py +++ b/lightlab/equipment/visa_bases/prologix_gpib.py @@ -139,30 +139,32 @@ def _is_valid_ip_address(ip_address): def _validate_hostname(hostname): - return bool(_is_valid_hostname(hostname) or _is_valid_ip_address(hostname)) + if _is_valid_hostname(hostname) or _is_valid_ip_address(hostname): + return True + else: + return False def _sanitize_address(address): '''Takes in an address of the form 'prologix://prologix_ip_address/gpib_primary_address[:gpib_secondary_address]' and returns prologix_ip_address, gpib_primary_address, gpib_secondary_address. If secondary address is not given, gpib_secondary_address = None''' - if not address.startswith('prologix://'): - raise RuntimeError(f'invalid address: {address}') - _, address = address.split('prologix://', maxsplit=1) - ip_address, gpib_address = address.split('/', maxsplit=1) - if not _validate_hostname(ip_address): - raise RuntimeError(f"invalid ip address: '{ip_address}'") - try: - if ':' in gpib_address: - gpib_pad, gpib_sad = gpib_address.split(':', maxsplit=1) - gpib_pad, gpib_sad = int(gpib_pad), int(gpib_sad) - else: - gpib_pad, gpib_sad = int(gpib_address), None - except ValueError: - raise RuntimeError( - f"invalid gpib format '{gpib_address}', should be like '10[:0]'" - ) - + if address.startswith('prologix://'): + _, address = address.split('prologix://', maxsplit=1) + ip_address, gpib_address = address.split('/', maxsplit=1) + if not _validate_hostname(ip_address): + raise RuntimeError("invalid ip address: '{}'".format(ip_address)) + try: + if ':' in gpib_address: + gpib_pad, gpib_sad = gpib_address.split(':', maxsplit=1) + gpib_pad, gpib_sad = int(gpib_pad), int(gpib_sad) + else: + gpib_pad, gpib_sad = int(gpib_address), None + except ValueError: + raise RuntimeError( + "invalid gpib format '{}', should be like '10[:0]'".format(gpib_address)) + else: + raise RuntimeError('invalid address: {}'.format(address)) return ip_address, gpib_pad, gpib_sad @@ -177,7 +179,7 @@ def __init__(self, address=None, tempSess=False): ''' if type(address) != str: - raise RuntimeError(f"Invalid address: {address}") + raise RuntimeError("Invalid address: {}".format(address)) self.tempSess = tempSess self.address = address @@ -207,8 +209,9 @@ def spoll(self): '''Return status byte of the instrument.''' gpib_addr = self._prologix_gpib_addr_formatted() - spoll = self._prologix_rm.query(f'++spoll {gpib_addr}') - return int(spoll.rstrip()) + spoll = self._prologix_rm.query('++spoll {}'.format(gpib_addr)) + status_byte = int(spoll.rstrip()) + return status_byte def LLO(self): '''This command disables front panel operation of the currently addressed instrument.''' @@ -245,9 +248,9 @@ def termination(self, value): elif value == '': eos = 3 else: - print(f"Invalid termination: {repr(value)}") + print("Invalid termination: {}".format(repr(value))) if eos is not None: - self._prologix_rm.send(f'++eos {eos}') + self._prologix_rm.send('++eos {}'.format(eos)) def open(self): ''' Open connection with instrument. @@ -265,7 +268,7 @@ def close(self): def write(self, writeStr): with self._prologix_rm.connected() as pconn: - pconn.send(f'++addr {self._prologix_gpib_addr_formatted()}') + pconn.send('++addr {}'.format(self._prologix_gpib_addr_formatted())) pconn.send(self._prologix_escape_characters(writeStr)) # def read(self, withTimeout=None): @@ -290,7 +293,7 @@ def wait(self, bigMsTimeout=10000): def clear(self): '''This command sends the Selected Device Clear (SDC) message to the currently specified GPIB address.''' with self._prologix_rm.connected() as pconn: - pconn.send(f'++addr {self._prologix_gpib_addr_formatted()}') + pconn.send('++addr {}'.format(self._prologix_gpib_addr_formatted())) pconn.send('++clr') def query_raw_binary(self, queryStr, withTimeout=None): @@ -299,7 +302,7 @@ def query_raw_binary(self, queryStr, withTimeout=None): are stripped. Also no decoding.''' with self._prologix_rm.connected() as pconn: - pconn.send(f'++addr {self._prologix_gpib_addr_formatted()}') + pconn.send('++addr {}'.format(self._prologix_gpib_addr_formatted())) pconn.send(self._prologix_escape_characters(queryStr)) if withTimeout is not None: diff --git a/lightlab/equipment/visa_bases/visa_driver.py b/lightlab/equipment/visa_bases/visa_driver.py index d768c914..620d28fb 100644 --- a/lightlab/equipment/visa_bases/visa_driver.py +++ b/lightlab/equipment/visa_bases/visa_driver.py @@ -15,16 +15,11 @@ class _AttrGetter(object): # https://stackoverflow.com/questions/28861064/super-object-has-no-attribute-getattr-in-python3 def __getattr__(self, name): - raise AttributeError(f"'{str(self)}' has no attribute '{name}'") + raise AttributeError("'{}' has no attribute '{}'".format(str(self), name)) -_InstrumentSessionBase_methods = [ - name - for name, _ in inspect.getmembers( - InstrumentSessionBase, - lambda o: inspect.isfunction(o) or isinstance(o, property), - ) -] +_InstrumentSessionBase_methods = list(name for name, _ in inspect.getmembers( + InstrumentSessionBase, lambda o: inspect.isfunction(o) or isinstance(o, property))) class InstrumentSession(_AttrGetter): @@ -66,38 +61,37 @@ def close(self): def __getattr__(self, name): if name in ('_session_object'): return super().__getattr__(name) - try: - return_attr = getattr(self._session_object, name) - except AttributeError: - return_attr = super().__getattr__(name) # pylint: disable=assignment-from-no-return else: - if name not in _InstrumentSessionBase_methods: - visalogger.warning("Access to %s.%s will be deprecated soon. " - "Please include it in InstrumentSessionBase. " - "", type(self._session_object).__name__, name) - - return return_attr + try: + return_attr = getattr(self._session_object, name) + except AttributeError: + return_attr = super().__getattr__(name) # pylint: disable=assignment-from-no-return + else: + if name not in _InstrumentSessionBase_methods: + visalogger.warning("Access to %s.%s will be deprecated soon. " + "Please include it in InstrumentSessionBase. " + "", type(self._session_object).__name__, name) + + return return_attr def __dir__(self): return set(super().__dir__() + list(_InstrumentSessionBase_methods)) def __setattr__(self, name, value): - if ( - name in ('_session_object') - or name not in 'address' - and not hasattr(self._session_object, name) - ): + if name in ('_session_object'): super().__setattr__(name, value) - elif name in 'address': + elif name in ('address'): super().__setattr__(name, value) if self._session_object is not None and self._session_object.address != value: tempSess = self._session_object.tempSess self.reinstantiate_session(address=value, tempSess=tempSess) - else: + elif hasattr(self._session_object, name): setattr(self._session_object, name, value) # also change in local dictionary if possible if name in self.__dict__: self.__dict__[name] = value + else: + super().__setattr__(name, value) class IncompleteClass(Exception): @@ -124,14 +118,8 @@ def __init__(cls, name, bases, dct): inst_klass = cls.instrument_category for essential in inst_klass.essentialMethods + inst_klass.essentialProperties: if essential not in dir(cls): - raise IncompleteClass( - ( - cls.__name__ - + f' does not implement {essential}, ' - + f'which is essential for {inst_klass.__name__}' - ) - ) - + raise IncompleteClass(cls.__name__ + ' does not implement {}, '.format(essential) + + 'which is essential for {}'.format(inst_klass.__name__)) super().__init__(name, bases, dct) def __call__(cls, name=None, address=None, *args, **kwargs): diff --git a/lightlab/laboratory/__init__.py b/lightlab/laboratory/__init__.py index 5ccd461f..68cd097e 100644 --- a/lightlab/laboratory/__init__.py +++ b/lightlab/laboratory/__init__.py @@ -45,18 +45,22 @@ class Hashable(object): No instance variables starting with "__" will be serialized. """ - context = jsonpickle.pickler.Pickler( - unpicklable=True, warn=True, keys=True) - def __eq__(self, other): + + def __json_self(self): + context = jsonpickle.pickler.Pickler( + unpicklable=True, warn=True, keys=True) jsonpickle.set_encoder_options('json', sort_keys=True) - json_self = self.context.flatten(self, reset=True) - json_other = self.context.flatten(other, reset=True) + json_self = context.flatten(self, reset=True) + return json_self + + def __eq__(self, other): + json_self = self.__json_self() + json_other = Hashable.__json_self(other) return json_self == json_other def __hash__(self): - jsonpickle.set_encoder_options('json', sort_keys=True) - json_self = self.context.flatten(self, reset=True) + json_self = self.__json_self() return hash(jsonpickle.json.encode(json_self)) def __init__(self, **kwargs): @@ -70,14 +74,18 @@ def __getstate__(self): serialization. Variables named as such are actually stored different in self.__dict__. Check PEP 8. ''' - klassnames = [self.__class__.__name__.lstrip('_')] - klassnames.extend(base.__name__.lstrip('_') for base in self.__class__.mro()) + klassnames = [] + klassnames.append(self.__class__.__name__.lstrip('_')) + + for base in self.__class__.mro(): + klassnames.append(base.__name__.lstrip('_')) + state = self.__dict__.copy() keys_to_delete = set() for key in state.keys(): if isinstance(key, str): for klassname in klassnames: - if key.startswith(f"_{klassname}__"): + if key.startswith("_{}__".format(klassname)): keys_to_delete.add(key) if key.startswith("__"): keys_to_delete.add(key) @@ -145,7 +153,7 @@ class NamedList(MutableSequence, Hashable): read_only = False def __init__(self, *args, read_only=False): # pylint: disable=super-init-not-called - self.list = [] + self.list = list() self.extend(list(args)) self.read_only = read_only @@ -169,13 +177,17 @@ def check(self, value): raise TypeError(f"{type(value)} does not have name.") def check_presence(self, name): - return [idx for idx, elem in enumerate(self) if elem.name == name] + matching_idxs = [idx for idx, elem in enumerate( + self) if elem.name == name] + return matching_idxs def __len__(self): return len(self.list) def __getitem__(self, i): - return self.dict[i] if isinstance(i, str) else self.list[i] + if isinstance(i, str): + return self.dict[i] + return self.list[i] def __delitem__(self, i): if self.read_only: diff --git a/lightlab/laboratory/experiments.py b/lightlab/laboratory/experiments.py index a2f9cea7..08f05f9f 100644 --- a/lightlab/laboratory/experiments.py +++ b/lightlab/laboratory/experiments.py @@ -44,7 +44,10 @@ class Experiment(Virtualizable): @property def lab(self): - return labstate.lab if self._lab is None else self._lab + if self._lab is None: + return labstate.lab + else: + return self._lab @lab.setter def lab(self, value): @@ -67,11 +70,17 @@ def is_valid(self, reset=True): def __init__(self, instruments=None, devices=None, **kwargs): super().__init__() - self.instruments = instruments if instruments is not None else list() - self.instruments_requirements = [] - self.devices = devices if devices is not None else list() - self.validate_exprs = [] - self.connections = [] + if instruments is not None: + self.instruments = instruments + else: + self.instruments = list() + self.instruments_requirements = list() + if devices is not None: + self.devices = devices + else: + self.devices = list() + self.validate_exprs = list() + self.connections = list() self.name = kwargs.pop("name", None) self.startup(**kwargs) @@ -171,8 +180,9 @@ def registerConnections(self, *connections): def connection_present(connection=connection, connections=self.lab.connections): if connection in connections: return True - logger.error("Connection {} is not compatible with lab %s", connection) - return False + else: + logger.error("Connection {} is not compatible with lab %s", connection) + return False self.validate_exprs.append(connection_present) def validate(self): @@ -194,37 +204,35 @@ def unlock(self): def __str__(self): if self.name is not None: - return f"Experiment {self.name}" - return f"Experiment {self.__class__.__name__}" + return "Experiment {}".format(self.name) + return "Experiment {}".format(self.__class__.__name__) def display(self): - lines = [f"{self}"] + lines = ["{}".format(self)] if self.valid: lines.append("Experiment is online!") else: lines.append("Experiment is offline.") - lines.extend(("===========", "Expected Instruments", "===========")) + lines.append("===========") + lines.append("Expected Instruments") + lines.append("===========") if len(self.instruments_requirements) > 0: - lines.extend( - [ - f" {str(instrument)} in ({str(host)}, {str(bench)})" - for instrument, host, bench in self.instruments_requirements - ] - ) - + lines.extend([" {} in ({}, {})".format(str(instrument), str(host), str(bench)) + for instrument, host, bench + in self.instruments_requirements]) else: lines.append(" No instruments.") - lines.extend(("=======", "Expected Connections", "=======")) + lines.append("=======") + lines.append("Expected Connections") + lines.append("=======") if len(self.connections) > 0: for connection in self.connections: connection_items = list(connection.items()) from_dev, from_port = tuple(connection_items[0]) to_dev, to_port = tuple(connection_items[1]) - lines.append( - f" {str(from_dev)}/{str(from_port)} <-> {str(to_dev)}/{str(to_port)}" - ) - + lines.append(" {}/{} <-> {}/{}".format(str(from_dev), str(from_port), + str(to_dev), str(to_port))) else: lines.append(" No connections.") lines.append("***") diff --git a/lightlab/laboratory/instruments/bases.py b/lightlab/laboratory/instruments/bases.py index 457c3af6..3cf83345 100644 --- a/lightlab/laboratory/instruments/bases.py +++ b/lightlab/laboratory/instruments/bases.py @@ -53,7 +53,7 @@ def isLive(self): ''' if self.hostname is not None: logger.debug("Pinging %s...", self.hostname) - response = os.system(f"ping -c 1 {self.hostname}") + response = os.system("ping -c 1 {}".format(self.hostname)) if response != 0: logger.warning("%s is not reachable via ping.", self) return response == 0 @@ -69,7 +69,7 @@ def _visa_prefix(self): Returns: (str) ''' - return f'visa://{self.hostname}/' + return 'visa://{}/'.format(self.hostname) def gpib_port_to_address(self, port, board=0): ''' @@ -80,7 +80,7 @@ def gpib_port_to_address(self, port, board=0): Returns: (str): the address that can be used in an initializer ''' - localSerialStr = f'GPIB{board}::{port}::INSTR' + localSerialStr = 'GPIB{}::{}::INSTR'.format(board, port) return self._visa_prefix() + localSerialStr def list_resources_info(self, use_cached=True): @@ -96,13 +96,15 @@ def list_resources_info(self, use_cached=True): """ if self.__cached_list_resources_info is None: use_cached = False - if not use_cached: - list_query = f"{self._visa_prefix()}?*::INSTR" + if use_cached: + return self.__cached_list_resources_info + else: + list_query = self._visa_prefix() + "?*::INSTR" rm = pyvisa.ResourceManager() logger.debug("Caching resource list in %s", self) self.__cached_list_resources_info = rm.list_resources_info( query=list_query) - return self.__cached_list_resources_info + return self.__cached_list_resources_info def list_gpib_resources_info(self, use_cached=True): """ Like :meth:`list_resources_info`, but only returns gpib @@ -137,17 +139,18 @@ def get_all_gpib_id(self, use_cached=True): use_cached = False if use_cached: return self.__cached_gpib_instrument_list - gpib_instrument_list = {} - logger.debug("Caching GPIB instrument list in %s", self) - for gpib_address in gpib_resources.keys(): - visa_object = VISAObject(gpib_address, tempSess=True) - try: - instr_id = visa_object.instrID() - gpib_instrument_list[gpib_address] = instr_id - except pyvisa.VisaIOError as err: - logger.error(err) - self.__cached_gpib_instrument_list = gpib_instrument_list - return gpib_instrument_list + else: + gpib_instrument_list = dict() + logger.debug("Caching GPIB instrument list in %s", self) + for gpib_address in gpib_resources.keys(): + visa_object = VISAObject(gpib_address, tempSess=True) + try: + instr_id = visa_object.instrID() + gpib_instrument_list[gpib_address] = instr_id + except pyvisa.VisaIOError as err: + logger.error(err) + self.__cached_gpib_instrument_list = gpib_instrument_list + return gpib_instrument_list def findGpibAddressById(self, id_string_search, use_cached=True): """ Finds a gpib address using :meth:`get_all_gpib_id`, given @@ -170,7 +173,8 @@ def findGpibAddressById(self, id_string_search, use_cached=True): logger.info("Found %s in %s.", id_string_search, gpib_address) return gpib_address logger.warning("%s not found in %s", id_string_search, self) - raise NotFoundError(f"{id_string_search} not found in {self}") + raise NotFoundError( + "{} not found in {}".format(id_string_search, self)) def addInstrument(self, *instruments): r""" Adds an instrument to lab.instruments if it is not already present. @@ -218,19 +222,17 @@ def checkInstrumentsLive(self): return all_live def __str__(self): - return f"Host {self.name}" + return "Host {}".format(self.name) def display(self): """ Displays the host's instrument table in a nice format.""" - lines = [f"{self}", "===========", "Instruments", "==========="] + lines = ["{}".format(self)] + lines.append("===========") + lines.append("Instruments") + lines.append("===========") if len(self.instruments) > 0: - lines.extend( - [ - f" {str(instrument)} ({str(instrument.host)})" - for instrument in self.instruments - ] - ) - + lines.extend([" {} ({})".format(str(instrument), str(instrument.host)) + for instrument in self.instruments]) else: lines.append(" No instruments.") lines.append("***") @@ -358,27 +360,27 @@ def removeDevice(self, *devices): def display(self): """ Displays the bench's table in a nice format.""" - lines = [f"{self}", "===========", "Instruments", "==========="] + lines = ["{}".format(self)] + lines.append("===========") + lines.append("Instruments") + lines.append("===========") if len(self.instruments) > 0: - lines.extend( - [ - f" {str(instrument)} ({str(instrument.host)})" - for instrument in self.instruments - ] - ) - + lines.extend([" {} ({})".format(str(instrument), str(instrument.host)) + for instrument in self.instruments]) else: lines.append(" No instruments.") - lines.extend(("=======", "Devices", "=======")) + lines.append("=======") + lines.append("Devices") + lines.append("=======") if len(self.devices) > 0: - lines.extend([f" {str(device)}" for device in self.devices]) + lines.extend([" {}".format(str(device)) for device in self.devices]) else: lines.append(" No devices.") lines.append("***") print("\n".join(lines)) def __str__(self): - return f"Bench {self.name}" + return "Bench {}".format(self.name) class Instrument(Node): @@ -441,11 +443,11 @@ def __dir__(self): @property def implementedOptionals(self): - return [ - opAttr - for opAttr in self.optionalAttributes - if hasattr(self._driver_class, opAttr) - ] + implementedOptionals = list() + for opAttr in self.optionalAttributes: + if hasattr(self._driver_class, opAttr): + implementedOptionals.append(opAttr) + return implementedOptionals # These control feedthroughs to the driver def __getattr__(self, attrName): @@ -475,9 +477,10 @@ def __setattr__(self, attrName, newVal): + self.implementedOptionals: setattr(self.driver, attrName, newVal) else: - if attrName == 'address' and self.__driver_object is not None: - self.__driver_object.close() - self.__driver_object.address = newVal + if attrName == 'address': # Reinitialize the driver + if self.__driver_object is not None: + self.__driver_object.close() + self.__driver_object.address = newVal super().__setattr__(mangle(attrName, self.__class__.__name__), newVal) def __delattr__(self, attrName): @@ -534,10 +537,11 @@ def driver_class(self): This way the object knows how to instantiate a driver instance from the labstate. """ - if self._driver_class is not None: + if self._driver_class is None: + logger.warning("Using default driver for %s.", self) + return DefaultDriver + else: return self._driver_class - logger.warning("Using default driver for %s.", self) - return DefaultDriver @property def driver_object(self): @@ -546,7 +550,7 @@ def driver_object(self): try: kwargs = self.driver_kwargs except AttributeError: # Fall back to the jank version where we try to guess what is important - kwargs = {} + kwargs = dict() for kwarg in ["useChans", "elChans", "dfbChans", "stateDict", "sourceMode"]: try: kwargs[kwarg] = getattr(self, kwarg) @@ -581,42 +585,38 @@ def id_string(self): return self._id_string def __str__(self): - return f"{self.name}" + return "{}".format(self.name) def __repr__(self): - return f"<{self.__class__.__name__} name={self.name}, address={self.address}, id={id(self)}>" + return "<{} name={}, address={}, id={}>".format(self.__class__.__name__, + self.name, self.address, id(self)) def display(self): """ Displays the instrument's info table in a nice format.""" - lines = [ - f"{self}", - f"Bench: {self.bench}", - f"Host: {self.host}", - f"address: {self.address}", - f"id_string: {self.id_string}", - ] - + lines = ["{}".format(self)] + lines.append("Bench: {}".format(self.bench)) + lines.append("Host: {}".format(self.host)) + lines.append("address: {}".format(self.address)) + lines.append("id_string: {}".format(self.id_string)) if not self.id_string: lines.append("The id_string should match the value returned by" " self.driver.instrID(), and is checked by the command" " self.isLive() in order to authenticate that the intrument" " in that address is the intended one.") - lines.extend( - ( - f"driver_class: {self.driver_class.__name__}", - "=====", - "Ports", - "=====", - ) - ) - + lines.append("driver_class: {}".format(self.driver_class.__name__)) + lines.append("=====") + lines.append("Ports") + lines.append("=====") if len(self.ports) > 0: - lines.extend([f" {str(port)}" for port in self.ports]) + lines.extend([" {}".format(str(port)) for port in self.ports]) else: lines.append(" No ports.") if hasattr(self, 'driver_kwargs'): - lines.extend(("=====", "Driver kwargs", "=====")) - lines.extend(f" {str(k)} = {str(v)}" for k, v in self.driver_kwargs.items()) + lines.append("=====") + lines.append("Driver kwargs") + lines.append("=====") + for k, v in self.driver_kwargs.items(): + lines.append(" {} = {}".format(str(k), str(v))) lines.append("***") print("\n".join(lines)) @@ -676,9 +676,7 @@ class MockInstrument(Instrument): def __getattr__(self, attrName): def noop(*args, **kwargs): - raise AttributeError( - f"Attempted to call method ('{attrName}') of a mock Instrument." - ) + raise AttributeError("Attempted to call method ('{}') of a mock Instrument.".format(attrName)) return noop @@ -709,13 +707,17 @@ def __init__(self, name, **kwargs): bench = typed_property(Bench, '_bench') def __str__(self): - return f"Device {self.name}" + return "Device {}".format(self.name) def display(self): """ Displays the device's info table in a nice format.""" - lines = [f"{self}", f"Bench: {self.bench}", "=====", "Ports", "====="] + lines = ["{}".format(self)] + lines.append("Bench: {}".format(self.bench)) + lines.append("=====") + lines.append("Ports") + lines.append("=====") if len(self.ports) > 0: - lines.extend([f" {str(port)}" for port in self.ports]) + lines.extend([" {}".format(str(port)) for port in self.ports]) else: lines.append(" No ports.") lines.append("***") diff --git a/lightlab/laboratory/state.py b/lightlab/laboratory/state.py index a6c188e5..9b259dde 100644 --- a/lightlab/laboratory/state.py +++ b/lightlab/laboratory/state.py @@ -113,7 +113,7 @@ def instruments_dict(self): # TODO DEPRECATE def __init__(self, filename=None): self.hosts = TypedList(Host) self.benches = TypedList(Bench) - self.connections = [] + self.connections = list() self.devices = TypedList(Device) self.instruments = TypedList(Instrument) if filename is None: @@ -370,12 +370,8 @@ def loadState(cls, filename=None, validateHash=True): sha256 = json_state.pop("__sha256__") jsonpickle.set_encoder_options('json', sort_keys=True, indent=4) if validateHash and sha256 != hash_sha256(json.encode(json_state)): - raise JSONDecodeError( - f"Labstate is corrupted. expected: {sha256} vs actual: {hash_sha256(json.encode(json_state))}.", - str(filename), - 0, - ) - + raise JSONDecodeError("Labstate is corrupted. expected: {} vs actual: {}.".format( + sha256, hash_sha256(json.encode(json_state))), str(filename), 0) # Compare versions of file vs. class version = json_state.pop("__version__") @@ -430,7 +426,10 @@ def _toJSON(self): @property def filename(self): """ Filename used to serialize labstate.""" - return _filename if self.__filename__ is None else self.__filename__ + if self.__filename__ is None: + return _filename + else: + return self.__filename__ @filename.setter def filename(self, fname): @@ -461,12 +460,13 @@ def saveState(self, fname=None, save_backup=True): self._saveState(fname, save_backup=False) return except JSONDecodeError: - if os.stat(fname).st_size != 0: + if os.stat(fname).st_size == 0: + logger.warning("%s is empty. Saving for the first time.", _filename) + self._saveState(fname, save_backup=False) + return + else: raise - logger.warning("%s is empty. Saving for the first time.", _filename) - self._saveState(fname, save_backup=False) - return if not self.__sha256__: logger.debug("Attempting to compare fabricated labstate vs. preloaded one.") self.__sha256__ = self.__toJSON()["__sha256__"] @@ -489,14 +489,13 @@ def _saveState(self, fname=None, save_backup=True): filepath = Path(fname).resolve() # it is good to backup this file in caseit exists - if save_backup and filepath.exists(): + if save_backup: + if filepath.exists(): # pylint: disable=no-member # gets folder/filename.* and transforms into folder/filename_{timestamp}.json - filepath_backup = Path(filepath).with_name( - f"{filepath.stem}_{timestamp_string()}.json" - ) - - logger.debug("Backup %s to %s", filepath, filepath_backup) - shutil.copy2(filepath, filepath_backup) + filepath_backup = Path(filepath).with_name( + "{}_{}.json".format(filepath.stem, timestamp_string())) + logger.debug("Backup %s to %s", filepath, filepath_backup) + shutil.copy2(filepath, filepath_backup) # save to filepath, overwriting filepath.touch() # pylint: disable=no-member @@ -515,6 +514,13 @@ def init_module(module): except (OSError) as e: logger.error("%s: %s.", e.__class__.__name__, e) empty_lab = True + except JSONDecodeError as e: + if os.stat(_filename).st_size == 0: + logger.warning("%s is empty.", _filename) + else: + logger.error("%s: %s is corrupted. %s.", e.__class__.__name__, _filename, e) + empty_lab = True + if empty_lab: logger.warning("Starting fresh new LabState(). " "Save for the first time with lab._saveState()") diff --git a/lightlab/laboratory/virtualization.py b/lightlab/laboratory/virtualization.py index c3a3e927..7b487847 100644 --- a/lightlab/laboratory/virtualization.py +++ b/lightlab/laboratory/virtualization.py @@ -32,7 +32,7 @@ class Virtualizable(object): synced = None def __init__(self): - self.synced = [] + self.synced = list() def synchronize(self, *newVirtualizables): r''' Adds another object that this one will put in the same virtual @@ -56,7 +56,7 @@ def __setAll(self, toVirtual): Returns: (list): the previous virtual states ''' - old_values = [] + old_values = list() for sub in ([self] + self.synced): old_values.append(sub._virtual) # pylint: disable=protected-access sub._virtual = toVirtual # pylint: disable=protected-access @@ -202,21 +202,18 @@ def __init__(self, real_obj=None, virt_obj=None): self.real_obj = real_obj self.virt_obj = virt_obj if real_obj is not None and virt_obj is not None: + violated = [] allowed = real_obj.essentialMethods + \ real_obj.essentialProperties + dir(VirtualInstrument) - if violated := [ - attr - for attr in dir(type(virt_obj)) - if attr not in allowed and '__' not in attr - ]: - logger.warning( - ( - f'Virtual instrument ({type(virt_obj).__name__}) violates ' - + f'interface of the real one ({type(real_obj).__name__})' - ) - ) - + for attr in dir(type(virt_obj)): + if attr not in allowed \ + and '__' not in attr: + violated.append(attr) + if len(violated) > 0: + logger.warning('Virtual instrument ({}) violates '.format(type(virt_obj).__name__) + + 'interface of the real one ({})'.format(type(real_obj).__name__)) logger.warning('Got: ' + ', '.join(violated)) # pylint: disable=logging-not-lazy + # logger.warning('Allowed: ' + ', '.join(filter(lambda x: '__' not in x, allowed))) self.synced = [] super().__init__() diff --git a/lightlab/util/characterize.py b/lightlab/util/characterize.py index 384baf3c..e8542df1 100644 --- a/lightlab/util/characterize.py +++ b/lightlab/util/characterize.py @@ -11,7 +11,7 @@ from .data import FunctionBundle -def strobeTest(fActuate, fSense, fReset=None, nPts=10, maxDelay=1, visualize=True): # pylint: disable=W0613 +def strobeTest(fActuate, fSense, fReset=None, nPts=10, maxDelay=1, visualize=True): # pylint: disable=W0613 ''' Looks at a sense variable at different delays after calling an actuate function. Good for determining the time needed to wait for settling. Calls each function once per delay point to construct a picture like the strobe experiment, or a sampling scope @@ -29,7 +29,10 @@ def strobeTest(fActuate, fSense, fReset=None, nPts=10, maxDelay=1, visualize=Tru # Figure out if sense is scalar or array; wrap accordingly testV = fSense() - w = 1 if np.isscalar(testV) else len(testV) + if np.isscalar(testV): + w = 1 + else: + w = len(testV) fWrapped = lambda: np.reshape(np.array(fSense()), (1, w)) t = np.zeros((nPts, 1)) @@ -142,7 +145,10 @@ def monitorVariable(fValue, sleepSec=0, nReps=100, plotEvery=1): curves = None testV = fValue() - w = 1 if np.isscalar(testV) else len(testV) + if np.isscalar(testV): + w = 1 + else: + w = len(testV) fWrapped = lambda: np.reshape(np.array(fValue()), (1, w)) t0 = time.time() timeFun = lambda: time.time() - t0 diff --git a/lightlab/util/config.py b/lightlab/util/config.py index 03eb65f4..2fe726dc 100644 --- a/lightlab/util/config.py +++ b/lightlab/util/config.py @@ -3,6 +3,12 @@ from configparser import ConfigParser from pathlib import Path import argparse +try: + from os import getuid +except ImportError: + # User is on windows + def getuid(): + return 1 user_config_path = os.path.expanduser("~") + "/.lightlab" + "/config.conf" user_config_path = Path(user_config_path).resolve() @@ -26,7 +32,7 @@ def get_config(): read_files.append(system_config_path) if os.path.isfile(user_config_path): read_files.append(user_config_path) - if read_files: + if len(read_files) > 0: config.read(read_files) return config @@ -37,9 +43,9 @@ def parse_param(param): split_param = param.split(".") section, item = None, None if len(split_param) > 0: - section = split_param[0] or None + section = split_param[0] if split_param[0] else None if len(split_param) > 1: - item = split_param[1] or None + item = split_param[1] if split_param[1] else None return (section, item) @@ -80,7 +86,7 @@ def print_config_param(param): if section is not None and item is not None: gotten_param = get_config_param(param) print(f"{section}.{item}: {gotten_param}") - elif section is not None: + elif section is not None and item is None: for key, value in config[section].items(): print(f"{section}.{key}: {value}") else: @@ -108,7 +114,7 @@ def reset_config_param(param): if section is not None and item is not None: config.remove_option(section, item) print(f"{section}.{item} reset.") - elif section is not None: + elif section is not None and item is None: config.remove_section(section) print(f"{section}.* reset.") config_save(config) @@ -122,24 +128,18 @@ def config_save(config, omit_default=True): unset_items = [] for section in config.sections(): if section in default_config.keys(): - unset_items.extend( - (section, option) - for option in config[section].keys() - if option in default_config[section].keys() - and config[section][option] - == default_config[section][option] - ) - + for option in config[section].keys(): + if option in default_config[section].keys(): + if config[section][option] == default_config[section][option]: + unset_items.append((section, option)) for section, item in unset_items: config.remove_option(section, item) # remove all sections that are default - unset_sections = [ - section - for section in config.sections() - if len(config[section].keys()) == 0 - ] - + unset_sections = [] + for section in config.sections(): + if len(config[section].keys()) == 0: + unset_sections.append(section) for section in unset_sections: config.remove_section(section) @@ -154,7 +154,7 @@ def config_save(config, omit_default=True): config.write(user_config_file) print(f'----saving {user_config_path}----', file=sys.stderr) config.write(sys.stderr) - print(f'----{"-" * len(f"saving {user_config_path}")}----', file=sys.stderr) + print('----{}----'.format("-" * len(f"saving {user_config_path}")), file=sys.stderr) return True @@ -174,11 +174,14 @@ def config_save(config, omit_default=True): def config_main(args): config_args = config_cmd_parser.parse_args(args) + def is_root(): + return getuid() == 0 + # If --system is set, change system_config_path if config_args.system: global user_config_path # pylint: disable=W0603 user_config_path = system_config_path - elif os.getuid() == 0 and not os.environ.get('DOCKER'): + elif is_root() and not os.environ.get('DOCKER'): raise SystemExit("Do not run as root except with --system flag.") params = config_args.params @@ -192,16 +195,17 @@ def config_main(args): else: print_config_param(None) elif config_args.action == 'set': - if len(params) != 2: - raise SystemExit("Invalid syntax. Use lightlab config set section.item value.") - param = params[0] - set_value = params[1] - set_config_param(param, set_value) + if len(params) == 2: + param = params[0] + set_value = params[1] + set_config_param(param, set_value) + else: + raise SystemExit(f"Invalid syntax. Use lightlab config set section.item value.") elif config_args.action == 'reset': if len(params) == 1: param = params[0] reset_config_param(param) else: - raise SystemExit("Invalid syntax. Use lightlab config reset section[.item]") + raise SystemExit(f"Invalid syntax. Use lightlab config reset section[.item]") else: config_cmd_parser.print_help() diff --git a/lightlab/util/data/basic.py b/lightlab/util/data/basic.py index 01be8997..9284a62b 100644 --- a/lightlab/util/data/basic.py +++ b/lightlab/util/data/basic.py @@ -16,11 +16,8 @@ def verifyListOfType(arg, checkType): if isinstance(arg, (list, tuple)): for a in arg: if not isinstance(a, checkType): - raise Exception( - (f'Incorrect type, expecting {str(checkType)}' + '. Got ') - + str(type(a)) - ) - + raise Exception('Incorrect type, expecting ' + str(checkType) + + '. Got ' + str(type(a))) return arg @@ -82,7 +79,7 @@ def mangle(name, klass): try: i = 0 while klass[i] == '_': - i += 1 + i = i + 1 except IndexError: return name klass = klass[i:] @@ -91,7 +88,7 @@ def mangle(name, klass): if tlen > MANGLE_LEN: klass = klass[:MANGLE_LEN - tlen] - return f"_{klass}{name}" + return "_%s%s" % (klass, name) # Simple common array operations diff --git a/lightlab/util/data/one_dim.py b/lightlab/util/data/one_dim.py index 00f029ce..bf988019 100644 --- a/lightlab/util/data/one_dim.py +++ b/lightlab/util/data/one_dim.py @@ -116,9 +116,7 @@ def __init__(self, abscissaPoints, ordinatePoints, unsafe=False): if isinstance(arr, np.ndarray): if arr.ndim > 1: raise ValueError( - f'Must be a one dimensional array. Got shape {str(arr.shape)}' - ) - + 'Must be a one dimensional array. Got shape ' + str(arr.shape)) if arr.ndim == 0: arr = np.array([arr]) checkVals[iv] = arr.copy() @@ -127,13 +125,8 @@ def __init__(self, abscissaPoints, ordinatePoints, unsafe=False): elif np.isscalar(arr): checkVals[iv] = np.array([arr]) else: - raise TypeError( - ( - f'Unsupported type: {str(type(arr))}' - + '. Need np.ndarray, scalar, list, or tuple' - ) - ) - + raise TypeError('Unsupported type: ' + str(type(arr)) + + '. Need np.ndarray, scalar, list, or tuple') self.absc, self.ordi = tuple(checkVals) if self.absc.shape != self.ordi.shape: raise ValueError('Shapes do not match. Got ' + @@ -158,7 +151,7 @@ def __len__(self): return len(self.absc) def __iter__(self): - raise TypeError(f"{self.__class__.__qualname__} is not iterable") + raise TypeError("{} is not iterable".format(self.__class__.__qualname__)) def __getitem__(self, sl): ''' Slice this function. @@ -410,7 +403,10 @@ def uniformlySample(self): MeasuredFunction: new object ''' dxes = np.diff(self.absc) - return self if all(dxes == dxes[0]) else self.resample(len(self)) + if all(dxes == dxes[0]): + return self + else: + return self.resample(len(self)) def addPoint(self, xyPoint): ''' Adds the (x, y) point to the stored absc and ordi @@ -480,22 +476,13 @@ def movingAverage(self, windowWidth=None, mode='valid'): ''' if windowWidth is None: windowWidth = (max(self.absc) - min(self.absc)) / 10 - dx = abs(np.diff(self.absc[:2])[0]) + dx = abs(np.diff(self.absc[0:2])[0]) windPts = np.int(windowWidth / dx) if windPts % 2 == 0: # Make sure windPts is odd so that basis doesn't shift windPts += 1 if windPts >= np.size(self.ordi): - raise Exception( - ( - ( - f'windowWidth is {str(windPts)}' - + ' wide, which is bigger than the data itself (' - ) - + str(np.size(self.ordi)) - + ')' - ) - ) - + raise Exception('windowWidth is ' + str(windPts) + + ' wide, which is bigger than the data itself (' + str(np.size(self.ordi)) + ')') filt = np.ones(windPts) / windPts invalidIndeces = int((windPts - 1) / 2) @@ -644,21 +631,31 @@ def invert(self, yVals, directionToDescend=None): maxInd = np.argmax(self.ordi) minInd = np.argmin(self.ordi) if directionToDescend is None: - directionToDescend = 'left' if minInd < maxInd else 'right' - yValArr = np.array([yVals]) if np.isscalar(yVals) else yVals + if minInd < maxInd: + directionToDescend = 'left' + else: + directionToDescend = 'right' + if np.isscalar(yVals): + yValArr = np.array([yVals]) + else: + yValArr = yVals xValArr = np.zeros(yValArr.shape) for iVal, y in enumerate(yValArr): xValArr[iVal] = interpInverse(*self.getData(), startIndex=maxInd, direction=directionToDescend, threshVal=y) - return xValArr[0] if np.isscalar(yVals) else xValArr + if np.isscalar(yVals): + return xValArr[0] + else: + return xValArr def centerOfMass(self): ''' Returns abscissa point where mass is centered ''' deb = self.debias().clip(0, None) weighted = np.multiply(*deb.getData()) - return np.sum(weighted) / np.sum(deb.ordi) + com = np.sum(weighted) / np.sum(deb.ordi) + return com def moment(self, order=2, relativeGauss=False): ''' The order'th moment of the function @@ -781,7 +778,8 @@ def _minAbsc(fa, fb): dxb = np.mean(np.abs(np.diff(np.sort(fb.absc)))) new_dx = min(dxa, dxb) - return np.arange(min_absc, max_absc + new_dx, new_dx) + newAbsc = np.arange(min_absc, max_absc + new_dx, new_dx) + return newAbsc @staticmethod def _maxAbsc(fa, fb): @@ -798,7 +796,8 @@ def _maxAbsc(fa, fb): dxb = np.mean(np.abs(np.diff(np.sort(fb.absc)))) new_dx = min(dxa, dxb) - return np.arange(min_absc, max_absc + new_dx, new_dx) + newAbsc = np.arange(min_absc, max_absc + new_dx, new_dx) + return newAbsc def __sub__(self, other): ''' Returns the subtraction of the two functions, in the domain of the shortest abscissa object. @@ -841,7 +840,7 @@ def __pow__(self, power): try: new_ordi = ordi ** power # uses numpy's power overload except ValueError as err: - raise ValueError(f"Invalid power {power} (not a number)") from err + raise ValueError("Invalid power {} (not a number)".format(power)) from err return self.__newOfSameSubclass(absc, new_ordi) @@ -899,13 +898,10 @@ def lin(self): Returns: Spectrum: new object ''' - return ( - type(self)( - self.absc.copy(), 10 ** (self.ordi.copy() / 10), inDbm=False - ) - if self.inDbm - else type(self)(self.absc.copy(), self.ordi.copy(), inDbm=False) - ) + if not self.inDbm: + return type(self)(self.absc.copy(), self.ordi.copy(), inDbm=False) + else: + return type(self)(self.absc.copy(), 10 ** (self.ordi.copy() / 10), inDbm=False) def db(self): ''' The spectrum in decibel units @@ -915,8 +911,9 @@ def db(self): ''' if self.inDbm: return type(self)(self.absc.copy(), self.ordi.copy(), inDbm=True) - clippedOrdi = np.clip(self.ordi, 1e-12, None) - return type(self)(self.absc.copy(), 10 * np.log10(clippedOrdi), inDbm=True) + else: + clippedOrdi = np.clip(self.ordi, 1e-12, None) + return type(self)(self.absc.copy(), 10 * np.log10(clippedOrdi), inDbm=True) def __binMathHelper(self, other): ''' Adds a check to make sure lin/db is in the same state ''' @@ -929,7 +926,7 @@ def simplePlot(self, *args, livePlot=False, **kwargs): ''' super().simplePlot(*args, livePlot=livePlot, **kwargs) plt.xlabel('Wavelength (nm)') - plt.ylabel(f"Transmission ({'dB' if self.inDbm else 'lin'})") + plt.ylabel('Transmission ({})'.format('dB' if self.inDbm else 'lin')) # Peak and trough related @@ -962,12 +959,18 @@ def refineResonanceWavelengths(self, filtShapes, seedRes=None, isPeak=None): useFilts = filtShapes.copy() if type(self) == Spectrum: # For Spectrum objects only - spectFun = self.lin() if isPeak else 1 - self.lin() + if isPeak: + spectFun = self.lin() + else: + spectFun = 1 - self.lin() for i in range(len(filtShapes)): if type(filtShapes[i]).__name__ != 'Spectrum': raise Exception( 'If calling object is Spectrum, the filter shapes must also be Spectrum types') - useFilts[i] = filtShapes[i].lin() if isPeak else 1 - filtShapes[i].lin() + if isPeak: + useFilts[i] = filtShapes[i].lin() + else: + useFilts[i] = 1 - filtShapes[i].lin() else: spectFun = self @@ -1025,7 +1028,7 @@ def simplePlot(self, *args, livePlot=False, **kwargs): ''' super().simplePlot(*args, livePlot=livePlot, **kwargs) plt.xlabel('Frequency (GHz)') - plt.ylabel(f"Transmission ({'dB' if self.inDbm else 'lin'})") + plt.ylabel('Transmission ({})'.format('dB' if self.inDbm else 'lin')) def nm(self): ''' Convert to Spectrum''' diff --git a/lightlab/util/data/two_dim.py b/lightlab/util/data/two_dim.py index fd9ba084..86d4f8bc 100644 --- a/lightlab/util/data/two_dim.py +++ b/lightlab/util/data/two_dim.py @@ -224,17 +224,8 @@ def _putInTimebase(self, testFun): TypeError: if the type of ``testFun`` is different than the others in this FunctionalBasis ''' if type(testFun).__name__ is not self.memberType.__name__: - raise TypeError( - ( - ( - f'This FunctionalBasis expects {str(self.memberType)}' - + ', but was given ' - ) - + str(type(testFun)) - + '.' - ) - ) - + raise TypeError('This FunctionalBasis expects ' + str(self.memberType) + + ', but was given ' + str(type(testFun)) + '.') # Check time base if np.any(testFun.absc != self.absc): # logger.warning('Warning: Basis signal time abscissa are different. Interpolating...') @@ -256,7 +247,8 @@ def multiAxisPlot(self, *args, axList=None, titleRoot=None, **kwargs): if axList is None: _, axList = plt.subplots(nrows=len(self), figsize=(14, 14)) if len(axList) != len(self): - raise ValueError(f'Wrong number of axes. Got {len(axList)}, need {len(self)}.') + raise ValueError('Wrong number of axes. Got {}, need {}.'.format( + len(axList), len(self))) for i, ax in enumerate(axList): plt.sca(ax) self[i].simplePlot(*args, **kwargs) @@ -316,7 +308,10 @@ def moment(self, order=2, allDims=True, relativeGauss=False): byDim = np.zeros(len(self)) for iDim in range(len(self)): byDim[iDim] = self[iDim].moment(order, relativeGauss=relativeGauss) - return np.mean(byDim) if allDims else byDim + if allDims: + return np.mean(byDim) + else: + return byDim def componentAnalysis(self, *args, pcaIca=True, lNorm=2, expectedComponents=None, **kwargs): ''' Gives the waveform representing the principal component of the order @@ -360,7 +355,10 @@ def correctSigns(self, otherBundle, maintainOrder=True): for iDim, oSig in enumerate(otherBundle): if maintainOrder: sSig = self[iDim] - newWfm = sSig if (sSig * oSig).getMean() > 0 else -1 * sSig + if (sSig * oSig).getMean() > 0: + newWfm = sSig + else: + newWfm = -1 * sSig else: permCorrs = np.zeros(len(self)) for jDim, sSig in enumerate(self): @@ -397,7 +395,8 @@ def innerProds(self, trial): ''' tvec = self._putInTimebase(trial) tmat = np.tile(tvec, (self.nDims, 1)) - return np.sum(np.multiply(tmat, self.ordiMat), axis=1).A1 + products = np.sum(np.multiply(tmat, self.ordiMat), axis=1).A1 + return products def magnitudes(self): ''' The inner product of the basis with itself @@ -504,7 +503,10 @@ def item(self, index, dim=None): if np.isscalar(index): assert(dim is not None) newAbsc = self.absc[dim] - newOrdi = self.ordi[index, :] if dim == 0 else self.ordi[:, index] + if dim == 0: + newOrdi = self.ordi[index, :] + else: + newOrdi = self.ordi[:, index] return MeasuredFunction(newAbsc, newOrdi) else: assert(len(index) == 2) @@ -561,7 +563,8 @@ def errorAt(self, testVec=None): def invert(self, desiredVec): reflectedVec = desiredVec - self.errorAt(desiredVec) avgErr = (self.errorAt(desiredVec) + self.errorAt(reflectedVec)) / 2 - return desiredVec - avgErr + commandVec = desiredVec - avgErr + return commandVec def zeroCenteredSquareSize(self): ''' Very stupid, just look at corner points @@ -577,7 +580,8 @@ def cornerInd(grid, minOrMax): def zcSz(grid, corner): cornerVec = grid[(corner + (slice(None), ))] - return np.min(np.abs(cornerVec)) + sqSide = np.min(np.abs(cornerVec)) + return sqSide nomiCornerInds = [cornerInd(self.nomiGrid, m) for m in [True, False]] nomiSq = np.min([zcSz(self.nomiGrid, nomiCornerInds[i]) for i in range(2)]) diff --git a/lightlab/util/io/jsonpickleable.py b/lightlab/util/io/jsonpickleable.py index 146f9440..4727692d 100644 --- a/lightlab/util/io/jsonpickleable.py +++ b/lightlab/util/io/jsonpickleable.py @@ -77,17 +77,19 @@ def __getstate__(self): if key in allNotPickled: keys_to_delete.add(key) + # 2. hardware placeholders elif (val.__class__.__name__ == 'VISAObject' or any(base.__name__ == 'VISAObject' for base in val.__class__.mro())): klassname = val.__class__.__name__ logger.warning('Not pickling %s = %s.', key, klassname) - state[key] = HardwareReference(f'Reference to a {klassname}') + state[key] = HardwareReference('Reference to a ' + klassname) + # 3. functions that are not available in modules - saves the code text elif jsonpickle.util.is_function(val) and not jsonpickle.util.is_module_function(val): - state[f'{key}_dilled'] = dill.dumps(val) + state[key + '_dilled'] = dill.dumps(val) keys_to_delete.add(key) - # 4. double underscore attributes have already been removed + # 4. double underscore attributes have already been removed for key in keys_to_delete: del state[key] return state @@ -121,17 +123,10 @@ def _fromJSONcheck(cls, json_string): err.args = (newm,) + err.args[1:] raise - if ( - not isinstance(restored_object, cls) - and type(restored_object).__name__ != cls.__name__ - ): - raise TypeError( - ( - 'Loaded class is different than intended.\n' - + f'Got {type(restored_object).__name__}, needed {cls.__name__}.' - ) - ) - + if not isinstance(restored_object, cls): # This is likely to happen if lightlab has been reloaded + if type(restored_object).__name__ != cls.__name__: # This is not ok + raise TypeError('Loaded class is different than intended.\n' + + 'Got {}, needed {}.'.format(type(restored_object).__name__, cls.__name__)) for a in cls.notPickled: setattr(restored_object, a, None) diff --git a/lightlab/util/io/progress.py b/lightlab/util/io/progress.py index 5ddeeb15..9bd59997 100644 --- a/lightlab/util/io/progress.py +++ b/lightlab/util/io/progress.py @@ -21,8 +21,10 @@ def printWait(*args): Args: \*args (Tuple(str)): Strings that will be written ''' - msg = ''.join(str(a) for a in args) - print(f"{msg}... ", end='') + msg = '' + for a in args: + msg += str(a) + print(msg + "... ", end='') sys.stdout.flush() @@ -34,7 +36,9 @@ def printProgress(*args): Args: *args (str, Tuple(str)): Arguments that will be written ''' - msg = ''.join(str(a) for a in args) + msg = '' + for a in args: + msg += str(a) sys.stdout.write('\b' * 1000) sys.stdout.flush() sys.stdout.write(msg) @@ -91,7 +95,7 @@ def __init__(self, name, swpSize, runServer=True, stdoutPrint=False, **kwargs): def print_string(): prntStr = self.name + "\n" for iterDim, _ in enumerate(self.size): - prntStr += f'Dim-{str(iterDim)}...' + prntStr += 'Dim-' + str(iterDim) + '...' return prntStr if True: @@ -126,12 +130,13 @@ def getUrl(): port = int(fx.readline()) except FileNotFoundError: port = 'null' - return prefix + host + ':' + port + return prefix + host + ':' + str(port) def __tag(self, bodytext, autorefresh=False): ''' Do the HTML tags ''' if not hasattr(self, '__tagHead') or self.__tagHead is None: - t = '\n' + '\n' + t = '\n' + t += '\n' t += '\n' t += '' t += 'Sweep Progress Monitor' @@ -139,11 +144,12 @@ def __tag(self, bodytext, autorefresh=False): if autorefresh: t += '<meta http-equiv="refresh" content="5" />\n' # Autorefresh every 5 seconds t += '<body>\n' - t += f'<h1>{self.name}' + '</h1>\n' + t += '<h1>' + self.name + '</h1>\n' t += r'<hr \>\\n' self.__tagHead = t if not hasattr(self, '__tagFoot') or self.__tagFoot is None: - t = '</body>\n' + '</html>\n' + t = '</body>\n' + t += '</html>\n' self.__tagFoot = t return self.__tagHead + bodytext + self.__tagFoot @@ -155,7 +161,7 @@ def __writeHtmlEnd(self): self.__tagHead = None self.__tagFoot = None body = '<h2>Sweep completed!</h2>\n' - body += ptag(f'At {ProgressWriter.tims(time.time())}') + body += ptag('At ' + ProgressWriter.tims(time.time())) htmlText = self.__tag(body, autorefresh=False) with self.filePath.open('w') as fx: # pylint: disable=no-member fx.write(htmlText) @@ -175,7 +181,7 @@ def __writeHtml(self): body = '' for i, p in enumerate(self.currentPnt): dimStr = i * 'sub-' + 'dimension[' + str(i) + '] : ' - dimStr += f'{str(p + 1)} of {str(self.size[i])}' + dimStr += str(p + 1) + ' of ' + str(self.size[i]) body += ptag(dimStr) body += r'<hr \>\\n' @@ -189,7 +195,7 @@ def __writeHtml(self): ProgressWriter.tims(self.startTime)) body += ptag('(Latest Update) ' + ProgressWriter.tims(currentTime)) - body += ptag(f'(Expected Completion) {ProgressWriter.tims(endTime)}') + body += ptag('(Expected Completion) ' + ProgressWriter.tims(endTime)) # Say where the files are hosted body += ptag('This monitor service is hosted in the directory:') @@ -212,14 +218,17 @@ def update(self, steps=1): self.__writeHtml() if self.printing: self.__writeStdio() - self.tempfile.write(self._get_std_string() + "\n") + if True: + self.tempfile.write(self._get_std_string() + "\n") + self.tempfile.flush() else: if self.serving: self.__writeHtmlEnd() if self.printing: self.__writeStdioEnd() - self.tempfile.write("End." + "\n") - self.tempfile.flush() + if True: + self.tempfile.write("End." + "\n") + self.tempfile.flush() def __updateOneInternal(self): for i in range(len(self.size)): @@ -238,4 +247,4 @@ def tims(cls, epochTime): def ptag(s): - return f'<p>{s}' + '</p>\n' + return '<p>' + s + '</p>\n' diff --git a/lightlab/util/io/saveload.py b/lightlab/util/io/saveload.py index e0e091c7..a73b9215 100644 --- a/lightlab/util/io/saveload.py +++ b/lightlab/util/io/saveload.py @@ -19,7 +19,7 @@ def _makeFileExist(filename): rp = _getFileDir(filename) os.makedirs(_getFileDir(), mode=0o775, exist_ok=True) rp.touch() - print(f"Saving to file: {rp}") + print("Saving to file: {}".format(rp)) return rp @@ -54,7 +54,7 @@ def pprintFileDir(*, generate=False): childNames = list(map(lambda x: x.name, childrenFiles)) sortedChildren = [x for _, x in sorted(zip(childNames, childrenFiles))] for child in sortedChildren: - justified = f'{child.name.rjust(maxStrLen)} ' + justified = child.name.rjust(maxStrLen) + ' ' if child.name.endswith('.pkl'): print(justified, f'io.loadPickle(\'{child.stem}\')') elif child.name.endswith('.gz'): @@ -71,7 +71,10 @@ def _endingWith(filerootname, suffix): froot = str(filerootname) if suffix[0] != '.': suffix = f'.{suffix}' - return froot if froot.endswith(suffix) else froot + suffix + if froot.endswith(suffix): + return froot + else: + return froot + suffix def savePickle(filename, dataTuple): diff --git a/lightlab/util/measprocessing.py b/lightlab/util/measprocessing.py index 212b4df6..2b901fa1 100644 --- a/lightlab/util/measprocessing.py +++ b/lightlab/util/measprocessing.py @@ -46,7 +46,8 @@ def fgSpect(self, avgCnt=1, raw=None, bgType=None): bg = self.getBgSpect(bgType) if bgType is None and type(bg) is float and bg == 0: self.setBgSmoothed(raw) - return raw - bg + unbiased = raw - bg + return unbiased def resonances(self, spect=None, avgCnt=1): ''' Returns the current wavelengths of detected peaks in order sorted by wavelength. @@ -158,7 +159,5 @@ def getBgSpect(self, bgType=None): except KeyError: raise KeyError('Background of type \'' + bgType + '\' has not been taken yet.') else: - raise ValueError( - (f'Invalid background token: {bgType}' + '. Need ') - + ', '.join(preferredOrder) - ) + raise ValueError('Invalid background token: ' + bgType + + '. Need ' + str(', '.join(preferredOrder))) diff --git a/lightlab/util/plot.py b/lightlab/util/plot.py index 5273f676..3a8dd83c 100644 --- a/lightlab/util/plot.py +++ b/lightlab/util/plot.py @@ -8,7 +8,7 @@ class DynamicLine(object): ''' A line that can refresh when called ''' - def __init__(self, formatStr='b-', existing=None, geometry=[(0, 0), (4, 4)]): # pylint: disable=dangerous-default-value + def __init__(self, formatStr='b-', existing=None, geometry=[(0, 0), (4, 4)]): # pylint: disable=dangerous-default-value ''' Args: formatStr (str): plotting line format @@ -28,9 +28,7 @@ def __init__(self, formatStr='b-', existing=None, geometry=[(0, 0), (4, 4)]): # Geometry is ignored here self.lines, = self.ax.plot([], [], formatStr) plt.get_current_fig_manager().window.wm_geometry( - f'+{str(geometry[0][0])}+{str(geometry[0][1])}' - ) - + '+' + str(geometry[0][0]) + '+' + str(geometry[0][1])) # Autoscale on unknown axis and known lims on the other self.ax.set_autoscaley_on(True) # Other stuff diff --git a/lightlab/util/search.py b/lightlab/util/search.py index 2b1c4f59..2bde69b9 100644 --- a/lightlab/util/search.py +++ b/lightlab/util/search.py @@ -99,11 +99,12 @@ def shrinkAround(arr, bestInd, shrinkage=.6): def doesMFbracket(targetY, twoPointMF): yRange = twoPointMF.getRange() if targetY < yRange[0]: - return 'low' + outOfRangeDirection = 'low' elif targetY > yRange[1]: - return 'high' + outOfRangeDirection = 'high' else: - return 'in-range' + outOfRangeDirection = 'in-range' + return outOfRangeDirection def bracketSearch(evalPointFun, targetY, startBounds, xTol, hardConstrain=False, livePlot=False): diff --git a/lightlab/util/sweep.py b/lightlab/util/sweep.py index 202ba9f3..686cc67d 100644 --- a/lightlab/util/sweep.py +++ b/lightlab/util/sweep.py @@ -36,8 +36,8 @@ class Sweeper(object): def __init__(self): self.data = None self.savefile = None - self.plotOptions = {} - self.monitorOptions = {} + self.plotOptions = dict() + self.monitorOptions = dict() def gather(self): print('gather method must be overloaded in subclass') @@ -184,7 +184,7 @@ def repeater(cls, nTrials): new.addActuation('trial', lambda a: None, np.arange(nTrials)) return new - def gather(self, soakTime=None, autoSave=False, returnToStart=False): # pylint: disable=arguments-differ + def gather(self, soakTime=None, autoSave=False, returnToStart=False): # pylint: disable=arguments-differ ''' Perform the sweep Args: @@ -235,7 +235,7 @@ def gather(self, soakTime=None, autoSave=False, returnToStart=False): # pylin if iDim == self.actuDims - 1 or index[iDim + 1] == 0 or actuObj.doOnEveryPoint: y = actuObj.function(x) # The actual function call occurs here if y is not None: - pointData[f'{actuKey}-return'] = y + pointData[actuKey + '-return'] = y # Do the measurement, store return values for measKey, measFun in self.measure.items(): @@ -274,7 +274,7 @@ def gather(self, soakTime=None, autoSave=False, returnToStart=False): # pylin display.clear_output(wait=True) # Progress report prog.update() - # End of the main loop + # End of the main loop except Exception as err: logger.error('Error while sweeping. Keeping data. %s', err) @@ -403,18 +403,9 @@ def addStaticData(self, name, contents): if np.isscalar(contents): contents *= np.ones(self.swpShape) if np.any(contents.shape != self.swpShape): - raise ValueError( - ( - ( - (f'Static data {name} is wrong shape for sweep.' + 'Need ') - + str(self.swpShape) - + '. Got ' - ) - + str(contents.shape) - + 'The order that actuations and static data are added matter.' - ) - ) - + raise ValueError('Static data ' + name + ' is wrong shape for sweep.' + + 'Need ' + str(self.swpShape) + '. Got ' + str(contents.shape) + + 'The order that actuations and static data are added matter.') self.static[name] = contents def subsume(self, other, useMinorOptions=False): diff --git a/notebooks/Examples/ParameterExtraction/myProcedures.py b/notebooks/Examples/ParameterExtraction/myProcedures.py index cc286695..957471f4 100644 --- a/notebooks/Examples/ParameterExtraction/myProcedures.py +++ b/notebooks/Examples/ParameterExtraction/myProcedures.py @@ -32,7 +32,8 @@ def dither(centerVolt): for iDv, dv in enumerate(dvArr): source_meter_instr.setVoltage(centerVolt + dv) diArr[iDv] = source_meter_instr.measCurrent() - return diArr[0] - 2 * diArr[1] + diArr[2] + d2idv2 = diArr[0] - 2 * diArr[1] + diArr[2] + return d2idv2 foundThresh, _ = peakSearch(dither, [-1, 3], livePlot=True, nSwarm=5) return foundThresh diff --git a/pylintrc b/pylintrc index 1b5e5ae6..bfd35ff8 100644 --- a/pylintrc +++ b/pylintrc @@ -769,4 +769,4 @@ min-public-methods=2 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception +overgeneral-exceptions=builtins.Exception diff --git a/pylintrc-errors b/pylintrc-errors index fdc588e3..e26127f8 100644 --- a/pylintrc-errors +++ b/pylintrc-errors @@ -769,4 +769,4 @@ min-public-methods=2 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception +overgeneral-exceptions=builtins.Exception diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..a18cb1af --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "lightlab" +authors = [ + {name = "Alex Tait", email = "atait@ieee.org"}, + {name = "Thomas Ferreira de Lima", email = "github@tlima.me"}, +] +maintainers = [ + {name = "Thomas Ferreira de Lima", email = "github@tlima.me"} +] +description = "Lightwave Lab instrument automation tools" +readme = "README.rst" +requires-python = ">=3.7" +keywords = ["gpib", "visa", "instrument control"] +license = {file = "LICENSE"} +classifiers = [ + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering", + "Topic :: System :: Hardware :: Hardware Drivers", + "Framework :: Jupyter", +] +dependencies = [ + 'dpath', + 'jsonpickle>=1.4.1', + 'matplotlib', + 'IPython', + 'PyVISA', + 'scipy', + 'scikit-learn', + 'dill', +] +dynamic = ["version"] + +[project.urls] +repository = "https://github.com/lightwave-lab/lightlab" + +[project.scripts] +lightlab = "lightlab.command_line:main" + +[tool.setuptools.dynamic] +version = {attr = "version.version"} + +[tool.setuptools] +packages = ["lightlab"] \ No newline at end of file diff --git a/readthedocs.yml b/readthedocs.yml index c8b8b04c..465b81ba 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -2,5 +2,5 @@ build: image: latest python: - version: 3.6 + version: 3.10 pip_install: true diff --git a/setup.cfg b/setup.cfg index d4ebd5bc..d9e895c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,2 @@ [tool:pytest] -flake8-ignore = E731 E402 E501 W504 - -[metadata] -description-file = README.rst -license-file = LICENSE +flake8-ignore = E731 E402 E501 W504 \ No newline at end of file diff --git a/setup.py b/setup.py index ec0e0960..3be5be94 100644 --- a/setup.py +++ b/setup.py @@ -1,71 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import os -import sys -from setuptools import setup, find_packages - - -LABSTATE_FILENAME = "labstate.json" -JUPYTER_GROUP = "jupyter" - -# assert sys.version_info >= (3, 6), "Use python >= 3.6 - We are living in the __future__!" - - -def touch(fname, times=None): - with open(fname, 'a'): - os.utime(fname, times) - +from setuptools import setup def main(): - with open('README.rst') as f: - readme = f.read() - - with open('LICENSE') as f: - license_text = f.read() - - with open("version.py") as f: - code = compile(f.read(), "version.py", 'exec') - version_dict = {} - exec(code, {}, version_dict) # pylint: disable=exec-used - release = version_dict['release'] - - metadata = dict( - name='lightlab', - version=release, - description='Lightwave Lab instrument automation tools', - long_description=readme, - license=license_text.split('\n')[0], - python_requires='>=3.6', - packages=find_packages(exclude=('tests', 'docs', 'data')), - url="https://github.com/lightwave-lab/lightlab", - author="Alex Tait <atait@ieee.org>, Thomas Ferreira de Lima <tlima@princeton.edu>", - author_email="tlima@princeton.edu", - classifiers=( - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Topic :: Scientific/Engineering", - "Topic :: System :: Hardware :: Hardware Drivers", - "Framework :: Jupyter", - ), - install_requires=[ - 'dpath', - 'jsonpickle>=1.4.1', - 'matplotlib', - 'IPython', - 'PyVISA', - 'scipy', - 'sklearn', - 'dill', - ], - entry_points={ - 'console_scripts': ['lightlab=lightlab.command_line:main'], - } - ) - - setup(**metadata) + # All metadata was moved to pyproject.toml + setup() if __name__ == '__main__': diff --git a/test-requirements.txt b/test-requirements.txt index f31d286c..311c5f6c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,63 +1,6 @@ -appnope==0.1.0 -astroid==2.2.5 -atomicwrites==1.3.0 -attrs==19.1.0 -backcall==0.1.0 -coverage==4.5.3 -cycler==0.10.0 -decorator==4.4.0 -dill==0.2.9 -dpath==2.0.1 -flake8==3.6.0 -importlib-metadata==0.17 -ipykernel==5.1.1 -ipython==7.16.3 -ipython-genutils==0.2.0 -isort==4.3.20 -jedi==0.13.3 -joblib==0.13.2 -jsonpickle==1.2 -jsonschema==3.0.1 -jupyter-client==5.2.4 -jupyter-core==4.4.0 -kiwisolver==1.1.0 -lazy-object-proxy==1.4.1 -matplotlib==3.1.0 -mccabe==0.6.1 -mock==2.0.0 -more-itertools==7.0.0 -nbformat==4.4.0 -nbval==0.9.1 -numpy==1.22.0 -packaging==19.0 -parso==0.4.0 -pbr==5.2.1 -pexpect==4.7.0 -pickleshare==0.7.5 -pluggy==0.12.0 -prompt-toolkit==2.0.9 -ptyprocess==0.6.0 -py==1.10.0 -pycodestyle==2.4.0 -pyflakes==2.0.0 -Pygments==2.7.4 -pylint==2.3.1 -pyparsing==2.4.0 -pyrsistent==0.15.2 -pytest==4.6.2 -pytest-cov==2.5.1 -pytest-flake8==1.0.2 -pytest-pylint==0.12.1 -python-dateutil==2.8.0 -PyVISA==1.9.1 -pyzmq==18.0.1 -scikit-learn==0.21.2 -scipy==1.3.0 -six==1.12.0 -sklearn==0.0 -tornado==6.0.2 -traitlets==4.3.2 -typed-ast==1.4.0 -wcwidth==0.1.7 -wrapt==1.11.1 -zipp==0.5.1 +pytest +pytest-flake8 +pytest-cov +pytest-pylint +mock +freezegun \ No newline at end of file diff --git a/tests/test_JSONpickleable.py b/tests/test_JSONpickleable.py index 42291698..37e22816 100644 --- a/tests/test_JSONpickleable.py +++ b/tests/test_JSONpickleable.py @@ -137,7 +137,9 @@ def expJSONfile(): def test_JSONpickleableWithFile(expJSONfile): global INSTANTIATION_COUNTER + INSTANTIATION_COUNTER = 0 loadedExp = SomeVirtualizedExperiment.load(expJSONfile) + assert INSTANTIATION_COUNTER == 0 # __init__ is not called again validate(loadedExp) diff --git a/tests/test_config.py b/tests/test_config.py index 2d13dae1..7cba1966 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,7 @@ from lightlab import config from configparser import ConfigParser -filename = f'testconfig_{int(time.time())}.conf' +filename = 'testconfig_{}.conf'.format(int(time.time())) user_config_path = Path(filename).resolve() default_config = config.default_config diff --git a/tests/test_imports.py b/tests/test_imports.py index b96789df..899e2e8d 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -1,19 +1,15 @@ '''Import all instrument module by name''' - import pkgutil import lightlab import pytest import importlib package = lightlab -modules = [ - modname - for _, modname, _ in pkgutil.walk_packages( - path=package.__path__, - prefix=f'{package.__name__}.', - onerror=lambda x: None, - ) -] +modules = list() +for _, modname, _ in pkgutil.walk_packages(path=package.__path__, + prefix=package.__name__ + '.', + onerror=lambda x: None): + modules.append(modname) @pytest.mark.parametrize("modname", modules) diff --git a/tests/test_labstate.py b/tests/test_labstate.py index 7351a345..63f2d496 100644 --- a/tests/test_labstate.py +++ b/tests/test_labstate.py @@ -1,6 +1,5 @@ '''Tests whether the functionality of the laboratory module is working properly.''' - import pytest import lightlab.laboratory.state as labstate from lightlab.laboratory.instruments import LocalHost, Host, Bench, Instrument, Keithley @@ -12,9 +11,10 @@ import time import os import logging +from freezegun import freeze_time logging.disable(logging.CRITICAL) -filename = f'test_{int(time.time())}.json' +filename = 'test_{}.json'.format(int(time.time())) labstate._filename = filename # Shared objects @@ -179,7 +179,9 @@ def test_bench_iteration(lab): benches_items.append(bench) benches_names.append(bench_name) - benches_values = list(lab.benches.values()) + benches_values = [] + for bench in lab.benches.values(): + benches_values.append(bench) assert benches_items == benches_values assert benches_names == [bench.name for bench in benches_values] @@ -207,7 +209,7 @@ def test_savestate(lab): lab.updateHost(h1) lab.saveState(filename, save_backup=False) - +@freeze_time("2023-02-21") def test_reloadlabstate(lab): ''' Saves and reloads LabState and asserts equality ''' lab2 = labstate.LabState.loadState(filename=filename) diff --git a/tests/test_virtualization.py b/tests/test_virtualization.py index 66abb9f2..5c958226 100644 --- a/tests/test_virtualization.py +++ b/tests/test_virtualization.py @@ -59,9 +59,11 @@ def checkHits(N=1): and that the hammer was put down afterwards ''' global NAIL, IN_HAND - yield # user code runs NAIL = 0 + IN_HAND = False + yield # user code runs assert NAIL == N + assert not IN_HAND ''' INSTRUMENT INTERFACES @@ -287,4 +289,3 @@ def test_dualWeilding(): with pytest.raises(VirtualizationError): complicatedProcedure_resultingIn3hits(dual1, dual2) - diff --git a/tests/test_visa_drivers.py b/tests/test_visa_drivers.py index ad531aff..9ae6fde2 100644 --- a/tests/test_visa_drivers.py +++ b/tests/test_visa_drivers.py @@ -1,17 +1,16 @@ ''' Tests whether all the visa drivers included in lightlab are properly coded. All tests should be safe to run locally.''' - import pytest from mock import patch from lightlab.equipment import lab_instruments -from lightlab.equipment.lab_instruments import VISAInstrumentDriver +from lightlab.equipment.lab_instruments import VISAInstrumentDriver, experimental_instruments import inspect classes = [ obj for name, obj in inspect.getmembers(lab_instruments) - if inspect.isclass(obj) and issubclass(obj, VISAInstrumentDriver) + if inspect.isclass(obj) and issubclass(obj, VISAInstrumentDriver) and name not in experimental_instruments ] class OpenError(RuntimeError): diff --git a/version.py b/version.py index e232bb6f..dfd2db4c 100644 --- a/version.py +++ b/version.py @@ -1,9 +1,9 @@ # version.py # The short X.Y version. -version = '1.1.0' +version = '1.1.1' # The full version, including alpha/beta/rc tags. -release = f"{version}" +release = version + "" if __name__ == '__main__': - print(f'lightlab v{version}') + print('lightlab v' + version)