qcodes 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. qcodes/__init__.py +65 -0
  2. qcodes/actions.py +207 -0
  3. qcodes/config/__init__.py +2 -0
  4. qcodes/config/config.py +396 -0
  5. qcodes/config/qcodesrc.json +11 -0
  6. qcodes/config/qcodesrc_schema.json +55 -0
  7. qcodes/data/__init__.py +0 -0
  8. qcodes/data/data_array.py +520 -0
  9. qcodes/data/data_set.py +803 -0
  10. qcodes/data/format.py +309 -0
  11. qcodes/data/gnuplot_format.py +366 -0
  12. qcodes/data/hdf5_format.py +385 -0
  13. qcodes/data/io.py +266 -0
  14. qcodes/data/location.py +166 -0
  15. qcodes/data/manager.py +163 -0
  16. qcodes/instrument/__init__.py +0 -0
  17. qcodes/instrument/base.py +630 -0
  18. qcodes/instrument/function.py +134 -0
  19. qcodes/instrument/ip.py +236 -0
  20. qcodes/instrument/metaclass.py +45 -0
  21. qcodes/instrument/mock.py +278 -0
  22. qcodes/instrument/parameter.py +1001 -0
  23. qcodes/instrument/remote.py +529 -0
  24. qcodes/instrument/server.py +189 -0
  25. qcodes/instrument/sweep_values.py +286 -0
  26. qcodes/instrument/visa.py +198 -0
  27. qcodes/instrument_drivers/AlazarTech/ATS.py +1001 -0
  28. qcodes/instrument_drivers/AlazarTech/ATS9870.py +266 -0
  29. qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +143 -0
  30. qcodes/instrument_drivers/AlazarTech/__init__.py +0 -0
  31. qcodes/instrument_drivers/QuTech/IVVI.py +390 -0
  32. qcodes/instrument_drivers/QuTech/__init__.py +0 -0
  33. qcodes/instrument_drivers/__init__.py +0 -0
  34. qcodes/instrument_drivers/agilent/Agilent_34400A.py +155 -0
  35. qcodes/instrument_drivers/agilent/E8527D.py +79 -0
  36. qcodes/instrument_drivers/agilent/HP33210A.py +104 -0
  37. qcodes/instrument_drivers/agilent/__init__.py +0 -0
  38. qcodes/instrument_drivers/agilent/test_suite.py +96 -0
  39. qcodes/instrument_drivers/ithaco/Ithaco_1211.py +110 -0
  40. qcodes/instrument_drivers/ithaco/__init__.py +0 -0
  41. qcodes/instrument_drivers/oxford/__init__.py +0 -0
  42. qcodes/instrument_drivers/oxford/mercuryiPS.py +358 -0
  43. qcodes/instrument_drivers/oxford/triton.py +227 -0
  44. qcodes/instrument_drivers/rigol/DG4000.py +591 -0
  45. qcodes/instrument_drivers/rigol/__init__.py +0 -0
  46. qcodes/instrument_drivers/rohde_schwarz/SGS100A.py +99 -0
  47. qcodes/instrument_drivers/rohde_schwarz/ZNB20.py +154 -0
  48. qcodes/instrument_drivers/rohde_schwarz/__init__.py +0 -0
  49. qcodes/instrument_drivers/signal_hound/USB_SA124B.py +567 -0
  50. qcodes/instrument_drivers/signal_hound/__init__.py +0 -0
  51. qcodes/instrument_drivers/stanford_research/SR560.py +116 -0
  52. qcodes/instrument_drivers/stanford_research/SR830.py +314 -0
  53. qcodes/instrument_drivers/stanford_research/SR865.py +305 -0
  54. qcodes/instrument_drivers/stanford_research/__init__.py +0 -0
  55. qcodes/instrument_drivers/tektronix/AWG5014.py +1228 -0
  56. qcodes/instrument_drivers/tektronix/AWG520.py +659 -0
  57. qcodes/instrument_drivers/tektronix/Keithley_2000.py +195 -0
  58. qcodes/instrument_drivers/tektronix/Keithley_2600.py +83 -0
  59. qcodes/instrument_drivers/tektronix/Keithley_2700.py +395 -0
  60. qcodes/instrument_drivers/tektronix/__init__.py +0 -0
  61. qcodes/instrument_drivers/test.py +94 -0
  62. qcodes/instrument_drivers/weinschel/Weinschel_8320.py +21 -0
  63. qcodes/instrument_drivers/weinschel/__init__.py +0 -0
  64. qcodes/instrument_drivers/weinschel/test_suite.py +27 -0
  65. qcodes/loops.py +1009 -0
  66. qcodes/measure.py +155 -0
  67. qcodes/plots/__init__.py +0 -0
  68. qcodes/plots/base.py +280 -0
  69. qcodes/plots/colors.py +135 -0
  70. qcodes/plots/pyqtgraph.py +384 -0
  71. qcodes/plots/qcmatplotlib.py +206 -0
  72. qcodes/process/__init__.py +0 -0
  73. qcodes/process/helpers.py +60 -0
  74. qcodes/process/qcodes_process.py +72 -0
  75. qcodes/process/server.py +395 -0
  76. qcodes/process/stream_queue.py +152 -0
  77. qcodes/station.py +178 -0
  78. qcodes/test.py +119 -0
  79. qcodes/tests/__init__.py +0 -0
  80. qcodes/tests/common.py +7 -0
  81. qcodes/tests/data_mocks.py +173 -0
  82. qcodes/tests/instrument_mocks.py +327 -0
  83. qcodes/tests/py35_syntax.py +13 -0
  84. qcodes/tests/test_combined_par.py +156 -0
  85. qcodes/tests/test_command.py +120 -0
  86. qcodes/tests/test_config.py +222 -0
  87. qcodes/tests/test_data.py +668 -0
  88. qcodes/tests/test_deferred_operations.py +105 -0
  89. qcodes/tests/test_driver_testcase.py +58 -0
  90. qcodes/tests/test_format.py +414 -0
  91. qcodes/tests/test_hdf5formatter.py +333 -0
  92. qcodes/tests/test_helpers.py +630 -0
  93. qcodes/tests/test_instrument.py +981 -0
  94. qcodes/tests/test_instrument_server.py +156 -0
  95. qcodes/tests/test_json.py +48 -0
  96. qcodes/tests/test_location_provider.py +109 -0
  97. qcodes/tests/test_loop.py +825 -0
  98. qcodes/tests/test_measure.py +49 -0
  99. qcodes/tests/test_metadata.py +49 -0
  100. qcodes/tests/test_multiprocessing.py +471 -0
  101. qcodes/tests/test_nested_attrs.py +100 -0
  102. qcodes/tests/test_parameter.py +151 -0
  103. qcodes/tests/test_plots.py +44 -0
  104. qcodes/tests/test_sweep_values.py +159 -0
  105. qcodes/tests/test_validators.py +482 -0
  106. qcodes/tests/test_visa.py +184 -0
  107. qcodes/utils/__init__.py +0 -0
  108. qcodes/utils/command.py +176 -0
  109. qcodes/utils/deferred_operations.py +276 -0
  110. qcodes/utils/helpers.py +417 -0
  111. qcodes/utils/metadata.py +42 -0
  112. qcodes/utils/nested_attrs.py +177 -0
  113. qcodes/utils/reload_code.py +98 -0
  114. qcodes/utils/threading.py +72 -0
  115. qcodes/utils/timing.py +89 -0
  116. qcodes/utils/validators.py +393 -0
  117. qcodes/version.py +1 -0
  118. qcodes/widgets/__init__.py +0 -0
  119. qcodes/widgets/display.py +47 -0
  120. qcodes/widgets/widgets.css +77 -0
  121. qcodes/widgets/widgets.js +237 -0
  122. qcodes/widgets/widgets.py +215 -0
  123. qcodes-0.1.0.dist-info/DESCRIPTION.rst +165 -0
  124. qcodes-0.1.0.dist-info/METADATA +194 -0
  125. qcodes-0.1.0.dist-info/RECORD +128 -0
  126. qcodes-0.1.0.dist-info/WHEEL +5 -0
  127. qcodes-0.1.0.dist-info/metadata.json +1 -0
  128. qcodes-0.1.0.dist-info/top_level.txt +1 -0
