From 1bf47ce7a93d7258a2165a97bed9fd9d8ad0ab4c Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 12:17:29 +1000 Subject: [PATCH 01/21] Added test:// URI schema. --- instruments/abstract_instruments/instrument.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/instruments/abstract_instruments/instrument.py b/instruments/abstract_instruments/instrument.py index 2f19cc7bf..c97f4ca7f 100644 --- a/instruments/abstract_instruments/instrument.py +++ b/instruments/abstract_instruments/instrument.py @@ -286,7 +286,8 @@ def binblockread(self, data_width, fmt=None): # CLASS METHODS # URI_SCHEMES = ["serial", "tcpip", "gpib+usb", - "gpib+serial", "visa", "file", "usbtmc", "vxi11"] + "gpib+serial", "visa", "file", "usbtmc", "vxi11", + "test"] @classmethod def open_from_uri(cls, uri): @@ -306,6 +307,7 @@ def open_from_uri(cls, uri): gpib+serial:///dev/ttyACM0/15 # Currently non-functional. visa://USB::0x0699::0x0401::C0000001::0::INSTR usbtmc://USB::0x0699::0x0401::C0000001::0::INSTR + test:// For the ``serial`` URI scheme, baud rates may be explicitly specified using the query parameter ``baud=``, as in the example @@ -391,6 +393,8 @@ def open_from_uri(cls, uri): # vxi11://192.168.1.104 # vxi11://TCPIP::192.168.1.105::gpib,5::INSTR return cls.open_vxi11(parsed_uri.netloc, **kwargs) + elif parsed_uri.scheme == "test": + return cls.open_test(**kwargs) else: raise NotImplementedError("Invalid scheme or not yet " "implemented.") From 4eef89dc5a52d255bbe1b1f77f155cda041ee20f Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 12:17:53 +1000 Subject: [PATCH 02/21] Config: Py3 fix and support for setting attrs. --- instruments/config.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/instruments/config.py b/instruments/config.py index 74d09499b..f04f417cb 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -78,7 +78,7 @@ def load_instruments(conf_file_name, conf_path="/"): all other keys in the YAML file. :param str conf_file_name: Name of the configuration file to load - instruments from. + instruments from. Alternatively, a file-like object may be provided. :param str conf_path: ``"/"`` separated path to the section in the configuration file to load. @@ -98,20 +98,36 @@ def load_instruments(conf_file_name, conf_path="/"): raise ImportError("Could not import PyYAML, which is required " "for this function.") - with open(conf_file_name, 'r') as f: - conf_dict = yaml.load(f) + if isinstance(conf_file_name, (str, unicode)): + with open(conf_file_name, 'r') as f: + conf_dict = yaml.load(f) + else: + conf_dict = yaml.load(conf_file_name) conf_dict = walk_dict(conf_dict, conf_path) inst_dict = {} - for name, value in conf_dict.iteritems(): + for name, value in conf_dict.items(): try: inst_dict[name] = value["class"].open_from_uri(value["uri"]) + + if 'attrs' in value: + # We have some attrs we can set on the newly created instrument. + for attr_name, attr_value in value['attrs'].items(): + # Allow "." in attribute names so that we can set attributes + # recursively. + target = inst_dict[name] + while '.' in attr_name: + head, attr_name = attr_name.split('.', 1) + target = getattr(target, head) + setattr(target, attr_name, attr_value) + except IOError as ex: # FIXME: need to subclass Warning so that repeated warnings # aren't ignored. - warnings.warn("Exception occured loading device URI " + warnings.warn("Exception occured loading device with URI " "{}:\n\t{}.".format(value["uri"], ex), RuntimeWarning) inst_dict[name] = None + return inst_dict From 6507fb3d63815460e6b6db5e0a90b7f807e0ef06 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 12:50:42 +1000 Subject: [PATCH 03/21] Added sub/indexing to attrs config. --- instruments/config.py | 10 +++------- instruments/util_fns.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/instruments/config.py b/instruments/config.py index f04f417cb..733595def 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -16,6 +16,8 @@ except ImportError: yaml = None +from instruments.util_fns import setattr_expression + # FUNCTIONS ################################################################### @@ -114,13 +116,7 @@ def load_instruments(conf_file_name, conf_path="/"): if 'attrs' in value: # We have some attrs we can set on the newly created instrument. for attr_name, attr_value in value['attrs'].items(): - # Allow "." in attribute names so that we can set attributes - # recursively. - target = inst_dict[name] - while '.' in attr_name: - head, attr_name = attr_name.split('.', 1) - target = getattr(target, head) - setattr(target, attr_name, attr_value) + setattr_expression(inst_dict[name], attr_name, attr_value) except IOError as ex: # FIXME: need to subclass Warning so that repeated warnings diff --git a/instruments/util_fns.py b/instruments/util_fns.py index 316343fc9..977bfdcf3 100644 --- a/instruments/util_fns.py +++ b/instruments/util_fns.py @@ -14,11 +14,13 @@ from enum import Enum, IntEnum import quantities as pq -# FUNCTIONS ################################################################### +# CONSTANTS ################################################################### -# pylint: disable=too-many-arguments +_IDX_REGEX = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)\[(-?[0-9]*)\]') +# FUNCTIONS ################################################################### +# pylint: disable=too-many-arguments def assume_units(value, units): """ If units are not provided for ``value`` (that is, if it is a raw @@ -37,6 +39,34 @@ def assume_units(value, units): value = pq.Quantity(value, units) return value +def setattr_expression(target, name_expr, value): + """ + Recursively calls getattr/setattr for attribute + names that are miniature expressions with subscripting. + For instance, of the form ``a[0].b``. + """ + # Allow "." in attribute names so that we can set attributes + # recursively. + if '.' in name_expr: + # Recursion: We have to strip off a level of getattr. + head, name_expr = name_expr.split('.', 1) + match = _IDX_REGEX.match(head) + if match: + head_name, head_idx = match.groups() + target = getattr(target, head_name)[int(head_idx)] + else: + target = getattr(target, head) + + setattr_expression(target, name_expr, value) + else: + # Base case: We're in the last part of a dot-expression. + match = _IDX_REGEX.match(name_expr) + if match: + name, idx = match.groups() + getattr(target, name)[int(idx)] = value + else: + setattr(target, name_expr, value) + def convert_temperature(temperature, base): """ From 8a48ace966aa470a3285a908a1a90fafb51927e3 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 13:00:12 +1000 Subject: [PATCH 04/21] Added to docstring. --- instruments/config.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/instruments/config.py b/instruments/config.py index 733595def..e26601cab 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -65,6 +65,17 @@ def load_instruments(conf_file_name, conf_path="/"): the form ``{'ddg': instruments.srs.SRSDG645.open_from_uri('gpib+usb://COM7/15')}``. + Optionally, each the value of each instrument key can also contain a dictionary + of attributes to set on the newly-created instrument. For instance, the following + dictionary creates a ThorLabs APT motor controller instrument with a single motor + model configured:: + + rot_stage: + class: !!python/name:instruments.thorabsapt.APTMotorController + uri: serial:///dev/ttyUSB0?baud=115200 + attrs: + channel[0].motor_model: PRM1-Z8 + By specifying a path within the configuration file, one can load only a part of the given file. For instance, consider the configuration:: From 90226878018298047b3b09ebd364de424a967a80 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 13:15:56 +1000 Subject: [PATCH 05/21] Added support for physical quantities to config. --- instruments/config.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/instruments/config.py b/instruments/config.py index e26601cab..8f625e65d 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -16,6 +16,8 @@ except ImportError: yaml = None +import quantities as pq + from instruments.util_fns import setattr_expression # FUNCTIONS ################################################################### @@ -48,6 +50,11 @@ def walk_dict(d, path): # Otherwise, resolve that segment and recurse. return walk_dict(d[path[0]], path[1:]) +def quantity_constructor(loader, node): + # Follows the example of http://stackoverflow.com/a/43081967/267841. + value = loader.construct_scalar(node) + magnitude, units = value.split(" ", 1) + return pq.Quantity(magnitude, units) def load_instruments(conf_file_name, conf_path="/"): """ @@ -76,6 +83,16 @@ def load_instruments(conf_file_name, conf_path="/"): attrs: channel[0].motor_model: PRM1-Z8 + Unitful attributes can be specified by using the ``!Q`` tag to quickly create + instances of `pq.Quantity`. In the example above, for instance, we can set a motion + timeout as a unitful quantity:: + + attrs: + motion_timeout: !Q 1 minute + + When using the ``!Q`` tag, any text before a space is taken to be the magnitude + of the quantity, and text following is taken to be the unit specification. + By specifying a path within the configuration file, one can load only a part of the given file. For instance, consider the configuration:: @@ -110,6 +127,8 @@ def load_instruments(conf_file_name, conf_path="/"): if yaml is None: raise ImportError("Could not import PyYAML, which is required " "for this function.") + # Add support for the physical quantity tag. + yaml.add_constructor(u'!Q', quantity_constructor) if isinstance(conf_file_name, (str, unicode)): with open(conf_file_name, 'r') as f: From b3ec1e25fa646cb75b1089fecf32067f0e7a7409 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 17:09:11 +1000 Subject: [PATCH 06/21] pylint fixes --- instruments/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/instruments/config.py b/instruments/config.py index 8f625e65d..f4de35cd9 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -18,6 +18,8 @@ import quantities as pq +from future.builtins import str + from instruments.util_fns import setattr_expression # FUNCTIONS ################################################################### @@ -51,6 +53,10 @@ def walk_dict(d, path): return walk_dict(d[path[0]], path[1:]) def quantity_constructor(loader, node): + """ + Constructs a `pq.Quantity` instance from a PyYAML + node tagged as ``!Q``. + """ # Follows the example of http://stackoverflow.com/a/43081967/267841. value = loader.construct_scalar(node) magnitude, units = value.split(" ", 1) @@ -130,7 +136,7 @@ def load_instruments(conf_file_name, conf_path="/"): # Add support for the physical quantity tag. yaml.add_constructor(u'!Q', quantity_constructor) - if isinstance(conf_file_name, (str, unicode)): + if isinstance(conf_file_name, str): with open(conf_file_name, 'r') as f: conf_dict = yaml.load(f) else: From 0ff31866b1421fb3521b65469409b5ecafae7e8a Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 17:23:40 +1000 Subject: [PATCH 07/21] Tests for new setattr_expression --- instruments/tests/test_util_fns.py | 46 +++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/instruments/tests/test_util_fns.py b/instruments/tests/test_util_fns.py index 914372404..7144f1bc7 100644 --- a/instruments/tests/test_util_fns.py +++ b/instruments/tests/test_util_fns.py @@ -16,7 +16,8 @@ from instruments.util_fns import ( ProxyList, - assume_units, convert_temperature + assume_units, convert_temperature, + setattr_expression ) # TEST CASES ################################################################# @@ -169,3 +170,46 @@ def test_temperater_conversion_failure(): @raises(ValueError) def test_assume_units_failures(): assume_units(1, 'm').rescale('s') + +def test_setattr_expression_simple(): + class A(object): + x = 'x' + y = 'y' + z = 'z' + + a = A() + setattr_expression(a, 'x', 'foo') + assert a.x == 'foo' + +def test_setattr_expression_index(): + class A(object): + x = ['x', 'y', 'z'] + + a = A() + setattr_expression(a, 'x[1]', 'foo') + assert a.x[1] == 'foo' + +def test_setattr_expression_nested(): + class B(object): + x = 'x' + class A(object): + b = None + def __init__(self): + self.b = B() + + a = A() + setattr_expression(a, 'b.x', 'foo') + assert a.b.x == 'foo' + +def test_setattr_expression_both(): + class B(object): + x = 'x' + class A(object): + b = None + def __init__(self): + self.b = [B()] + + a = A() + setattr_expression(a, 'b[0].x', 'foo') + assert a.b[0].x == 'foo' + From bf8bcc1a9b324b9f2db337ebd7d8de15fec2407f Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 17:41:36 +1000 Subject: [PATCH 08/21] Added tests for new config, YAML. --- instruments/config.py | 15 ++++--- instruments/tests/test_config.py | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 instruments/tests/test_config.py diff --git a/instruments/config.py b/instruments/config.py index f4de35cd9..57afa9be3 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -20,7 +20,7 @@ from future.builtins import str -from instruments.util_fns import setattr_expression +from instruments.util_fns import setattr_expression, split_unit_str # FUNCTIONS ################################################################### @@ -43,8 +43,9 @@ def walk_dict(d, path): # Treat as a base case that the path is empty. if not path: return d - if isinstance(path, str): + if not isinstance(path, list): path = path.split("/") + if not path[0]: # If the first part of the path is empty, do nothing. return walk_dict(d, path[1:]) @@ -59,8 +60,12 @@ def quantity_constructor(loader, node): """ # Follows the example of http://stackoverflow.com/a/43081967/267841. value = loader.construct_scalar(node) - magnitude, units = value.split(" ", 1) - return pq.Quantity(magnitude, units) + return pq.Quantity(*split_unit_str(value)) + +# We avoid having to register !Q every time by doing as soon as the +# relevant constructor is defined. +if yaml: + yaml.add_constructor(u'!Q', quantity_constructor) def load_instruments(conf_file_name, conf_path="/"): """ @@ -133,8 +138,6 @@ def load_instruments(conf_file_name, conf_path="/"): if yaml is None: raise ImportError("Could not import PyYAML, which is required " "for this function.") - # Add support for the physical quantity tag. - yaml.add_constructor(u'!Q', quantity_constructor) if isinstance(conf_file_name, str): with open(conf_file_name, 'r') as f: diff --git a/instruments/tests/test_config.py b/instruments/tests/test_config.py new file mode 100644 index 000000000..8c7c5ae64 --- /dev/null +++ b/instruments/tests/test_config.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Module containing tests for util_fns.py +""" + +# IMPORTS #################################################################### + +from __future__ import absolute_import, unicode_literals + +from io import StringIO + +import quantities as pq + +from unittest import skipIf + +try: + import yaml +except ImportError: + yaml = None + +from instruments import Instrument +from instruments.config import ( + load_instruments +) + +# TEST CASES ################################################################# + +# pylint: disable=protected-access,missing-docstring + +@skipIf(yaml is None, "PyYAML is not installed.") +def test_load_test_instrument(): + config_data = StringIO(u""" +test: + class: !!python/name:instruments.Instrument + uri: test:// +""") + insts = load_instruments(config_data) + assert isinstance(insts['test'], Instrument) + +@skipIf(yaml is None, "PyYAML is not installed.") +def test_load_test_instrument_subtree(): + config_data = StringIO(u""" +instruments: + test: + class: !!python/name:instruments.Instrument + uri: test:// +""") + insts = load_instruments(config_data, conf_path="/instruments") + assert isinstance(insts['test'], Instrument) + +@skipIf(yaml is None, "PyYAML is not installed.") +def test_yaml_quantity_tag(): + yaml_data = StringIO(u""" +a: + b: !Q 37 tesla + c: !Q 41.2 inches + d: !Q 98 +""") + data = yaml.load(yaml_data) + assert data['a']['b'] == pq.Quantity(37, 'tesla') + assert data['a']['c'] == pq.Quantity(41.2, 'inches') + assert data['a']['d'] == 98 + +@skipIf(yaml is None, "PyYAML is not installed.") +def test_load_test_instrument_setattr(): + config_data = StringIO(u""" +test: + class: !!python/name:instruments.Instrument + uri: test:// + attrs: + foo: !Q 111 GHz +""") + insts = load_instruments(config_data) + assert insts['test'].foo == pq.Quantity(111, 'GHz') From f4d23bf84c4b704bbb834c7c9ca4c86b47c6a94f Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 17:45:12 +1000 Subject: [PATCH 09/21] pylint fix for new tests --- instruments/tests/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instruments/tests/test_config.py b/instruments/tests/test_config.py index 8c7c5ae64..eaec4d6ab 100644 --- a/instruments/tests/test_config.py +++ b/instruments/tests/test_config.py @@ -10,10 +10,10 @@ from io import StringIO -import quantities as pq - from unittest import skipIf +import quantities as pq + try: import yaml except ImportError: From 5f4ac278ea7e68d1589d0881d6a50b9e79c2633b Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Wed, 12 Apr 2017 18:33:02 +1000 Subject: [PATCH 10/21] Very minor pylint fix --- instruments/tests/test_util_fns.py | 1 - 1 file changed, 1 deletion(-) diff --git a/instruments/tests/test_util_fns.py b/instruments/tests/test_util_fns.py index 7144f1bc7..ecf8e6ec5 100644 --- a/instruments/tests/test_util_fns.py +++ b/instruments/tests/test_util_fns.py @@ -212,4 +212,3 @@ def __init__(self): a = A() setattr_expression(a, 'b[0].x', 'foo') assert a.b[0].x == 'foo' - From 86ca2e6cb600655b03e64bedf8cf6c073c69baea Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 13 Apr 2017 14:06:53 +1000 Subject: [PATCH 11/21] Added ruamel.yaml as testing dep. --- dev-requirements.txt | 1 + instruments/config.py | 7 +++++-- instruments/tests/test_config.py | 13 +------------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 7418bc754..3f8a28360 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ mock nose pylint +ruamel.yaml diff --git a/instruments/config.py b/instruments/config.py index 57afa9be3..05f5dc509 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -12,9 +12,12 @@ import warnings try: - import yaml + import ruamel_yaml as yaml except ImportError: - yaml = None + try: + import yaml + except ImportError: + yaml = None import quantities as pq diff --git a/instruments/tests/test_config.py b/instruments/tests/test_config.py index eaec4d6ab..00a0a635d 100644 --- a/instruments/tests/test_config.py +++ b/instruments/tests/test_config.py @@ -10,25 +10,17 @@ from io import StringIO -from unittest import skipIf - import quantities as pq -try: - import yaml -except ImportError: - yaml = None - from instruments import Instrument from instruments.config import ( - load_instruments + load_instruments, yaml ) # TEST CASES ################################################################# # pylint: disable=protected-access,missing-docstring -@skipIf(yaml is None, "PyYAML is not installed.") def test_load_test_instrument(): config_data = StringIO(u""" test: @@ -38,7 +30,6 @@ def test_load_test_instrument(): insts = load_instruments(config_data) assert isinstance(insts['test'], Instrument) -@skipIf(yaml is None, "PyYAML is not installed.") def test_load_test_instrument_subtree(): config_data = StringIO(u""" instruments: @@ -49,7 +40,6 @@ def test_load_test_instrument_subtree(): insts = load_instruments(config_data, conf_path="/instruments") assert isinstance(insts['test'], Instrument) -@skipIf(yaml is None, "PyYAML is not installed.") def test_yaml_quantity_tag(): yaml_data = StringIO(u""" a: @@ -62,7 +52,6 @@ def test_yaml_quantity_tag(): assert data['a']['c'] == pq.Quantity(41.2, 'inches') assert data['a']['d'] == 98 -@skipIf(yaml is None, "PyYAML is not installed.") def test_load_test_instrument_setattr(): config_data = StringIO(u""" test: From fa0a518e839b38998b074f91a14c0d611aa8fb92 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 13 Apr 2017 14:10:30 +1000 Subject: [PATCH 12/21] Reworked awkward sentence. --- instruments/config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/instruments/config.py b/instruments/config.py index 05f5dc509..dfe144be8 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -86,8 +86,9 @@ def load_instruments(conf_file_name, conf_path="/"): the form ``{'ddg': instruments.srs.SRSDG645.open_from_uri('gpib+usb://COM7/15')}``. - Optionally, each the value of each instrument key can also contain a dictionary - of attributes to set on the newly-created instrument. For instance, the following + Each instrument configuration section can also specify one or more attributes + to set. These attributes are specified using a ``attrs`` section as well as the + required ``class`` and ``uri`` sections. For instance, the following dictionary creates a ThorLabs APT motor controller instrument with a single motor model configured:: From a0aff098ff7398b80a95290a78f78f3575077a1c Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 13 Apr 2017 14:12:10 +1000 Subject: [PATCH 13/21] Updated docs for ruamel.yaml dep. --- doc/source/intro.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/intro.rst b/doc/source/intro.rst index c1943320e..072c1e582 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -36,7 +36,7 @@ $ pip install -r requirements.txt Optional Dependencies ~~~~~~~~~~~~~~~~~~~~~ -- `PyYAML`_ (required for configuration file support) +- `ruamel.yaml`_ (required for configuration file support) - `PyUSB`_ (version 1.0a or higher, required for raw USB support) - `PyVISA`_ (required for accessing instruments via VISA library) @@ -44,7 +44,7 @@ Optional Dependencies .. _quantities: http://pythonhosted.org/quantities/ .. _enum34: https://pypi.python.org/pypi/enum34 .. _future: https://pypi.python.org/pypi/future -.. _PyYAML: https://bitbucket.org/xi/pyyaml +.. _ruamel.yaml: http://yaml.readthedocs.io .. _PyUSB: http://sourceforge.net/apps/trac/pyusb/ .. _PyVISA: http://pyvisa.sourceforge.net/ From 29ffc572cf2ac27e20ddeee017ff514284c44a09 Mon Sep 17 00:00:00 2001 From: Steven Casagrande Date: Wed, 19 Apr 2017 17:13:33 -0400 Subject: [PATCH 14/21] Remove pyyaml, only use ruamel.yaml --- dev-requirements.txt | 1 - doc/source/intro.rst | 6 ++---- instruments/config.py | 12 ++---------- requirements.txt | 2 +- setup.py | 2 +- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index bddd7459e..c42f8689e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,3 @@ mock nose pylint==1.6.5 -ruamel.yaml diff --git a/doc/source/intro.rst b/doc/source/intro.rst index c62902376..733a33c44 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -33,15 +33,13 @@ $ pip install -r requirements.txt - `enum34`_ - `future`_ - `python-vxi11`_ -- `PyUSB`_ +- `PyUSB`_ (version 1.0a or higher, required for raw USB support) - `python-usbtmc`_ -- `PyYAML`_ +- `ruamel.yaml`_ (required for configuration file support) Optional Dependencies ~~~~~~~~~~~~~~~~~~~~~ -- `ruamel.yaml`_ (required for configuration file support) -- `PyUSB`_ (version 1.0a or higher, required for raw USB support) - `PyVISA`_ (required for accessing instruments via VISA library) .. _PySerial: http://pyserial.sourceforge.net/ diff --git a/instruments/config.py b/instruments/config.py index dfe144be8..022010ea2 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -11,14 +11,7 @@ import warnings -try: - import ruamel_yaml as yaml -except ImportError: - try: - import yaml - except ImportError: - yaml = None - +import ruamel.yaml as yaml import quantities as pq from future.builtins import str @@ -67,8 +60,7 @@ def quantity_constructor(loader, node): # We avoid having to register !Q every time by doing as soon as the # relevant constructor is defined. -if yaml: - yaml.add_constructor(u'!Q', quantity_constructor) +yaml.add_constructor(u'!Q', quantity_constructor) def load_instruments(conf_file_name, conf_path="/"): """ diff --git a/requirements.txt b/requirements.txt index 113c7a6a3..622bb14d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,4 @@ enum34 python-vxi11>=0.8 pyusb python-usbtmc -pyyaml +ruamel.yaml diff --git a/setup.py b/setup.py index c928c9608..b422efd4b 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ "python-vxi11", "python-usbtmc", "pyusb", - "pyyaml" + "ruamel.yaml" ] EXTRAS_REQUIRE = { 'VISA': ["pyvisa"] From ccda2a1d2f20056e7f13fdf77d158026ec9d93af Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 11:17:12 +1000 Subject: [PATCH 15/21] Disabled info category messages from mylint. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7cbb7d02b..f2f27b526 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,8 @@ install: - pip install coverage script: - nosetests --with-coverage -w instruments - - pylint --py3k instruments/ - - pylint instruments/ + - pylint --py3k instruments + - pylint --disable=I instruments after_success: - coveralls deploy: From 8711b96e32e2d89ffc8e13a0ad1b13c0f984c956 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 11:25:14 +1000 Subject: [PATCH 16/21] Added version dump to travis config. --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index f2f27b526..ed6d2479f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,17 @@ install: - "pip install -r dev-requirements.txt" - pip install python-coveralls - pip install coverage +before_script: + # We use before_script to report version and path information in a way + # that can be easily hidden by Travis' log folding. Moreover, a nonzero + # exit code from this block kills the entire job, meaning that if we can't + # even sensibly get version information, we correctly abort. + - which python + - python --version + - which nosetests + - nosetests --version + - which pylint + - pylint --version script: - nosetests --with-coverage -w instruments - pylint --py3k instruments From f73a2439116ba89b867893dbcfe00f1982e03694 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 11:42:02 +1000 Subject: [PATCH 17/21] Trying ot use python -m to work around venv issues. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed6d2479f..816f0f3d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,8 +23,10 @@ before_script: - pylint --version script: - nosetests --with-coverage -w instruments - - pylint --py3k instruments - - pylint --disable=I instruments + # We invoke pylint using -m to ensure that we get the exact same + # version as in the virtualenv that Travis creates for 3.5. + - python -m pylint --py3k instruments + - python -m pylint --disable=I instruments after_success: - coveralls deploy: From 8d969813abed3d102a72e09aefdb0c711e6f2a39 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 12:06:39 +1000 Subject: [PATCH 18/21] Revert "Trying ot use python -m to work around venv issues." This reverts commit f73a2439116ba89b867893dbcfe00f1982e03694. --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 816f0f3d6..ed6d2479f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,10 +23,8 @@ before_script: - pylint --version script: - nosetests --with-coverage -w instruments - # We invoke pylint using -m to ensure that we get the exact same - # version as in the virtualenv that Travis creates for 3.5. - - python -m pylint --py3k instruments - - python -m pylint --disable=I instruments + - pylint --py3k instruments + - pylint --disable=I instruments after_success: - coveralls deploy: From 5b148b6f9c2d438b1222306b0ed11646f644eaa6 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 14:19:26 +1000 Subject: [PATCH 19/21] ruamel.yaml vs ruamel_yaml and fixing pylint false +ve. --- instruments/config.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/instruments/config.py b/instruments/config.py index 022010ea2..20f2b8f17 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -11,7 +11,18 @@ import warnings -import ruamel.yaml as yaml +try: + import ruamel.yaml as yaml +except ImportError: + # Some versions of ruamel.yaml are named ruamel_yaml, so try that + # too. + # + # In either case, we've observed issues with pylint where it will raise + # a false positive from its import-error checker, so we locally disable + # it here. Once the cause for the false positive has been identified, + # the import-error check should be re-enabled. + import ruamel_yaml as yaml # pylint: disable=import-error + import quantities as pq from future.builtins import str From 3099d079e7f93d82c4e791307b4b27090d646d82 Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 14:26:21 +1000 Subject: [PATCH 20/21] Explicitly use unsafe loader as suggested by ruamel.yaml warnings. --- instruments/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instruments/config.py b/instruments/config.py index 20f2b8f17..107daa157 100644 --- a/instruments/config.py +++ b/instruments/config.py @@ -143,14 +143,14 @@ def load_instruments(conf_file_name, conf_path="/"): """ if yaml is None: - raise ImportError("Could not import PyYAML, which is required " + raise ImportError("Could not import ruamel.yaml, which is required " "for this function.") if isinstance(conf_file_name, str): with open(conf_file_name, 'r') as f: - conf_dict = yaml.load(f) + conf_dict = yaml.load(f, Loader=yaml.Loader) else: - conf_dict = yaml.load(conf_file_name) + conf_dict = yaml.load(conf_file_name, Loader=yaml.Loader) conf_dict = walk_dict(conf_dict, conf_path) From afca981707f592c7c1d19e9f30287db6ec7b642a Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Thu, 20 Apr 2017 14:34:33 +1000 Subject: [PATCH 21/21] Marked test as explicitly unsafe as well. --- instruments/tests/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instruments/tests/test_config.py b/instruments/tests/test_config.py index 00a0a635d..6a2119fa5 100644 --- a/instruments/tests/test_config.py +++ b/instruments/tests/test_config.py @@ -47,7 +47,7 @@ def test_yaml_quantity_tag(): c: !Q 41.2 inches d: !Q 98 """) - data = yaml.load(yaml_data) + data = yaml.load(yaml_data, Loader=yaml.Loader) assert data['a']['b'] == pq.Quantity(37, 'tesla') assert data['a']['c'] == pq.Quantity(41.2, 'inches') assert data['a']['d'] == 98