diff options
Diffstat (limited to 'grc/core/Param.py')
-rw-r--r-- | grc/core/Param.py | 709 |
1 files changed, 214 insertions, 495 deletions
diff --git a/grc/core/Param.py b/grc/core/Param.py index 2077925879..a1e4c782fb 100644 --- a/grc/core/Param.py +++ b/grc/core/Param.py @@ -17,111 +17,30 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ +from __future__ import absolute_import + import ast -import weakref +import numbers import re +import collections -from . import Constants -from .Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES -from .Element import Element -from .utils import odict - -# Blacklist certain ids, its not complete, but should help -import __builtin__ +import six +from six.moves import builtins, filter, map, range, zip +from . import Constants, blocks +from .base import Element +from .utils.descriptors import Evaluated, EvaluatedEnum, setup_names -ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(__builtin__) +# Blacklist certain ids, its not complete, but should help +ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(builtins) try: from gnuradio import gr ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_')) -except ImportError: +except (ImportError, AttributeError): pass -_check_id_matcher = re.compile('^[a-z|A-Z]\w*$') -_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$') - - -def _get_keys(lst): - return [elem.get_key() for elem in lst] - - -def _get_elem(lst, key): - try: - return lst[_get_keys(lst).index(key)] - except ValueError: - raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst))) - - -def num_to_str(num): - """ Display logic for numbers """ - def eng_notation(value, fmt='g'): - """Convert a number to a string in engineering notation. E.g., 5e-9 -> 5n""" - template = '{:' + fmt + '}{}' - magnitude = abs(value) - for exp, symbol in zip(range(9, -15-1, -3), 'GMk munpf'): - factor = 10 ** exp - if magnitude >= factor: - return template.format(value / factor, symbol.strip()) - return template.format(value, '') - - if isinstance(num, COMPLEX_TYPES): - num = complex(num) # Cast to python complex - if num == 0: - return '0' - output = eng_notation(num.real) if num.real else '' - output += eng_notation(num.imag, '+g' if output else 'g') + 'j' if num.imag else '' - return output - else: - return str(num) - - -class Option(Element): - - def __init__(self, param, n): - Element.__init__(self, param) - self._name = n.find('name') - self._key = n.find('key') - self._opts = dict() - opts = n.findall('opt') - # Test against opts when non enum - if not self.get_parent().is_enum() and opts: - raise Exception('Options for non-enum types cannot have sub-options') - # Extract opts - for opt in opts: - # Separate the key:value - try: - key, value = opt.split(':') - except: - raise Exception('Error separating "{}" into key:value'.format(opt)) - # Test against repeated keys - if key in self._opts: - raise Exception('Key "{}" already exists in option'.format(key)) - # Store the option - self._opts[key] = value - - def __str__(self): - return 'Option {}({})'.format(self.get_name(), self.get_key()) - - def get_name(self): - return self._name - - def get_key(self): - return self._key - - ############################################## - # Access Opts - ############################################## - def get_opt_keys(self): - return self._opts.keys() - - def get_opt(self, key): - return self._opts[key] - - def get_opts(self): - return self._opts.values() - -class TemplateArg(object): +class TemplateArg(str): """ A cheetah template argument created from a param. The str of this class evaluates to the param's to code method. @@ -129,8 +48,11 @@ class TemplateArg(object): The __call__ or () method can return the param evaluated to a raw python data type. """ - def __init__(self, param): - self._param = weakref.proxy(param) + def __new__(cls, param): + value = param.to_code() + instance = str.__new__(cls, value) + setattr(instance, '_param', param) + return instance def __getitem__(self, item): return str(self._param.get_opt(item)) if self._param.is_enum() else NotImplemented @@ -150,217 +72,110 @@ class TemplateArg(object): return self._param.get_evaluated() +@setup_names class Param(Element): is_param = True - def __init__(self, block, n): - """ - Make a new param from nested data. + name = Evaluated(str, default='no name') + dtype = EvaluatedEnum(Constants.PARAM_TYPE_NAMES, default='raw') + hide = EvaluatedEnum('none all part') + + # region init + def __init__(self, parent, id, label='', dtype='raw', default='', + options=None, option_labels=None, option_attributes=None, + category='', hide='none', **_): + """Make a new param from nested data""" + super(Param, self).__init__(parent) + self.key = id + self.name = label.strip() or id.title() + self.category = category or Constants.DEFAULT_PARAM_TAB + + self.dtype = dtype + self.value = self.default = str(default) + + self.options = self._init_options(options or [], option_labels or [], + option_attributes or {}) + self.hide = hide or 'none' + # end of args ######################################################## - Args: - block: the parent element - n: the nested odict - """ - # If the base key is a valid param key, copy its data and overlay this params data - base_key = n.find('base_key') - if base_key and base_key in block.get_param_keys(): - n_expanded = block.get_param(base_key)._n.copy() - n_expanded.update(n) - n = n_expanded - # Save odict in case this param will be base for another - self._n = n - # Parse the data - self._name = n.find('name') - self._key = n.find('key') - value = n.find('value') or '' - self._type = n.find('type') or 'raw' - self._hide = n.find('hide') or '' - self._tab_label = n.find('tab') or block.get_param_tab_labels()[0] - if self._tab_label not in block.get_param_tab_labels(): - block.get_param_tab_labels().append(self._tab_label) - # Build the param - Element.__init__(self, block) - # Create the Option objects from the n data - self._options = list() self._evaluated = None - for option in map(lambda o: Option(param=self, n=o), n.findall('option')): - key = option.get_key() - # Test against repeated keys - if key in self.get_option_keys(): - raise Exception('Key "{}" already exists in options'.format(key)) - # Store the option - self.get_options().append(option) - # Test the enum options - if self.is_enum(): - # Test against options with identical keys - if len(set(self.get_option_keys())) != len(self.get_options()): - raise Exception('Options keys "{}" are not unique.'.format(self.get_option_keys())) - # Test against inconsistent keys in options - opt_keys = self.get_options()[0].get_opt_keys() - for option in self.get_options(): - if set(opt_keys) != set(option.get_opt_keys()): - raise Exception('Opt keys "{}" are not identical across all options.'.format(opt_keys)) - # If a value is specified, it must be in the options keys - if value or value in self.get_option_keys(): - self._value = value - else: - self._value = self.get_option_keys()[0] - if self.get_value() not in self.get_option_keys(): - raise Exception('The value "{}" is not in the possible values of "{}".'.format(self.get_value(), self.get_option_keys())) - else: - self._value = value or '' - self._default = value + self._stringify_flag = False + self._lisitify_flag = False self._init = False - self._hostage_cells = list() - self.template_arg = TemplateArg(self) - - def get_types(self): - return ( - 'raw', 'enum', - 'complex', 'real', 'float', 'int', - 'complex_vector', 'real_vector', 'float_vector', 'int_vector', - 'hex', 'string', 'bool', - 'file_open', 'file_save', '_multiline', '_multiline_python_external', - 'id', 'stream_id', - 'gui_hint', - 'import', - ) - def __repr__(self): - """ - Get the repr (nice string format) for this param. + @property + def template_arg(self): + return TemplateArg(self) - Returns: - the string representation - """ - ################################################## - # Truncate helper method - ################################################## - def _truncate(string, style=0): - max_len = max(27 - len(self.get_name()), 3) - if len(string) > max_len: - if style < 0: # Front truncate - string = '...' + string[3-max_len:] - elif style == 0: # Center truncate - string = string[:max_len/2 - 3] + '...' + string[-max_len/2:] - elif style > 0: # Rear truncate - string = string[:max_len-3] + '...' - return string - - ################################################## - # Simple conditions - ################################################## - if not self.is_valid(): - return _truncate(self.get_value()) - if self.get_value() in self.get_option_keys(): - return self.get_option(self.get_value()).get_name() - - ################################################## - # Split up formatting by type - ################################################## - # Default center truncate - truncate = 0 - e = self.get_evaluated() - t = self.get_type() - if isinstance(e, bool): - return str(e) - elif isinstance(e, COMPLEX_TYPES): - dt_str = num_to_str(e) - elif isinstance(e, VECTOR_TYPES): - # Vector types - if len(e) > 8: - # Large vectors use code - dt_str = self.get_value() - truncate = 1 - else: - # Small vectors use eval - dt_str = ', '.join(map(num_to_str, e)) - elif t in ('file_open', 'file_save'): - dt_str = self.get_value() - truncate = -1 - else: - # Other types - dt_str = str(e) + def _init_options(self, values, labels, attributes): + """parse option and option attributes""" + options = collections.OrderedDict() + options.attributes = collections.defaultdict(dict) - # Done - return _truncate(dt_str, truncate) + padding = [''] * max(len(values), len(labels)) + attributes = {key: value + padding for key, value in six.iteritems(attributes)} - def __repr2__(self): - """ - Get the repr (nice string format) for this param. + for i, option in enumerate(values): + # Test against repeated keys + if option in options: + raise KeyError('Value "{}" already exists in options'.format(option)) + # get label + try: + label = str(labels[i]) + except IndexError: + label = str(option) + # Store the option + options[option] = label + options.attributes[option] = {attrib: values[i] for attrib, values in six.iteritems(attributes)} - Returns: - the string representation - """ - if self.is_enum(): - return self.get_option(self.get_value()).get_name() - return self.get_value() + default = next(iter(options)) if options else '' + if not self.value: + self.value = self.default = default + + if self.is_enum() and self.value not in options: + self.value = self.default = default # TODO: warn + # raise ValueError('The value {!r} is not in the possible values of {}.' + # ''.format(self.get_value(), ', '.join(self.options))) + return options + # endregion def __str__(self): - return 'Param - {}({})'.format(self.get_name(), self.get_key()) + return 'Param - {}({})'.format(self.name, self.key) - def get_color(self): - """ - Get the color that represents this param's type. + def __repr__(self): + return '{!r}.param[{}]'.format(self.parent, self.key) - Returns: - a hex color code. - """ - try: - return { - # Number types - 'complex': Constants.COMPLEX_COLOR_SPEC, - 'real': Constants.FLOAT_COLOR_SPEC, - 'float': Constants.FLOAT_COLOR_SPEC, - 'int': Constants.INT_COLOR_SPEC, - # Vector types - 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC, - 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC, - 'float_vector': Constants.FLOAT_VECTOR_COLOR_SPEC, - 'int_vector': Constants.INT_VECTOR_COLOR_SPEC, - # Special - 'bool': Constants.INT_COLOR_SPEC, - 'hex': Constants.INT_COLOR_SPEC, - 'string': Constants.BYTE_VECTOR_COLOR_SPEC, - 'id': Constants.ID_COLOR_SPEC, - 'stream_id': Constants.ID_COLOR_SPEC, - 'raw': Constants.WILDCARD_COLOR_SPEC, - }[self.get_type()] - except: - return '#FFFFFF' - - def get_hide(self): - """ - Get the hide value from the base class. - Hide the ID parameter for most blocks. Exceptions below. - If the parameter controls a port type, vlen, or nports, return part. - If the parameter is an empty grid position, return part. - These parameters are redundant to display in the flow graph view. + def is_enum(self): + return self.get_raw('dtype') == 'enum' - Returns: - hide the hide property string - """ - hide = self.get_parent().resolve_dependencies(self._hide).strip() - if hide: - return hide - # Hide ID in non variable blocks - if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()): - return 'part' - # Hide port controllers for type and nports - if self.get_key() in ' '.join(map(lambda p: ' '.join([p._type, p._nports]), - self.get_parent().get_ports())): - return 'part' - # Hide port controllers for vlen, when == 1 - if self.get_key() in ' '.join(map( - lambda p: p._vlen, self.get_parent().get_ports()) - ): - try: - if int(self.get_evaluated()) == 1: - return 'part' - except: - pass - return hide + def get_value(self): + value = self.value + if self.is_enum() and value not in self.options: + value = self.default + self.set_value(value) + return value + + def set_value(self, value): + # Must be a string + self.value = str(value) + + def set_default(self, value): + if self.default == self.value: + self.set_value(value) + self.default = str(value) + + def rewrite(self): + Element.rewrite(self) + del self.name + del self.dtype + del self.hide + + self._evaluated = None + try: + self._evaluated = self.evaluate() + except Exception as e: + self.add_error_message(str(e)) def validate(self): """ @@ -368,14 +183,8 @@ class Param(Element): The value must be evaluated and type must a possible type. """ Element.validate(self) - if self.get_type() not in self.get_types(): - self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type())) - - self._evaluated = None - try: - self._evaluated = self.evaluate() - except Exception, e: - self.add_error_message(str(e)) + if self.dtype not in Constants.PARAM_TYPE_NAMES: + self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) def get_evaluated(self): return self._evaluated @@ -390,150 +199,112 @@ class Param(Element): self._init = True self._lisitify_flag = False self._stringify_flag = False - self._hostage_cells = list() - t = self.get_type() - v = self.get_value() + dtype = self.dtype + expr = self.get_value() ######################### # Enum Type ######################### if self.is_enum(): - return v + return expr ######################### # Numeric Types ######################### - elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): + elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): # Raise exception if python cannot evaluate this value try: - e = self.get_parent().get_parent().evaluate(v) - except Exception, e: - raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e)) + value = self.parent_flowgraph.evaluate(expr) + except Exception as value: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) # Raise an exception if the data is invalid - if t == 'raw': - return e - elif t == 'complex': - if not isinstance(e, COMPLEX_TYPES): - raise Exception('Expression "{}" is invalid for type complex.'.format(str(e))) - return e - elif t == 'real' or t == 'float': - if not isinstance(e, REAL_TYPES): - raise Exception('Expression "{}" is invalid for type float.'.format(str(e))) - return e - elif t == 'int': - if not isinstance(e, INT_TYPES): - raise Exception('Expression "{}" is invalid for type integer.'.format(str(e))) - return e - elif t == 'hex': - return hex(e) - elif t == 'bool': - if not isinstance(e, bool): - raise Exception('Expression "{}" is invalid for type bool.'.format(str(e))) - return e + if dtype == 'raw': + return value + elif dtype == 'complex': + if not isinstance(value, Constants.COMPLEX_TYPES): + raise Exception('Expression "{}" is invalid for type complex.'.format(str(value))) + return value + elif dtype in ('real', 'float'): + if not isinstance(value, Constants.REAL_TYPES): + raise Exception('Expression "{}" is invalid for type float.'.format(str(value))) + return value + elif dtype == 'int': + if not isinstance(value, Constants.INT_TYPES): + raise Exception('Expression "{}" is invalid for type integer.'.format(str(value))) + return value + elif dtype == 'hex': + return hex(value) + elif dtype == 'bool': + if not isinstance(value, bool): + raise Exception('Expression "{}" is invalid for type bool.'.format(str(value))) + return value else: - raise TypeError('Type "{}" not handled'.format(t)) + raise TypeError('Type "{}" not handled'.format(dtype)) ######################### # Numeric Vector Types ######################### - elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): - if not v: - # Turn a blank string into an empty list, so it will eval - v = '()' - # Raise exception if python cannot evaluate this value + elif dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): + default = [] + + if not expr: + return default # Turn a blank string into an empty list, so it will eval + try: - e = self.get_parent().get_parent().evaluate(v) - except Exception, e: - raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e)) + value = self.parent.parent.evaluate(expr) + except Exception as value: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) + + if not isinstance(value, Constants.VECTOR_TYPES): + self._lisitify_flag = True + value = [value] + # Raise an exception if the data is invalid - if t == 'complex_vector': - if not isinstance(e, VECTOR_TYPES): - self._lisitify_flag = True - e = [e] - if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]): - raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e))) - return e - elif t == 'real_vector' or t == 'float_vector': - if not isinstance(e, VECTOR_TYPES): - self._lisitify_flag = True - e = [e] - if not all([isinstance(ei, REAL_TYPES) for ei in e]): - raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e))) - return e - elif t == 'int_vector': - if not isinstance(e, VECTOR_TYPES): - self._lisitify_flag = True - e = [e] - if not all([isinstance(ei, INT_TYPES) for ei in e]): - raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e))) - return e + if dtype == 'complex_vector' and not all(isinstance(item, numbers.Complex) for item in value): + raise Exception('Expression "{}" is invalid for type complex vector.'.format(value)) + elif dtype in ('real_vector', 'float_vector') and not all(isinstance(item, numbers.Real) for item in value): + raise Exception('Expression "{}" is invalid for type float vector.'.format(value)) + elif dtype == 'int_vector' and not all(isinstance(item, Constants.INT_TYPES) for item in value): + raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(value))) + return value ######################### # String Types ######################### - elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): + elif dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): # Do not check if file/directory exists, that is a runtime issue try: - e = self.get_parent().get_parent().evaluate(v) - if not isinstance(e, str): + value = self.parent.parent.evaluate(expr) + if not isinstance(value, str): raise Exception() except: self._stringify_flag = True - e = str(v) - if t == '_multiline_python_external': - ast.parse(e) # Raises SyntaxError - return e + value = str(expr) + if dtype == '_multiline_python_external': + ast.parse(value) # Raises SyntaxError + return value ######################### # Unique ID Type ######################### - elif t == 'id': - # Can python use this as a variable? - if not _check_id_matcher.match(v): - raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, and underscores.'.format(v)) - ids = [param.get_value() for param in self.get_all_params(t, 'id')] - - if v in ID_BLACKLIST: - raise Exception('ID "{}" is blacklisted.'.format(v)) - - if self._key == 'id': - # Id should only appear once, or zero times if block is disabled - if ids.count(v) > 1: - raise Exception('ID "{}" is not unique.'.format(v)) - else: - # Id should exist to be a reference - if ids.count(v) < 1: - raise Exception('ID "{}" does not exist.'.format(v)) - - return v + elif dtype == 'id': + self.validate_block_id() + return expr ######################### # Stream ID Type ######################### - elif t == 'stream_id': - # Get a list of all stream ids used in the virtual sinks - ids = [param.get_value() for param in filter( - lambda p: p.get_parent().is_virtual_sink(), - self.get_all_params(t), - )] - # Check that the virtual sink's stream id is unique - if self.get_parent().is_virtual_sink(): - # Id should only appear once, or zero times if block is disabled - if ids.count(v) > 1: - raise Exception('Stream ID "{}" is not unique.'.format(v)) - # Check that the virtual source's steam id is found - if self.get_parent().is_virtual_source(): - if v not in ids: - raise Exception('Stream ID "{}" is not found.'.format(v)) - return v + elif dtype == 'stream_id': + self.validate_stream_id() + return expr ######################### # GUI Position/Hint ######################### - elif t == 'gui_hint': - if ':' in v: - tab, pos = v.split(':') - elif '@' in v: - tab, pos = v, '' + elif dtype == 'gui_hint': + if ':' in expr: + tab, pos = expr.split(':') + elif '@' in expr: + tab, pos = expr, '' else: - tab, pos = '', v + tab, pos = '', expr if '@' in tab: tab, index = tab.split('@') @@ -563,20 +334,51 @@ class Param(Element): ######################### # Import Type ######################### - elif t == 'import': + elif dtype == 'import': # New namespace n = dict() try: - exec v in n + exec(expr, n) except ImportError: - raise Exception('Import "{}" failed.'.format(v)) + raise Exception('Import "{}" failed.'.format(expr)) except Exception: - raise Exception('Bad import syntax: "{}".'.format(v)) - return filter(lambda k: str(k) != '__builtins__', n.keys()) + raise Exception('Bad import syntax: "{}".'.format(expr)) + return [k for k in list(n.keys()) if str(k) != '__builtins__'] ######################### else: - raise TypeError('Type "{}" not handled'.format(t)) + raise TypeError('Type "{}" not handled'.format(dtype)) + + def validate_block_id(self): + value = self.value + # Can python use this as a variable? + if not re.match(r'^[a-z|A-Z]\w*$', value): + raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, ' + 'and underscores.'.format(value)) + if value in ID_BLACKLIST: + raise Exception('ID "{}" is blacklisted.'.format(value)) + block_names = [block.name for block in self.parent_flowgraph.iter_enabled_blocks()] + # Id should only appear once, or zero times if block is disabled + if self.key == 'id' and block_names.count(value) > 1: + raise Exception('ID "{}" is not unique.'.format(value)) + elif value not in block_names: + raise Exception('ID "{}" does not exist.'.format(value)) + return value + + def validate_stream_id(self): + value = self.value + stream_ids = [ + block.params['stream_id'].value + for block in self.parent_flowgraph.iter_enabled_blocks() + if isinstance(block, blocks.VirtualSink) + ] + # Check that the virtual sink's stream id is unique + if isinstance(self.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2: + # Id should only appear once, or zero times if block is disabled + raise Exception('Stream ID "{}" is not unique.'.format(value)) + # Check that the virtual source's steam id is found + elif isinstance(self.parent_block, blocks.VirtualSource) and value not in stream_ids: + raise Exception('Stream ID "{}" is not found.'.format(value)) def to_code(self): """ @@ -587,8 +389,9 @@ class Param(Element): Returns: a string representing the code """ + self._init = True v = self.get_value() - t = self.get_type() + t = self.dtype # String types if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): if not self._init: @@ -605,87 +408,3 @@ class Param(Element): return '(%s)' % v else: return v - - def get_all_params(self, type, key=None): - """ - Get all the params from the flowgraph that have the given type and - optionally a given key - - Args: - type: the specified type - key: the key to match against - - Returns: - a list of params - """ - return sum([filter(lambda p: ((p.get_type() == type) and ((key is None) or (p.get_key() == key))), block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], []) - - def is_enum(self): - return self._type == 'enum' - - def get_value(self): - value = self._value - if self.is_enum() and value not in self.get_option_keys(): - value = self.get_option_keys()[0] - self.set_value(value) - return value - - def set_value(self, value): - # Must be a string - self._value = str(value) - - def set_default(self, value): - if self._default == self._value: - self.set_value(value) - self._default = str(value) - - def get_type(self): - return self.get_parent().resolve_dependencies(self._type) - - def get_tab_label(self): - return self._tab_label - - def get_name(self): - return self.get_parent().resolve_dependencies(self._name).strip() - - def get_key(self): - return self._key - - ############################################## - # Access Options - ############################################## - def get_option_keys(self): - return _get_keys(self.get_options()) - - def get_option(self, key): - return _get_elem(self.get_options(), key) - - def get_options(self): - return self._options - - ############################################## - # Access Opts - ############################################## - def get_opt_keys(self): - return self.get_option(self.get_value()).get_opt_keys() - - def get_opt(self, key): - return self.get_option(self.get_value()).get_opt(key) - - def get_opts(self): - return self.get_option(self.get_value()).get_opts() - - ############################################## - # Import/Export Methods - ############################################## - def export_data(self): - """ - Export this param's key/value. - - Returns: - a nested data odict - """ - n = odict() - n['key'] = self.get_key() - n['value'] = self.get_value() - return n |