qcodes/__init__.py ADDED
@@ -0,0 +1,65 @@
1
+ """Set up the main qcodes namespace."""
2
+
3
+ # flake8: noqa (we don't need the "<...> imported but unused" error)
4
+
5
+ # just for convenience in debugging, so we don't have to
6
+ # separately import multiprocessing
7
+ from multiprocessing import active_children
8
+
9
+ # config
10
+
11
+ from qcodes.config import Config
12
+
13
+ config = Config()
14
+
15
+ from qcodes.version import __version__
16
+ from qcodes.process.helpers import set_mp_method
17
+ from qcodes.utils.helpers import in_notebook
18
+
19
+ # code that should only be imported into the main (notebook) thread
20
+ # in particular, importing matplotlib in the side processes takes a long
21
+ # time and spins up other processes in order to try and get a front end
22
+ if in_notebook(): # pragma: no cover
23
+ try:
24
+ from qcodes.plots.qcmatplotlib import MatPlot
25
+ except Exception:
26
+ print('matplotlib plotting not supported, '
27
+ 'try "from qcodes.plots.qcmatplotlib import MatPlot" '
28
+ 'to see the full error')
29
+
30
+ try:
31
+ from qcodes.plots.pyqtgraph import QtPlot
32
+ except Exception:
33
+ print('pyqtgraph plotting not supported, '
34
+ 'try "from qcodes.plots.pyqtgraph import QtPlot" '
35
+ 'to see the full error')
36
+
37
+ from qcodes.widgets.widgets import show_subprocess_widget
38
+
39
+ from qcodes.station import Station
40
+ from qcodes.loops import get_bg, halt_bg, Loop
41
+ from qcodes.measure import Measure
42
+ from qcodes.actions import Task, Wait, BreakIf
43
+
44
+ from qcodes.data.manager import get_data_manager
45
+ from qcodes.data.data_set import DataMode, DataSet, new_data, load_data
46
+ from qcodes.data.location import FormatLocation
47
+ from qcodes.data.data_array import DataArray
48
+ from qcodes.data.format import Formatter
49
+ from qcodes.data.gnuplot_format import GNUPlotFormat
50
+ from qcodes.data.hdf5_format import HDF5Format
51
+ from qcodes.data.io import DiskIO
52
+
53
+ from qcodes.instrument.base import Instrument
54
+ from qcodes.instrument.ip import IPInstrument
55
+ from qcodes.instrument.visa import VisaInstrument
56
+ from qcodes.instrument.mock import MockInstrument, MockModel
57
+
58
+ from qcodes.instrument.function import Function
59
+ from qcodes.instrument.parameter import Parameter, StandardParameter, combine, CombinedParameter
60
+ from qcodes.instrument.sweep_values import SweepFixedValues, SweepValues
61
+
62
+ from qcodes.utils import validators
63
+
64
+ from qcodes.instrument_drivers.test import test_instruments, test_instrument
65
+ from qcodes.test import test_core, test_part
qcodes/actions.py ADDED
@@ -0,0 +1,207 @@
1
+ """Actions, mainly to be executed in measurement Loops."""
2
+ import time
3
+
4
+ from qcodes.utils.deferred_operations import is_function
5
+ from qcodes.utils.threading import thread_map
6
+
7
+
8
+ _NO_SNAPSHOT = {'type': None, 'description': 'Action without snapshot'}
9
+
10
+
11
+ def _actions_snapshot(actions, update):
12
+ """Make a list of snapshots from a list of actions."""
13
+ snapshot = []
14
+ for action in actions:
15
+ if hasattr(action, 'snapshot'):
16
+ snapshot.append(action.snapshot(update=update))
17
+ else:
18
+ snapshot.append(_NO_SNAPSHOT)
19
+ return snapshot
20
+
21
+
22
+ class Task:
23
+ """
24
+ A predefined task to be executed within a measurement Loop.
25
+
26
+ The first argument should be a callable, to which any subsequent
27
+ args and kwargs (which are evaluated before the loop starts) are passed.
28
+
29
+ The args and kwargs are first evaluated if they are found to be callable.
30
+
31
+ Keyword Args passed when the Task is called are ignored,
32
+ but are accepted for compatibility with other things happening in a Loop.
33
+
34
+ Args:
35
+ func (callable): Function to executed
36
+ *args: pass to func, after evaluation if callable
37
+ **kwargs: pass to func, after evaluation if callable
38
+
39
+ """
40
+ def __init__(self, func, *args, **kwargs):
41
+ self.func = func
42
+ self.args = args
43
+ self.kwargs = kwargs
44
+
45
+ def __call__(self, **ignore_kwargs):
46
+ # If any of the arguments are callable, evaluate them first
47
+ eval_args = [arg() if callable(arg) else arg for arg in self.args]
48
+ eval_kwargs = {k: (v() if callable(v) else v) for k, v in self.kwargs.items()}
49
+
50
+ self.func(*eval_args, **eval_kwargs)
51
+
52
+ def snapshot(self, update=False):
53
+ """
54
+ Snapshots task
55
+ Args:
56
+ update (bool): TODO not in use
57
+
58
+ Returns:
59
+ dict: snapshot
60
+ """
61
+ return {'type': 'Task', 'func': repr(self.func)}
62
+
63
+
64
+ class Wait:
65
+ """
66
+ A simple class to tell a Loop to wait <delay> seconds.
67
+
68
+ This is transformed into a Task within the Loop, such that
69
+ it can do other things (monitor, check for halt) during the delay.
70
+
71
+ But for use outside of a Loop, it is also callable (then it just sleeps)
72
+
73
+ Args:
74
+ delay: seconds to delay
75
+
76
+ Raises:
77
+ ValueError: if delay is negative
78
+ """
79
+ def __init__(self, delay):
80
+ if not delay >= 0:
81
+ raise ValueError('delay must be > 0, not {}'.format(repr(delay)))
82
+ self.delay = delay
83
+
84
+ def __call__(self):
85
+ if self.delay:
86
+ time.sleep(self.delay)
87
+
88
+ def snapshot(self, update=False):
89
+ """
90
+ Snapshots delay
91
+ Args:
92
+ update (bool): TODO not in use
93
+
94
+ Returns:
95
+ dict: snapshot
96
+ """
97
+ return {'type': 'Wait', 'delay': self.delay}
98
+
99
+
100
+ class _Measure:
101
+ """
102
+ A callable collection of parameters to measure.
103
+
104
+ This should not be constructed manually, only by an ActiveLoop.
105
+ """
106
+ def __init__(self, params_indices, data_set, use_threads):
107
+ self.use_threads = use_threads and len(params_indices) > 1
108
+ # the applicable DataSet.store function
109
+ self.store = data_set.store
110
+
111
+ # for performance, pre-calculate which params return data for
112
+ # multiple arrays, and the name mappings
113
+ self.getters = []
114
+ self.param_ids = []
115
+ self.composite = []
116
+ for param, action_indices in params_indices:
117
+ self.getters.append(param.get)
118
+
119
+ if hasattr(param, 'names'):
120
+ part_ids = []
121
+ for i in range(len(param.names)):
122
+ param_id = data_set.action_id_map[action_indices + (i,)]
123
+ part_ids.append(param_id)
124
+ self.param_ids.append(None)
125
+ self.composite.append(part_ids)
126
+ else:
127
+ param_id = data_set.action_id_map[action_indices]
128
+ self.param_ids.append(param_id)
129
+ self.composite.append(False)
130
+
131
+ def __call__(self, loop_indices, **ignore_kwargs):
132
+ out_dict = {}
133
+ if self.use_threads:
134
+ out = thread_map(self.getters)
135
+ else:
136
+ out = [g() for g in self.getters]
137
+
138
+ for param_out, param_id, composite in zip(out, self.param_ids,
139
+ self.composite):
140
+ if composite:
141
+ for val, part_id in zip(param_out, composite):
142
+ out_dict[part_id] = val
143
+ else:
144
+ out_dict[param_id] = param_out
145
+
146
+ self.store(loop_indices, out_dict)
147
+
148
+
149
+ class _Nest:
150
+
151
+ """
152
+ Wrapper to make a callable nested ActiveLoop.
153
+
154
+ This should not be constructed manually, only by an ActiveLoop.
155
+ """
156
+
157
+ def __init__(self, inner_loop, action_indices):
158
+ self.inner_loop = inner_loop
159
+ self.action_indices = action_indices
160
+
161
+ def __call__(self, **kwargs):
162
+ self.inner_loop._run_loop(action_indices=self.action_indices, **kwargs)
163
+
164
+
165
+ class BreakIf:
166
+
167
+ """
168
+ Loop action that breaks out of the loop if a condition is truthy.
169
+
170
+ Args:
171
+ condition (callable): a callable taking no arguments.
172
+ Can be a simple function that returns truthy when it's time to quit
173
+ May also be constructed by deferred operations on `Parameter`.
174
+ Raises:
175
+ TypeError: if condition is not a callable with no aguments.
176
+
177
+ Examples:
178
+ >>> BreakIf(gates.chan1 >= 3)
179
+ >>> BreakIf(abs(source.I * source.V) >= source.power_limit.get_latest)
180
+ """
181
+
182
+ def __init__(self, condition):
183
+ if not is_function(condition, 0):
184
+ raise TypeError('BreakIf condition must be a callable with '
185
+ 'no arguments')
186
+ self.condition = condition
187
+
188
+ def __call__(self, **ignore_kwargs):
189
+ if self.condition():
190
+ raise _QcodesBreak
191
+
192
+ def snapshot(self, update=False):
193
+ # TODO: make nice reprs for DeferredOperations
194
+ """
195
+ Snapshots breakif action
196
+ Args:
197
+ update (bool): TODO not in use
198
+
199
+ Returns:
200
+ dict: snapshot
201
+
202
+ """
203
+ return {'type': 'BreakIf', 'condition': repr(self.condition)}
204
+
205
+
206
+ class _QcodesBreak(Exception):
207
+ pass
@@ -0,0 +1,2 @@
1
+ from .config import Config
2
+ from .config import logger
@@ -0,0 +1,396 @@
1
+ import copy
2
+ import json
3
+ import jsonschema
4
+ import logging
5
+ import os
6
+ import pkg_resources as pkgr
7
+
8
+ from os.path import expanduser
9
+ from pathlib import Path
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ EMPTY_USER_SCHEMA = "User schema at {} not found." + \
14
+ "User settings won't be validated"
15
+ MISS_DESC = """ Passing a description without a type does not make sense.
16
+ Description is ignored """
17
+
18
+ BASE_SCHEMA = {
19
+ "$schema": "http://json-schema.org/draft-04/schema#",
20
+ "type": "object",
21
+ "description": "schema for a user qcodes config file",
22
+ "properties": {},
23
+ "required": []
24
+ }
25
+
26
+
27
+ class Config():
28
+ """
29
+ QCoDeS config system
30
+
31
+ Start with sane defaults, which you can't change, and
32
+ then customize your experience using files that update the configuration.
33
+
34
+
35
+ Attributes:
36
+ config_file_name(str): Name of config file
37
+ schema_file_name(str): Name of schema file
38
+
39
+ default_file_name(str):Filene name of default config
40
+ schema_default_file_name(str):Filene name of default schema
41
+
42
+ home_file_name(str):Filene name of home config
43
+ schema_home_file_name(str):Filene name of home schema
44
+
45
+ env_file_name(str):Filene name of env config
46
+ schema_env_file_name(str):Filene name of env schema
47
+
48
+ cwd_file_name(str):Filene name of cwd config
49
+ schema_cwd_file_name(str):Filene name of cwd schema
50
+
51
+ current_config(dict): Vaild config values
52
+ current_schema(dict): Validators and desciptions of config values
53
+ current_config_path(path): Path of the currently loaded config
54
+
55
+ """
56
+
57
+ config_file_name = "qcodesrc.json"
58
+ schema_file_name = "qcodesrc_schema.json"
59
+
60
+ # get abs path of packge config file
61
+ default_file_name = pkgr.resource_filename(__name__, config_file_name)
62
+ current_config_path = default_file_name
63
+
64
+ # get abs path of schema file
65
+ schema_default_file_name = pkgr.resource_filename(__name__,
66
+ schema_file_name)
67
+
68
+ # home dir, os independent
69
+ home_file_name = expanduser("~/{}".format(config_file_name))
70
+ schema_home_file_name = home_file_name.replace(config_file_name,
71
+ schema_file_name)
72
+
73
+ # this is for *nix people
74
+ env_file_name = os.environ.get("QCODES_CONFIG", "")
75
+ schema_env_file_name = env_file_name.replace(config_file_name,
76
+ schema_file_name)
77
+ # current working dir
78
+ cwd_file_name = "{}/{}".format(Path.cwd(), config_file_name)
79
+ schema_cwd_file_name = cwd_file_name.replace(config_file_name,
80
+ schema_file_name)
81
+
82
+ current_schema = None
83
+ current_config = None
84
+
85
+ defaults = None
86
+ defaults_schema = None
87
+
88
+ _diff_config = {}
89
+ _diff_schema = {}
90
+
91
+ def __init__(self):
92
+ self.defaults, self.defaults_schema = self.load_default()
93
+ self.current_config = self.update_config()
94
+
95
+ def load_default(self):
96
+ defaults = self.load_config(self.default_file_name)
97
+ defaults_schema = self.load_config(self.schema_default_file_name)
98
+ self.validate(defaults, defaults_schema)
99
+ return defaults, defaults_schema
100
+
101
+ def update_config(self):
102
+ """
103
+ Load defaults and validates.
104
+ A configuration file must be called qcodesrc.json
105
+ A schema file must be called schema.json
106
+ Configuration files (and their schema) are loaded and updated from the
107
+ default directories in the following order:
108
+ - default json config file from the repository
109
+ - user json config in user home directory
110
+ - user json config in $QCODES_CONFIG
111
+ - user json config in current working directory
112
+
113
+ If a key/value is not specified in the user configuration the default
114
+ is used. Configs are validated after every update.
115
+ Validation is also performed against a user provied schema if it's
116
+ found in the directory.
117
+ """
118
+ config = copy.deepcopy(self.defaults)
119
+ self.current_schema = copy.deepcopy(self.defaults_schema)
120
+
121
+ if os.path.isfile(self.home_file_name):
122
+ home_config = self.load_config(self.home_file_name)
123
+ config.update(home_config)
124
+ self.validate(config, self.current_schema,
125
+ self.schema_home_file_name)
126
+
127
+ if os.path.isfile(self.env_file_name):
128
+ env_config = self.load_config(self.env_file_name)
129
+ config.update(env_config)
130
+ self.validate(config, self.current_schema,
131
+ self.schema_env_file_name)
132
+
133
+ if os.path.isfile(self.cwd_file_name):
134
+ cwd_config = self.load_config(self.cwd_file_name)
135
+ config.update(cwd_config)
136
+ self.validate(config, self.current_schema,
137
+ self.schema_cwd_file_name)
138
+
139
+ return config
140
+
141
+ def validate(self, json_config=None, schema=None, extra_schema_path=None):
142
+ """
143
+ Validate configuration, if no arguments are passed, the default
144
+ validators are used.
145
+
146
+ Args:
147
+ json_config (Optiona[string]) : json file to validate
148
+ schema (Optiona[dict]): schema dictionary
149
+ extra_schema_path (Optiona[string]): schema path that contains
150
+ extra validators to be added to schema dictionary
151
+ """
152
+ if extra_schema_path is not None:
153
+ # add custom validation
154
+ if os.path.isfile(extra_schema_path):
155
+ with open(extra_schema_path) as f:
156
+ # user schema has to be both vaild in itself
157
+ # but then just update the user properties
158
+ # so that default types and values can NEVER
159
+ # be overwritten
160
+ new_user = json.load(f)["properties"]["user"]
161
+ user = schema["properties"]['user']
162
+ user["properties"].update(
163
+ new_user["properties"]
164
+ )
165
+ jsonschema.validate(json_config, schema)
166
+ else:
167
+ logger.warning(EMPTY_USER_SCHEMA.format(extra_schema_path))
168
+ else:
169
+ if json_config is None and schema is None:
170
+ jsonschema.validate(self.current_config, self.current_schema)
171
+ else:
172
+ jsonschema.validate(json_config, schema)
173
+
174
+ def add(self, key, value, value_type=None, description=None, default=None):
175
+ """ Add custom config value in place.
176
+ Add key, value with optional value_type to user cofnig and schema.
177
+ If value_type is specified then the new value is validated.
178
+
179
+ Args:
180
+ key(str): key to be added under user config
181
+ value (any): value to add to config
182
+ value_type(Optional(string)): type of value
183
+ allowed are string, boolean, integer
184
+ default (str): default value, stored only in the schema
185
+ description (str): description of key to add to schema
186
+
187
+ Examples:
188
+
189
+ >>> defaults.add("trace_color", "blue", "string", "description")
190
+
191
+ will update the config:
192
+ `...
193
+ "user": { "trace_color": "blue"}
194
+ ...`
195
+ and the schema:
196
+ `...
197
+ "user":{
198
+ "type" : "object",
199
+ "description": "controls user settings of qcodes"
200
+ "properties" : {
201
+ "trace_color": {
202
+ "description" : "description",
203
+ "type": "string"
204
+ }
205
+ }
206
+ }
207
+ ...`
208
+
209
+ Todo:
210
+ - Add enum support for value_type
211
+ - finsh _diffing
212
+ """
213
+ self.current_config["user"].update({key: value})
214
+
215
+ if self._diff_config.get("user", True):
216
+ self._diff_config["user"] = {}
217
+ self._diff_config.get("user").update({key: value})
218
+
219
+ if value_type is None:
220
+ if description is not None:
221
+ logger.warning(MISS_DESC)
222
+ else:
223
+ # update schema!
224
+ schema_entry = {key: {"type": value_type}}
225
+ if description is not None:
226
+ schema_entry = {key: {
227
+ "type": value_type,
228
+ "default": default,
229
+ "description": description}
230
+ }
231
+ # the schema is nested we only update properties of the user object
232
+ user = self.current_schema['properties']["user"]
233
+ user["properties"].update(schema_entry)
234
+ self.validate(self.current_config, self.current_schema)
235
+
236
+ # TODO(giulioungaretti) finish diffing
237
+ # now we update the entire schema
238
+ # and the entire configuration
239
+ # if it's saved then it will always
240
+ # take precedence even if the defaults
241
+ # values are changed upstream, and the local
242
+ # ones were actually left to their default
243
+ # values
244
+ if not self._diff_schema:
245
+ self._diff_schema = BASE_SCHEMA
246
+
247
+ props = self._diff_schema['properties']
248
+ if props.get("user", True):
249
+ props["user"] = {}
250
+ props.get("user").update(schema_entry)
251
+
252
+ def load_config(self, path):
253
+ """ Load a config JSON file
254
+ As a side effect it records which file is loaded
255
+
256
+ Args:
257
+ path(str): path to the config file
258
+ Raises:
259
+ FileNotFoundError: if config is missing
260
+ Return:
261
+ Union[DotDict, None]: a dot accessible config object
262
+ """
263
+ with open(path, "r") as fp:
264
+ config = json.load(fp)
265
+
266
+ config = DotDict(config)
267
+ self.current_config_path = path
268
+ return config
269
+
270
+ def save_config(self, path):
271
+ """ Save to file(s)
272
+ Saves current config to path.
273
+
274
+ Args:
275
+ path (string): path of new file(s)
276
+ """
277
+ with open(path, "w") as fp:
278
+ json.dump(self.current_config, fp, indent=4)
279
+
280
+ def save_schema(self, path):
281
+ """ Save to file(s)
282
+ Saves current schema to path.
283
+
284
+ Args:
285
+ path (string): path of new file(s)
286
+ """
287
+ with open(path, "w") as fp:
288
+ json.dump(self.current_schema, fp, indent=4)
289
+
290
+ def save_to_home(self):
291
+ """ Save files to home dir
292
+ """
293
+ self.save_config(self.home_file_name)
294
+ self.save_schema(self.schema_home_file_name)
295
+
296
+ def save_to_env(self):
297
+ """ Save files to env path
298
+ """
299
+ self.save_config(self.env_file_name)
300
+ self.save_schema(self.schema_env_file_name)
301
+
302
+ def save_to_cwd(self):
303
+ """ Save files to current working dir
304
+ """
305
+ self.save_config(self.cwd_file_name)
306
+ self.save_schema(self.schema_cwd_file_name)
307
+
308
+ def describe(self, name):
309
+ """
310
+ Describe a configuration entry
311
+
312
+ Args:
313
+ name (str): name of entry to describe
314
+ """
315
+ val = self.current_config
316
+ sch = self.current_schema["properties"]
317
+ for key in name.split('.'):
318
+ val = val[key]
319
+ if sch.get(key):
320
+ sch = sch[key]
321
+ else:
322
+ sch = sch['properties'][key]
323
+ description = sch.get("description", None) or "Generic value"
324
+ _type = str(sch.get("type", None)) or "Not defined"
325
+ default = sch.get("default", None) or "Not defined"
326
+
327
+ # add cool description to docstring
328
+ base_docstring = """{}.\nCurrent value: {}. Type: {}. Default: {}."""
329
+
330
+ doc = base_docstring.format(
331
+ description,
332
+ val,
333
+ _type,
334
+ default
335
+ )
336
+
337
+ return doc
338
+
339
+ def __getitem__(self, name):
340
+ val = self.current_config
341
+ for key in name.split('.'):
342
+ val = val[key]
343
+ return val
344
+
345
+ def __getattr__(self, name):
346
+ return getattr(self.current_config, name)
347
+
348
+ def __repr__(self):
349
+ old = super().__repr__()
350
+ base = """Current values: \n {} \n Current path: \n {} \n {}"""
351
+ return base.format(self.current_config,
352
+ self.current_config_path,
353
+ old)
354
+
355
+
356
+ class DotDict(dict):
357
+ """
358
+ Wrapper dict that allows to get dotted attributes
359
+ """
360
+ def __init__(self, value=None):
361
+ if value is None:
362
+ pass
363
+ else:
364
+ for key in value:
365
+ self.__setitem__(key, value[key])
366
+
367
+ def __setitem__(self, key, value):
368
+ if '.' in key:
369
+ myKey, restOfKey = key.split('.', 1)
370
+ target = self.setdefault(myKey, DotDict())
371
+ target[restOfKey] = value
372
+ else:
373
+ if isinstance(value, dict) and not isinstance(value, DotDict):
374
+ value = DotDict(value)
375
+ dict.__setitem__(self, key, value)
376
+
377
+ def __getitem__(self, key):
378
+ if '.' not in key:
379
+ return dict.__getitem__(self, key)
380
+ myKey, restOfKey = key.split('.', 1)
381
+ target = dict.__getitem__(self, myKey)
382
+ return target[restOfKey]
383
+
384
+ def __contains__(self, key):
385
+ if '.' not in key:
386
+ return dict.__contains__(self, key)
387
+ myKey, restOfKey = key.split('.', 1)
388
+ target = dict.__getitem__(self, myKey)
389
+ return restOfKey in target
390
+
391
+ def __deepcopy__(self, memo):
392
+ return DotDict(copy.deepcopy(dict(self)))
393
+
394
+ # dot acces baby
395
+ __setattr__ = __setitem__
396
+ __getattr__ = __getitem__
@@ -0,0 +1,11 @@
1
+ {
2
+ "core":{
3
+ "legacy_mp": false,
4
+ "loglevel": "DEBUG"
5
+ },
6
+ "gui" :{
7
+ "notebook": true,
8
+ "plotlib": "matplotlib"
9
+ },
10
+ "user": {}
11
+ }