Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions qcodes/instrument/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .base import Instrument
from qcodes.process.server import ServerManager, BaseServer
from qcodes.utils.nested_attrs import _NoDefault


class MockInstrument(Instrument):
Expand Down Expand Up @@ -178,11 +179,24 @@ class MockModel(ServerManager, BaseServer): # pragma: no cover
methods you shouldn't call: the extras (<instrument>_(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()

Expand Down Expand Up @@ -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)
25 changes: 23 additions & 2 deletions qcodes/process/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
28 changes: 28 additions & 0 deletions qcodes/tests/test_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')