diff options
author | Sebastian Koslowski <sebastian.koslowski@gmail.com> | 2017-11-08 21:28:35 +0100 |
---|---|---|
committer | Sebastian Koslowski <sebastian.koslowski@gmail.com> | 2017-11-09 19:26:43 +0100 |
commit | 14d79b777e773e299a1ffa0dd12d2508a46370a0 (patch) | |
tree | 7fd696fbf2435f5b2d672332d4f8253611726e8f | |
parent | cd9c3479e17fbdb84918e255cf6de74edf0ceab1 (diff) |
grc: move some of the param checkers to separate file
-rw-r--r-- | grc/core/Constants.py | 20 | ||||
-rw-r--r-- | grc/core/params/dtypes.py | 103 | ||||
-rw-r--r-- | grc/core/params/param.py | 154 | ||||
-rw-r--r-- | grc/core/utils/descriptors/evaluated.py | 7 | ||||
-rw-r--r-- | grc/gui/Utils.py | 4 | ||||
-rw-r--r-- | grc/gui/canvas/param.py | 4 |
6 files changed, 161 insertions, 131 deletions
diff --git a/grc/core/Constants.py b/grc/core/Constants.py index 59c5dff35e..8ed8899c70 100644 --- a/grc/core/Constants.py +++ b/grc/core/Constants.py @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import os +import numbers import stat import numpy @@ -54,7 +55,7 @@ TOP_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH -PARAM_TYPE_NAMES = ( +PARAM_TYPE_NAMES = { 'raw', 'enum', 'complex', 'real', 'float', 'int', 'complex_vector', 'real_vector', 'float_vector', 'int_vector', @@ -63,18 +64,17 @@ PARAM_TYPE_NAMES = ( 'id', 'stream_id', 'gui_hint', 'import', -) +} + +PARAM_TYPE_MAP = { + 'complex': numbers.Complex, + 'float': numbers.Real, + 'real': numbers.Real, + 'int': numbers.Integral, +} # Define types, native python + numpy VECTOR_TYPES = (tuple, list, set, numpy.ndarray) -COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128] -REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64] -INT_TYPES = [int, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64, - numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64] -# Cast to tuple for isinstance, concat subtypes -COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES) -REAL_TYPES = tuple(REAL_TYPES + INT_TYPES) -INT_TYPES = tuple(INT_TYPES) # Updating colors. Using the standard color palette from: # http://www.google.com/design/spec/style/color.html#color-color-palette diff --git a/grc/core/params/dtypes.py b/grc/core/params/dtypes.py new file mode 100644 index 0000000000..cddfdefe45 --- /dev/null +++ b/grc/core/params/dtypes.py @@ -0,0 +1,103 @@ +# Copyright 2008-2017 Free Software Foundation, Inc. +# This file is part of GNU Radio +# +# GNU Radio Companion is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# GNU Radio Companion is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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 re + +from six.moves import builtins + +from .. import blocks +from .. import Constants + + +# 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, AttributeError): + pass + + +validators = {} + + +def validates(*dtypes): + def decorator(func): + for dtype in dtypes: + assert dtype in Constants.PARAM_TYPE_NAMES + validators[dtype] = func + return func + return decorator + + +class ValidateError(Exception): + """Raised by validate functions""" + + +@validates('id') +def validate_block_id(param): + value = param.value + # Can python use this as a variable? + if not re.match(r'^[a-z|A-Z]\w*$', value): + raise ValidateError('ID "{}" must begin with a letter and may contain letters, numbers, ' + 'and underscores.'.format(value)) + if value in ID_BLACKLIST: + raise ValidateError('ID "{}" is blacklisted.'.format(value)) + block_names = [block.name for block in param.parent_flowgraph.iter_enabled_blocks()] + # Id should only appear once, or zero times if block is disabled + if param.key == 'id' and block_names.count(value) > 1: + raise ValidateError('ID "{}" is not unique.'.format(value)) + elif value not in block_names: + raise ValidateError('ID "{}" does not exist.'.format(value)) + return value + + +@validates('stream_id') +def validate_stream_id(param): + value = param.value + stream_ids = [ + block.params['stream_id'].value + for block in param.parent_flowgraph.iter_enabled_blocks() + if isinstance(block, blocks.VirtualSink) + ] + # Check that the virtual sink's stream id is unique + if isinstance(param.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2: + # Id should only appear once, or zero times if block is disabled + raise ValidateError('Stream ID "{}" is not unique.'.format(value)) + # Check that the virtual source's steam id is found + elif isinstance(param.parent_block, blocks.VirtualSource) and value not in stream_ids: + raise ValidateError('Stream ID "{}" is not found.'.format(value)) + + +@validates('complex', 'real', 'float', 'int') +def validate_scalar(param): + valid_types = Constants.PARAM_TYPE_MAP[param.dtype] + if not isinstance(param.value, valid_types): + raise ValidateError('Expression {!r} is invalid for type {!r}.'.format( + param.value, param.dtype)) + + +@validates('complex_vector', 'real_vector', 'float_vector', 'int_vector') +def validate_vector(param): + # todo: check vector types + + valid_types = Constants.PARAM_TYPE_MAP[param.dtype.split('_', 1)[0]] + if not all(isinstance(item, valid_types) for item in param.value): + raise ValidateError('Expression {!r} is invalid for type {!r}.'.format( + param.value, param.dtype)) diff --git a/grc/core/params/param.py b/grc/core/params/param.py index 787be9a19f..30a48bb434 100644 --- a/grc/core/params/param.py +++ b/grc/core/params/param.py @@ -18,28 +18,19 @@ from __future__ import absolute_import import ast -import numbers -import re import collections import textwrap import six -from six.moves import builtins, range +from six.moves import range -from .. import Constants, blocks +from .. import Constants from ..base import Element from ..utils.descriptors import Evaluated, EvaluatedEnum, setup_names +from . import dtypes from .template_arg import TemplateArg -# 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, AttributeError): - pass - @setup_names class Param(Element): @@ -147,6 +138,10 @@ class Param(Element): except Exception as e: self.add_error_message(str(e)) + rewriter = getattr(dtypes, 'rewrite_' + self.dtype, None) + if rewriter: + rewriter(self) + def validate(self): """ Validate the param. @@ -156,6 +151,13 @@ class Param(Element): if self.dtype not in Constants.PARAM_TYPE_NAMES: self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) + validator = dtypes.validators.get(self.dtype, None) + if self._init and validator: + try: + validator(self) + except dtypes.ValidateError as e: + self.add_error_message(e.message) + def get_evaluated(self): return self._evaluated @@ -173,68 +175,41 @@ class Param(Element): expr = self.get_value() ######################### - # Enum Type + # ID and Enum types (not evaled) ######################### - if self.is_enum(): + if dtype in ('id', 'stream_id') or self.is_enum(): return expr ######################### # Numeric Types ######################### elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): - # Raise exception if python cannot evaluate this value - try: - 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 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 + if expr: + try: + value = self.parent_flowgraph.evaluate(expr) + except Exception as e: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, e)) else: - raise TypeError('Type "{}" not handled'.format(dtype)) + value = 0 + if dtype == 'hex': + value = hex(value) + elif dtype == 'bool': + value = bool(value) + return value + ######################### # Numeric Vector Types ######################### 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 - + return [] # Turn a blank string into an empty list, so it will eval try: 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 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 @@ -242,7 +217,7 @@ class Param(Element): 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: - value = self.parent.parent.evaluate(expr) + value = self.parent_flowgraph.evaluate(expr) if not isinstance(value, str): raise Exception() except: @@ -252,28 +227,10 @@ class Param(Element): ast.parse(value) # Raises SyntaxError return value ######################### - # Unique ID Type - ######################### - elif dtype == 'id': - self.validate_block_id() - return expr - - ######################### - # Stream ID Type - ######################### - elif dtype == 'stream_id': - self.validate_stream_id() - return expr - - ######################### # GUI Position/Hint ######################### elif dtype == 'gui_hint': - if self.parent_block.state == 'disabled': - return '' - else: - return self.parse_gui_hint(expr) - + return self.parse_gui_hint(expr) if self.parent_block.state == 'enabled' else '' ######################### # Import Type ######################### @@ -292,37 +249,6 @@ class Param(Element): else: 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): """ Convert the value to code. @@ -333,24 +259,20 @@ class Param(Element): a string representing the code """ self._init = True - v = self.get_value() - t = self.dtype + value = self.get_value() # String types - if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): + if self.dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): if not self._init: self.evaluate() - return repr(v) if self._stringify_flag else v + return repr(value) if self._stringify_flag else value # Vector types - elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): + elif self.dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): if not self._init: self.evaluate() - if self._lisitify_flag: - return '(%s, )' % v - else: - return '(%s)' % v + return '[' + value + ']' if self._lisitify_flag else value else: - return v + return value def get_opt(self, item): return self.options.attributes[self.get_value()][item] @@ -470,7 +392,7 @@ class Param(Element): optionally a given key Args: - type: the specified type + dtype: the specified type key: the key to match against Returns: diff --git a/grc/core/utils/descriptors/evaluated.py b/grc/core/utils/descriptors/evaluated.py index e8bce6e6ed..0e1b68761c 100644 --- a/grc/core/utils/descriptors/evaluated.py +++ b/grc/core/utils/descriptors/evaluated.py @@ -79,9 +79,10 @@ class Evaluated(object): class EvaluatedEnum(Evaluated): def __init__(self, allowed_values, default=None, name=None): - self.allowed_values = allowed_values if isinstance(allowed_values, (list, tuple)) else \ - allowed_values.split() - default = default if default is not None else self.allowed_values[0] + if isinstance(allowed_values, six.string_types): + allowed_values = set(allowed_values.split()) + self.allowed_values = allowed_values + default = default if default is not None else next(iter(self.allowed_values)) super(EvaluatedEnum, self).__init__(str, default, name) def default_eval_func(self, instance): diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index f47c2e6b97..1b32e91439 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -19,6 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import +import numbers + from gi.repository import GLib import cairo import six @@ -91,7 +93,7 @@ def num_to_str(num): return template.format(value / factor, symbol.strip()) return template.format(value, '') - if isinstance(num, Constants.COMPLEX_TYPES): + if isinstance(num, numbers.Complex): num = complex(num) # Cast to python complex if num == 0: return '0' diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py index 845ff5a926..5777423c68 100644 --- a/grc/gui/canvas/param.py +++ b/grc/gui/canvas/param.py @@ -17,6 +17,8 @@ from __future__ import absolute_import +import numbers + from .drawable import Drawable from .. import ParamWidgets, Utils, Constants from ...core.params import Param as CoreParam @@ -126,7 +128,7 @@ class Param(CoreParam): t = self.dtype if isinstance(e, bool): return str(e) - elif isinstance(e, Constants.COMPLEX_TYPES): + elif isinstance(e, numbers.Complex): dt_str = Utils.num_to_str(e) elif isinstance(e, Constants.VECTOR_TYPES): # Vector types |