diff --git a/debian/changelog b/debian/changelog index ee3b4f4..8166be0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,76 @@ +frappy-core (0.17.11) focal; urgency=medium + + [ Alexander Zaft ] + * Add __format__ to EnumMember + + [ Markus Zolliker ] + * fix error from manual %-format conversion + + [ Alexander Zaft ] + * specify minimum pyqt5 version + + [ Markus Zolliker ] + * make io device visible as expert by default + + [ Jens Krüger ] + * MLZ/Entangle: Fix formatting issues + + [ Markus Zolliker ] + * improve error handling on callbacks + * fix issues with lakeshore 370 + * frappy_psi: two small fixes in k2601b/ppmssim + * a playground for debugging drivers + * issues with StructOf + * improve mercury temperature loop + * improve interactive client + + [ Jens Krüger ] + * Server: add missing 'restart_hook' missing + * MLZ/Entangle: Add unit init in AnalogOutput + * MLZ/Entangle: Fix user limits handling + + [ Alexander Zaft ] + * Improve address and connection handling + * Add Stopgap handling of cfg files in cfg-editor + + [ Markus Zolliker ] + * fix in frappy_psi.historywriter + * improve mercury driver + * driver for the triton dilution refrigerator + * fixes on HasConvergence and HasOutputModule + * improve and fix errors with parameter limits + * add HasOffset feature + + [ Alexander Zaft ] + * Fix interface class list + + [ Markus Zolliker ] + * mixins should not inherit Module + * fix interplay mercury.TemperatureLoop and HasConvergence + + [ Alexander Zaft ] + * Fix lower limit checking of FloatRange + + [ Markus Zolliker ] + * improve traceback while processing config file + * driver for mercury IPS + * add sea driver + + [ Georg Brandl ] + * debian: add new executable + + [ Markus Zolliker ] + * move more code from bin/frappy-cli to frappy/client/interactive.py + * add unit=s to pollinterval + * client.interactive: fix error when interface_classes empty + * phytron motor driver + * client: timestamps must never lie in the future + + [ Alexander Zaft ] + * Fix rstrip misuse in frappy-generator + + -- Alexander Zaft Thu, 25 May 2023 09:38:24 +0200 + frappy-core (0.17.10) focal; urgency=medium * Change leftover %-logging calls to lazy diff --git a/debian/frappy-core.install b/debian/frappy-core.install index bf4e5a0..afa4e03 100644 --- a/debian/frappy-core.install +++ b/debian/frappy-core.install @@ -1,5 +1,6 @@ usr/bin/frappy-cli usr/bin/frappy-server +usr/bin/frappy-play usr/lib/python3.*/dist-packages/frappy/*.py usr/lib/python3.*/dist-packages/frappy/lib usr/lib/python3.*/dist-packages/frappy/client diff --git a/etc/frappy-generator b/etc/frappy-generator index 9cf67b6..d50c226 100755 --- a/etc/frappy-generator +++ b/etc/frappy-generator @@ -39,7 +39,7 @@ def main(): frappy_unit = '/lib/systemd/system/frappy@.service' wants_dir = normal_dir + '/frappy.target.wants' - all_servers = [base.rstrip('_cfg') for (base, ext) in + all_servers = [base[:-4] if base.endswith('_cfg') else base for (base, ext) in map(path.splitext, os.listdir(config_dir)) if ext == '.py'] all_servers.sort() diff --git a/frappy/config.py b/frappy/config.py index c19cc6a..1b77f15 100644 --- a/frappy/config.py +++ b/frappy/config.py @@ -22,10 +22,12 @@ import os import re +from collections import Counter from frappy.errors import ConfigError from frappy.lib import generalConfig + class Undef: pass @@ -126,7 +128,7 @@ class Config(dict): self.modules.append(mod) -def process_file(filename): +def process_file(filename, log): with open(filename, 'rb') as f: config_text = f.read() node = NodeCollector() @@ -135,6 +137,13 @@ def process_file(filename): # pylint: disable=exec-used exec(compile(config_text, filename, 'exec'), ns) + + # check for duplicates in the file itself. Between files comes later + duplicates = [name for name, count in Counter([mod['name'] + for mod in mods.list]).items() if count > 1] + if duplicates: + log.warning('Duplicate module name in file \'%s\': %s', + filename, ','.join(duplicates)) return Config(node, mods) @@ -177,7 +186,7 @@ def load_config(cfgfiles, log): for cfgfile in cfgfiles.split(','): filename = to_config_path(cfgfile, log) log.debug('Parsing config file %s...', filename) - cfg = process_file(filename) + cfg = process_file(filename, log) if config: config.merge_modules(cfg) else: diff --git a/frappy/lib/__init__.py b/frappy/lib/__init__.py index cf40de1..827ac03 100644 --- a/frappy/lib/__init__.py +++ b/frappy/lib/__init__.py @@ -405,8 +405,4 @@ def merge_status(*args): texts matching maximal code are joined with ', ' """ maxcode = max(a[0] for a in args) - # take status value matching highest status code - merged = [a[1] for a in args if a[0] == maxcode and a[1]] - # merge the split texts. use dict instead of set for keeping order - merged = {m: 0 for mm in merged for m in mm.split(', ')} - return maxcode, ', '.join(merged) + return maxcode, ', '.join([a[1] for a in args if a[0] == maxcode and a[1]]) diff --git a/frappy/params.py b/frappy/params.py index 526738c..428ed78 100644 --- a/frappy/params.py +++ b/frappy/params.py @@ -27,7 +27,7 @@ import inspect from frappy.datatypes import BoolType, CommandType, DataType, \ DataTypeType, EnumType, NoneOr, OrType, FloatRange, \ - StringType, StructOf, TextType, TupleOf, ValueType + StringType, StructOf, TextType, TupleOf, ValueType, ArrayOf from frappy.errors import BadValueError, WrongTypeError, ProgrammingError from frappy.properties import HasProperties, Property from frappy.lib import generalConfig @@ -168,6 +168,9 @@ class Parameter(Accessible): or the minimum time between updates of equal values [sec]''', OrType(FloatRange(0), EnumType(always=0, never=999999999, default=-1)), export=False, default=-1) + influences = Property( + 'optional hint about effected parameters', ArrayOf(StringType()), + extname='influences', export=True, mandatory=False, default=[]) # used on the instance copy only # value = None @@ -363,6 +366,9 @@ class Command(Accessible): result = Property( 'datatype of the result from the command, or None', NoneOr(DataTypeType()), export=False, mandatory=True) + influences = Property( + 'optional hint about effected parameters', ArrayOf(StringType()), + extname='influences', export=True, mandatory=False, default=[]) func = None diff --git a/test/test_datatypes.py b/test/test_datatypes.py index d3e5160..a4b2c1b 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -658,6 +658,7 @@ def test_get_datatype(): (FloatRange(-10, 10), FloatRange()), (IntRange(-10, 10), FloatRange()), (IntRange(-10, 10), IntRange(-20, 10)), + (FloatRange(-10, 10), FloatRange(-15, 10)), (StringType(), StringType(isUTF8=True)), (StringType(10, 10), StringType()), (ArrayOf(StringType(), 3, 5), ArrayOf(StringType(), 3, 6)), diff --git a/test/test_modules.py b/test/test_modules.py index a6dda8c..7da7853 100644 --- a/test/test_modules.py +++ b/test/test_modules.py @@ -28,7 +28,7 @@ import pytest from frappy.datatypes import BoolType, FloatRange, StringType, IntRange, ScaledInteger from frappy.errors import ProgrammingError, ConfigError, RangeError -from frappy.modules import Communicator, Drivable, Readable, Module +from frappy.modules import Communicator, Drivable, Readable, Module, Writable from frappy.params import Command, Parameter, Limit from frappy.rwhandler import ReadHandler, WriteHandler, nopoll from frappy.lib import generalConfig @@ -245,7 +245,7 @@ def test_ModuleMagic(): 'group', 'export', 'relative_resolution', 'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr', 'absolute_resolution', 'max', 'min', 'readonly', 'constant', - 'description', 'needscfg', 'update_unchanged'} + 'description', 'needscfg', 'update_unchanged', 'influences'} # check on the level of classes # this checks Newclass1 too, as it is inherited by Newclass2 @@ -902,3 +902,17 @@ def test_limit_inheritance(): with pytest.raises(ValueError): mod2.write_a(0) + +@pytest.mark.parametrize('bases, iface_classes', [ + ([Module], ()), + ([Communicator], ('Communicator',)), + ([Readable], ('Readable',)), + ([Writable], ('Writable',)), + ([Drivable], ('Drivable',)), +]) +def test_interface_classes(bases, iface_classes): + srv = ServerStub({}) + class Mod(*bases): + pass + m = Mod('mod', LoggerStub(), {'description': 'test'}, srv) + assert m.interface_classes == iface_classes