Oleksandr Kravchuk 43e6a43e3d python: Remove unnecessary 'from __future__ import'
All of the removed `from __future__ import` were needed in older
versions of Python (mostly 2.5.x and below) but later became mandatory
in most versions of Python 3 hence are not necessary anymore.

More specifically, according to __future__.py[1]:
- unicode_literals is part of Python since versions 2.6.0 and 3.0.0;
- print_function is part of Python since versions 2.6.0 and 3.0.0;
- absolute_import is part of Python since versions 2.5.0 and 3.0.0;
- division is part of Python since versions 2.2.0 and 3.0.0;

Get rid of those unnecessary imports to slightly clean up the codebase.

[1] https://github.com/python/cpython/blob/master/Lib/__future__.py
2020-08-03 11:40:27 +02:00

442 lines
15 KiB
Python

# Copyright 2008-2017 Free Software Foundation, Inc.
# This file is part of GNU Radio
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
import ast
import collections
import textwrap
import six
from six.moves import range
from .. import Constants
from ..base import Element
from ..utils.descriptors import Evaluated, EvaluatedEnum, setup_names
from . import dtypes
from .template_arg import TemplateArg
attributed_str = type('attributed_str', (str,), {})
@setup_names
class Param(Element):
is_param = True
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 ########################################################
self._evaluated = None
self._stringify_flag = False
self._lisitify_flag = False
self.hostage_cells = set()
self._init = False
self.scale = {
'E': 1e18,
'P': 1e15,
'T': 1e12,
'G': 1e9,
'M': 1e6,
'k': 1e3,
'm': 1e-3,
'u': 1e-6,
'n': 1e-9,
'p': 1e-12,
'f': 1e-15,
'a': 1e-18,
}
self.scale_factor = None
self.number = None
def _init_options(self, values, labels, attributes):
"""parse option and option attributes"""
options = collections.OrderedDict()
options.attributes = collections.defaultdict(dict)
padding = [''] * max(len(values), len(labels))
attributes = {key: value + padding for key, value in six.iteritems(attributes)}
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)}
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
@property
def template_arg(self):
return TemplateArg(self)
def __str__(self):
return 'Param - {}({})'.format(self.name, self.key)
def __repr__(self):
return '{!r}.param[{}]'.format(self.parent, self.key)
def is_enum(self):
return self.get_raw('dtype') == 'enum'
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))
rewriter = getattr(dtypes, 'rewrite_' + self.dtype, None)
if rewriter:
rewriter(self)
def validate(self):
"""
Validate the param.
The value must be evaluated and type must a possible type.
"""
Element.validate(self)
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(str(e))
def get_evaluated(self):
return self._evaluated
def is_float(self, num):
"""
Check if string can be converted to float.
Returns:
bool type
"""
try:
float(num)
return True
except ValueError:
return False
def evaluate(self):
"""
Evaluate the value.
Returns:
evaluated type
"""
self._init = True
self._lisitify_flag = False
self._stringify_flag = False
dtype = self.dtype
expr = self.get_value()
scale_factor = self.scale_factor
#########################
# ID and Enum types (not evaled)
#########################
if dtype in ('id', 'stream_id','name') or self.is_enum():
if self.options.attributes:
expr = attributed_str(expr)
for key, value in self.options.attributes[expr].items():
setattr(expr, key, value)
return expr
#########################
# Numeric Types
#########################
elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
if expr:
try:
if isinstance(expr, str) and self.is_float(expr[:-1]):
scale_factor = expr[-1:]
if scale_factor in self.scale:
expr = str(float(expr[:-1])*self.scale[scale_factor])
value = self.parent_flowgraph.evaluate(expr)
except Exception as e:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, e))
else:
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'):
if not expr:
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]
return value
#########################
# String Types
#########################
elif dtype in ('string', 'file_open', 'file_save', 'dir_select', '_multiline', '_multiline_python_external'):
# Do not check if file/directory exists, that is a runtime issue
try:
# Do not evaluate multiline strings (code snippets or comments)
if dtype not in ['_multiline','_multiline_python_external']:
value = self.parent_flowgraph.evaluate(expr)
if not isinstance(value, str):
raise Exception()
else:
value = str(expr)
except Exception:
self._stringify_flag = True
value = str(expr)
if dtype == '_multiline_python_external':
ast.parse(value) # Raises SyntaxError
return value
#########################
# GUI Position/Hint
#########################
elif dtype == 'gui_hint':
return self.parse_gui_hint(expr) if self.parent_block.state == 'enabled' else ''
#########################
# Import Type
#########################
elif dtype == 'import':
# New namespace
n = dict()
try:
exec(expr, n)
except ImportError:
raise Exception('Import "{}" failed.'.format(expr))
except Exception:
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(dtype))
def to_code(self):
"""
Convert the value to code.
For string and list types, check the init flag, call evaluate().
This ensures that evaluate() was called to set the xxxify_flags.
Returns:
a string representing the code
"""
self._init = True
value = self.get_value()
# String types
if self.dtype in ('string', 'file_open', 'file_save', 'dir_select', '_multiline', '_multiline_python_external'):
if not self._init:
self.evaluate()
return repr(value) if self._stringify_flag else value
# Vector types
elif self.dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
if not self._init:
self.evaluate()
return '[' + value + ']' if self._lisitify_flag else value
else:
return value
def get_opt(self, item):
return self.options.attributes[self.get_value()][item]
##############################################
# GUI Hint
##############################################
def parse_gui_hint(self, expr):
"""
Parse/validate gui hint value.
Args:
expr: gui_hint string from a block's 'gui_hint' param
Returns:
string of python code for positioning GUI elements in pyQT
"""
self.hostage_cells.clear()
# Parsing
if ':' in expr:
tab, pos = expr.split(':')
elif ',' in expr:
tab, pos = '', expr
else:
tab, pos = expr, ''
if '@' in tab:
tab, index = tab.split('@')
else:
index = '0'
index = int(index)
# Validation
def parse_pos():
e = self.parent_flowgraph.evaluate(pos)
if not isinstance(e, (list, tuple)) or len(e) not in (2, 4) or not all(isinstance(ei, int) for ei in e):
raise Exception('Invalid GUI Hint entered: {e!r} (Must be a list of {{2,4}} non-negative integers).'.format(e=e))
if len(e) == 2:
row, col = e
row_span = col_span = 1
else:
row, col, row_span, col_span = e
if (row < 0) or (col < 0):
raise Exception('Invalid GUI Hint entered: {e!r} (non-negative integers only).'.format(e=e))
if (row_span < 1) or (col_span < 1):
raise Exception('Invalid GUI Hint entered: {e!r} (positive row/column span required).'.format(e=e))
return row, col, row_span, col_span
def validate_tab():
tabs = (block for block in self.parent_flowgraph.iter_enabled_blocks()
if block.key == 'qtgui_tab_widget' and block.name == tab)
tab_block = next(iter(tabs), None)
if not tab_block:
raise Exception('Invalid tab name entered: {tab} (Tab name not found).'.format(tab=tab))
tab_index_size = int(tab_block.params['num_tabs'].value)
if index >= tab_index_size:
raise Exception('Invalid tab index entered: {tab}@{index} (Index out of range).'.format(
tab=tab, index=index))
# Collision Detection
def collision_detection(row, col, row_span, col_span):
my_parent = '{tab}@{index}'.format(tab=tab, index=index) if tab else 'main'
# Calculate hostage cells
for r in range(row, row + row_span):
for c in range(col, col + col_span):
self.hostage_cells.add((my_parent, (r, c)))
for other in self.get_all_params('gui_hint'):
if other is self:
continue
collision = next(iter(self.hostage_cells & other.hostage_cells), None)
if collision:
raise Exception('Block {block!r} is also using parent {parent!r}, cell {cell!r}.'.format(
block=other.parent_block.name, parent=collision[0], cell=collision[1]
))
# Code Generation
if tab:
validate_tab()
if not pos:
layout = '{tab}_layout_{index}'.format(tab=tab, index=index)
else:
layout = '{tab}_grid_layout_{index}'.format(tab=tab, index=index)
else:
layout = 'top_grid_layout'
widget = '%s' # to be fill-out in the mail template
if pos:
row, col, row_span, col_span = parse_pos()
collision_detection(row, col, row_span, col_span)
widget_str = textwrap.dedent("""
self.{layout}.addWidget({widget}, {row}, {col}, {row_span}, {col_span})
for r in range({row}, {row_end}):
self.{layout}.setRowStretch(r, 1)
for c in range({col}, {col_end}):
self.{layout}.setColumnStretch(c, 1)
""".strip('\n')).format(
layout=layout, widget=widget,
row=row, row_span=row_span, row_end=row+row_span,
col=col, col_span=col_span, col_end=col+col_span,
)
else:
widget_str = 'self.{layout}.addWidget({widget})'.format(layout=layout, widget=widget)
return widget_str
def get_all_params(self, dtype, key=None):
"""
Get all the params from the flowgraph that have the given type and
optionally a given key
Args:
dtype: the specified type
key: the key to match against
Returns:
a list of params
"""
params = []
for block in self.parent_flowgraph.iter_enabled_blocks():
params.extend(
param for param in block.params.values()
if param.dtype == dtype and (key is None or key == param.name)
)
return params