From 11e45e9176d2f63079d107b25ada20027f5f2d97 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 8 Jul 2016 16:01:09 +0200 Subject: [PATCH 1/2] feature: Add NestedAttrAccess support to MockModel --- qcodes/instrument/mock.py | 46 +++++++++++++++++++++++++++++++++ qcodes/process/server.py | 25 ++++++++++++++++-- qcodes/tests/test_instrument.py | 28 ++++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/qcodes/instrument/mock.py b/qcodes/instrument/mock.py index 951b2aa6ccc4..aa182f1adfb0 100644 --- a/qcodes/instrument/mock.py +++ b/qcodes/instrument/mock.py @@ -4,6 +4,7 @@ from .base import Instrument from qcodes.process.server import ServerManager, BaseServer +from qcodes.utils.nested_attrs import NestedAttrAccess, _NoDefault class MockInstrument(Instrument): @@ -178,11 +179,24 @@ class MockModel(ServerManager, BaseServer): # pragma: no cover methods you shouldn't call: the extras (_(set|get)) should only be called on the server copy. Normally this should only be called via the attached instruments anyway. + + The model supports ``NestedAttrAccess`` calls ``getattr``, ``setattr``, + ``callattr``, and ``delattr`` Because the manager and server are the same + object, we override these methods with proxy methods after the server has + been started. """ def __init__(self, name='Model-{:.7s}'): super().__init__(name, server_class=None) + # now that the server has started, we can remap attribute access + # from the private methods (_getattr) to the public ones (getattr) + # but the server copy will still have the NestedAttrAccess ones + self.getattr = self._getattr + self.setattr = self._setattr + self.callattr = self._callattr + self.delattr = self._delattr + def _run_server(self): self.run_event_loop() @@ -221,3 +235,35 @@ def handle_cmd(self, cmd): else: raise ValueError() + + def _getattr(self, attr, default=_NoDefault): + """ + Get a (possibly nested) attribute of this model on its server. + + See NestedAttrAccess for details. + """ + return self.ask('method_call', 'getattr', attr, default) + + def _setattr(self, attr, value): + """ + Set a (possibly nested) attribute of this model on its server. + + See NestedAttrAccess for details. + """ + self.ask('method_call', 'setattr', attr, value) + + def _callattr(self, attr, *args, **kwargs): + """ + Call a (possibly nested) method of this model on its server. + + See NestedAttrAccess for details. + """ + return self.ask('method_call', 'callattr', attr, *args, **kwargs) + + def _delattr(self, attr): + """ + Delete a (possibly nested) attribute of this model on its server. + + See NestedAttrAccess for details. + """ + self.ask('method_call', 'delattr', attr) diff --git a/qcodes/process/server.py b/qcodes/process/server.py index e837619aeb0c..7acc408bd720 100644 --- a/qcodes/process/server.py +++ b/qcodes/process/server.py @@ -11,6 +11,7 @@ RESPONSE_OK = 'OK' RESPONSE_ERROR = 'ERROR' +from qcodes.utils.nested_attrs import NestedAttrAccess from .qcodes_process import QcodesProcess from .helpers import kill_queue @@ -204,7 +205,7 @@ def close(self): del self.query_lock -class BaseServer: +class BaseServer(NestedAttrAccess): """ Base class for servers to run in separate processes. @@ -234,12 +235,14 @@ class BaseServer: - `QUERY_WRITE` (from `server_manager.write`): will NEVER send a response, return values are ignored and errors go to the logging framework. - Two handlers are predefined: + Three handlers are predefined: - `handle_halt` (but override it if your event loop does not use self.running=False to stop) - `handle_get_handlers` (lists all available handler methods) + + - `handle_method_call` (call an arbitrary method on the server) """ # just for testing - how long to allow it to wait on a queue.get @@ -372,3 +375,21 @@ def handle_get_handlers(self): if name.startswith('handle_') and callable(getattr(self, name)): handlers.append(name[len('handle_'):]) return handlers + + def handle_method_call(self, method_name, *args, **kwargs): + """ + Pass through arbitrary method calls to the server. + + Args: + method_name (str): the method name to call. + Primarily intended for NestedAttrAccess, ie: + ``getattr``, ``setattr``, ``callattr``, ``delattr``. + + *args (Any): passed to the method + + **kwargs (Any): passed to the method + + Returns: + Any: the return value of the method + """ + return getattr(self, method_name)(*args, **kwargs) diff --git a/qcodes/tests/test_instrument.py b/qcodes/tests/test_instrument.py index f6b8a407092d..737a55d4395f 100644 --- a/qcodes/tests/test_instrument.py +++ b/qcodes/tests/test_instrument.py @@ -946,3 +946,31 @@ def test_local(self): with self.assertRaises(ValueError): self.gates.ask('knock knock? Oh never mind.') + + +class TestModelAttrAccess(TestCase): + def setUp(self): + self.model = AMockModel() + + def tearDown(self): + self.model.close() + + def test_attr_access(self): + model = self.model + + model.a = 'local' + with self.assertRaises(AttributeError): + model.getattr('a') + + self.assertEqual(model.getattr('a', 'dflt'), 'dflt') + + model.setattr('a', 'remote') + self.assertEqual(model.a, 'local') + self.assertEqual(model.getattr('a'), 'remote') + + model.delattr('a') + self.assertEqual(model.getattr('a', 'dflt'), 'dflt') + + model.fmt = 'local override of a remote method' + self.assertEqual(model.callattr('fmt', 42), '42.000') + self.assertEqual(model.callattr('fmt', value=12.4), '12.400') From b0c5aa460bd09c085e8bf491815ce7d9b5685f56 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 25 Jul 2016 12:27:18 +0200 Subject: [PATCH 2/2] style: remove unused import instrument/mock.py --- qcodes/instrument/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/instrument/mock.py b/qcodes/instrument/mock.py index aa182f1adfb0..30ca212d78ec 100644 --- a/qcodes/instrument/mock.py +++ b/qcodes/instrument/mock.py @@ -4,7 +4,7 @@ from .base import Instrument from qcodes.process.server import ServerManager, BaseServer -from qcodes.utils.nested_attrs import NestedAttrAccess, _NoDefault +from qcodes.utils.nested_attrs import _NoDefault class MockInstrument(Instrument):