Changed variables interface, now variables can be intialized using absolute or relative variables names. Some variables types are done.

packages
Иванов Денис 4 years ago
parent 471f957daa
commit c8517ec59c

@ -1,5 +1,6 @@
import re import re
import ast import ast
import dis
from contextlib import contextmanager from contextlib import contextmanager
from inspect import signature, getsource from inspect import signature, getsource
from types import FunctionType, LambdaType from types import FunctionType, LambdaType
@ -12,15 +13,19 @@ class VariableType:
return True return True
class DependenceError(Exception):
pass
class VariableError(Exception): class VariableError(Exception):
pass pass
class VariableTypeError(Exception): class VariableTypeError(VariableError):
pass pass
class DependenceError(Exception): class VariableNotFoundError(VariableError):
pass pass
@ -41,7 +46,9 @@ class IntegerType(VariableType):
integer_pattern = re.compile(r"^-?\d+$") integer_pattern = re.compile(r"^-?\d+$")
@classmethod @classmethod
def check(self, value: str) -> bool: def check(self, value) -> bool:
if isinstance(value, int):
return True
return self.integer_pattern.match(value) return self.integer_pattern.match(value)
@ -49,12 +56,18 @@ class FloatType(VariableType):
float_pattern = re.compile(r"^-?\d+(\.\d+)?$") float_pattern = re.compile(r"^-?\d+(\.\d+)?$")
@classmethod @classmethod
def check(self, value: str) -> bool: def check(self, value) -> bool:
if isinstance(value, float):
return True
return self.integer_pattern.match(value) return self.integer_pattern.match(value)
class BooleanType(VariableType): class BooleanType(VariableType):
pass bool_available = {'True', 'False'}
@classmethod
def check(self, value) -> bool:
return isinstance(value, bool) or value in self.bool_available
class ListType(VariableType): class ListType(VariableType):
@ -66,11 +79,16 @@ class ReadonlyType(VariableType):
class Dependence: class Dependence:
current_namespace = None
datavars_root = None
def __init__(self, *variables, depend=None): def __init__(self, *variables, depend=None):
self._subscriptions = variables self._subscriptions = variables
self.depend_function = depend self.depend_function = depend
def check(self): def check(self):
'''Метод для запуска проверки корректности функции зависимости, а также
сопоставления ее числу заданных зависимостей.'''
if self.depend_function is None: if self.depend_function is None:
if len(self._subscriptions) > 1: if len(self._subscriptions) > 1:
raise DependenceError('the depend function is needed if the' raise DependenceError('the depend function is needed if the'
@ -84,16 +102,19 @@ class Dependence:
def calculate_value(self): def calculate_value(self):
'''Метод для расчета значения переменной с использованием зависимостей '''Метод для расчета значения переменной с использованием зависимостей
и заданной для них функции.''' и заданной для них функции.'''
args_values = tuple(subscription.get_value() for subscription
in self._subscriptions)
print('args_values: {}'.format(args_values))
try: try:
if self.depend_function is None: if self.depend_function is None:
return self._subscriptions[-1].get_value() return args_values[-1]
return self.depend_function(*(subscription.get_value() return self.depend_function(*args_values)
for subscription
in self._subscriptions))
except Exception as error: except Exception as error:
raise DependenceError('can not calculate using dependencies: {}' raise DependenceError('can not calculate using dependencies: {}'
' reason: {}'.format(', '.join( ' reason: {}'.format(', '.join(
self._subscriptions), [subscription.fullname
for subscription
in self._subscriptions]),
str(error))) str(error)))
@property @property
@ -123,6 +144,65 @@ class Dependence:
len(function_signature.parameters), len(function_signature.parameters),
len(self._subscriptions))) len(self._subscriptions)))
def _get_variables(self, variables_names):
print('current_namespace: {}'.format(self.current_namespace.fullname))
print('root:')
for namespace_name, namespace in self.datavars_root:
print(namespace_name)
variables = list()
for variable_name in variables_names:
result = None
name_parts = variable_name.split('.')
if not name_parts[0]:
namespace = self.current_namespace
index = 1
while not name_parts[index]:
namespace = namespace.parent
index += 1
name_parts = name_parts[index:]
else:
namespace = self.datavars_root
result = namespace
for part in name_parts:
result = result[part]
variables.append(result)
return variables
def __ne__(self, other):
return not self == other
def __eq__(self, other):
if not isinstance(other, Dependence):
return False
if not self._compare_depend_functions(self.depend_function,
other.depend_function):
return False
for l_var, r_var in zip(self._subscriptions, other._subscriptions):
if l_var != r_var:
return False
return True
def _compare_depend_functions(self, l_func: FunctionType,
r_func: FunctionType) -> bool:
'''Метод для сравнения функций путем сравнения инструкций, полученных
в результате их дизассемблирования.'''
l_instructions = list(dis.get_instructions(l_func))
r_instructions = list(dis.get_instructions(r_func))
for l_instr, r_instr in zip(l_instructions, r_instructions):
if l_instr.opname != r_instr.opname:
return False
if l_instr.arg != r_instr.arg:
return False
if ((l_instr.argval != l_instr.argrepr) and
(r_instr.argval != r_instr.argrepr)):
if r_instr.argval != l_instr.argval:
return False
return True
class VariableNode: class VariableNode:
def __init__(self, name, namespace, variable_type=StringType, source=None): def __init__(self, name, namespace, variable_type=StringType, source=None):
@ -151,8 +231,6 @@ class VariableNode:
if self.calculating: if self.calculating:
raise CyclicVariableError(self.name) raise CyclicVariableError(self.name)
self._invalidate()
if self._source is None: if self._source is None:
raise VariableError("No sources to update variable '{}'". raise VariableError("No sources to update variable '{}'".
format(self.fullname)) format(self.fullname))
@ -172,16 +250,18 @@ class VariableNode:
@source.setter @source.setter
def source(self, source): def source(self, source):
self._invalidate() if self._source != source:
self._source = source self._invalidate()
if isinstance(source, Dependence): self._source = source
for subscription in source._subscriptions: if isinstance(source, Dependence):
subscription.subscribers.add(self) for subscription in source._subscriptions:
subscription.subscribers.add(self)
def _invalidate(self): def _invalidate(self):
self.value = None if self.value is not None:
for subscriber in self.subscribers: self.value = None
subscriber._invalidate() for subscriber in self.subscribers:
subscriber._invalidate()
@contextmanager @contextmanager
def _start_calculate(self): def _start_calculate(self):
@ -194,6 +274,7 @@ class VariableNode:
self.calculating = False self.calculating = False
def get_value(self): def get_value(self):
'''Метод для получения значения переменной.'''
if self.value is None: if self.value is None:
self.update_value() self.update_value()
return self.value return self.value
@ -233,20 +314,24 @@ class NamespaceNode:
else: else:
return self.name return self.name
def get_variable_node(self, name): def __getattr__(self, name: str):
if name in self.variables: '''Метод возвращает ноду пространства имен или значение переменной.'''
return self.variables[name] if name in self.namespaces:
return self.namespaces[name]
elif name in self.variables:
return self.variables[name].get_value()
else: else:
raise VariableError("Variable '{variable_name}' is not found in" raise VariableError("'{variable_name}' is not found in the"
" the namespace '{namespace_name}'".format( " namespace '{namespace_name}'".format(
variable_name=name, variable_name=name,
namespace_name=self.name)) namespace_name=self.name))
def __getattr__(self, name: str): def __getitem__(self, name: str):
'''Метод возвращает ноду пространства имен или ноду переменной.'''
if name in self.namespaces: if name in self.namespaces:
return self.namespaces[name] return self.namespaces[name]
elif name in self.variables: elif name in self.variables:
return self.variables[name].get_value() return self.variables[name]
else: else:
raise VariableError("'{variable_name}' is not found in the" raise VariableError("'{variable_name}' is not found in the"
" namespace '{namespace_name}'".format( " namespace '{namespace_name}'".format(
@ -261,12 +346,20 @@ class NamespaceNode:
class VariableAPI: class VariableAPI:
'''Класс для создания переменных при задании их через
python-скрипты.'''
def __init__(self): def __init__(self):
self.current_namespace = None self.current_namespace = None
# TODO Продумать другой способ обработки ошибок.
self.errors = [] self.errors = []
def __call__(self, name: str, source=None): def __call__(self, name: str, source=None):
variable = VariableNode(name, self.current_namespace) '''Метод для создания переменных внутри with Namespace('name').'''
if name not in self.current_namespace.variables:
variable = VariableNode(name, self.current_namespace)
else:
variable = self.current_namespace[name]
if isinstance(source, Dependence): if isinstance(source, Dependence):
try: try:
source.check() source.check()
@ -280,6 +373,8 @@ class VariableAPI:
class NamespaceAPI: class NamespaceAPI:
'''Класс для создания пространств имен при задании переменных через
python-скрипты.'''
def __init__(self, var_fabric: VariableAPI, def __init__(self, var_fabric: VariableAPI,
datavars_root=NamespaceNode('<root>')): datavars_root=NamespaceNode('<root>')):
self._datavars = datavars_root self._datavars = datavars_root
@ -287,28 +382,51 @@ class NamespaceAPI:
self.namespace_stack = [datavars_root] self.namespace_stack = [datavars_root]
self._variables_fabric.current_namespace = self._datavars self._variables_fabric.current_namespace = self._datavars
Dependence.current_namespace = self._datavars
Dependence.datavars_root = self._datavars
@property @property
def datavars(self): def datavars(self):
'''Метод для получения корневого пространства имен, через которое далее
можно получить доступ к переменным.'''
return self._datavars return self._datavars
def set_root(self, datavars_root: NamespaceNode): def set_root(self, datavars_root: NamespaceNode):
'''Метод для установки корневого пространства имен, которое пока что
будет использоваться для предоставления доступа к переменным.'''
self._datavars = datavars_root self._datavars = datavars_root
self.namespace_stack = [datavars_root] self.namespace_stack = [datavars_root]
self._variables_fabric.current_namespace = self._datavars self._variables_fabric.current_namespace = self._datavars
def reset(self):
'''Метод для сброса корневого пространства имен.'''
self._datavars = NamespaceNode('<root>')
self.namespace_stack = [self._datavars]
self._variables_fabric.current_namespace = self._datavars
Dependence.current_namespace = self._datavars
@contextmanager @contextmanager
def __call__(self, namespace_name): def __call__(self, namespace_name):
new_namespace = NamespaceNode( '''Метод для создания пространств имен с помощью with.'''
namespace_name, if namespace_name not in self.namespace_stack[-1].namespaces:
parent=self.namespace_stack[-1]) namespace = NamespaceNode(namespace_name,
self.namespace_stack[-1].add_namespace(new_namespace) parent=self.namespace_stack[-1])
self.namespace_stack.append(new_namespace) else:
self._variables_fabric.current_namespace = new_namespace namespace = self.namespace_stack[-1].namespaces[namespace_name]
self.namespace_stack[-1].add_namespace(namespace)
self.namespace_stack.append(namespace)
self._variables_fabric.current_namespace = namespace
Dependence.datavars_root = self._datavars
Dependence.current_namespace = namespace
try: try:
yield self yield self
finally: finally:
self._variables_fabric.current_namespace =\ current_namespace = self.namespace_stack.pop()
self.namespace_stack.pop() self._variables_fabric.current_namespace = current_namespace
Dependence.current_namespace = current_namespace
Variable = VariableAPI() Variable = VariableAPI()

@ -1,6 +1,7 @@
import pytest import pytest
from calculate.vars.alt_datavars import NamespaceNode, VariableNode,\ from calculate.vars.alt_datavars import NamespaceNode, VariableNode,\
Namespace, Variable, Dependence Namespace, Variable, Dependence,\
CyclicVariableError
@pytest.mark.alt_vars @pytest.mark.alt_vars
@ -23,6 +24,9 @@ class TestNamespace:
assert namespace_1.variables == dict() assert namespace_1.variables == dict()
assert namespace_1.fullname == 'namespace_1' assert namespace_1.fullname == 'namespace_1'
assert namespace_1.namespace_2 == namespace_2
assert namespace_1['namespace_2'] == namespace_2
assert namespace_2.namespaces == dict() assert namespace_2.namespaces == dict()
assert namespace_2.variables == dict() assert namespace_2.variables == dict()
assert namespace_2.parent == namespace_1 assert namespace_2.parent == namespace_1
@ -39,11 +43,75 @@ class TestNamespace:
assert namespace_1.fullname == 'namespace_1' assert namespace_1.fullname == 'namespace_1'
assert namespace_1.var_1 == 'value_1' assert namespace_1.var_1 == 'value_1'
assert namespace_1['var_1'] == variable_1
assert namespace_1.var_2 == 'value_2' assert namespace_1.var_2 == 'value_2'
assert namespace_1.get_variable_node('var_1').fullname ==\ assert namespace_1['var_2'] == variable_2
'namespace_1.var_1'
assert namespace_1.get_variable_node('var_2').fullname ==\ assert namespace_1['var_1'].fullname == 'namespace_1.var_1'
'namespace_1.var_2' assert namespace_1['var_2'].fullname == 'namespace_1.var_2'
def test_compare_two_dependencies_equal(self):
namespace_1 = NamespaceNode('namespace_1')
variable_1 = VariableNode('var_1', namespace_1, source=2)
variable_2 = VariableNode('var_2', namespace_1, source=4)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
'greater' if arg_1 > arg_2 else 'less')
dependence_2 = Dependence(variable_1, variable_2,
depend=lambda var_1, var_2:
'greater' if var_1 > var_2 else 'less')
assert dependence_1 == dependence_2
def test_compare_two_dependencies_equal_but_one_is_function_and_other_is_lambda(self):
namespace_1 = NamespaceNode('namespace_1')
variable_1 = VariableNode('var_1', namespace_1, source=2)
variable_2 = VariableNode('var_2', namespace_1, source=4)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
'greater' if arg_1 > arg_2 else 'less')
def comparator(var_1, var_2):
if var_1 > var_2:
return 'greater'
else:
return 'less'
dependence_2 = Dependence(variable_1, variable_2, depend=comparator)
assert dependence_1 == dependence_2
def test_compare_two_dependencies_not_equal(self):
namespace_1 = NamespaceNode('namespace_1')
variable_1 = VariableNode('var_1', namespace_1, source=2)
variable_2 = VariableNode('var_2', namespace_1, source=4)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
'less' if arg_1 > arg_2 else 'greater')
dependence_2 = Dependence(variable_1, variable_2,
depend=lambda var_1, var_2:
'greater' if var_1 > var_2 else 'less')
assert dependence_1 != dependence_2
def test_compare_two_dependencies_not_equal_but_one_is_function_and_other_is_lambda(self):
namespace_1 = NamespaceNode('namespace_1')
variable_1 = VariableNode('var_1', namespace_1, source=2)
variable_2 = VariableNode('var_2', namespace_1, source=4)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
'greater' if arg_1 > arg_2 else 'less')
def comparator(var_1, var_2):
if var_1 > var_2:
return 'less'
else:
return 'greater'
dependence_2 = Dependence(variable_1, variable_2, depend=comparator)
assert dependence_1 != dependence_2
def test_if_a_variable_subscribed_to_two_other_variables_using_set_function__the_(self): def test_if_a_variable_subscribed_to_two_other_variables_using_set_function__the_(self):
namespace_1 = NamespaceNode('namespace_1') namespace_1 = NamespaceNode('namespace_1')
@ -58,10 +126,12 @@ class TestNamespace:
assert namespace_2.var_1 == 'less' assert namespace_2.var_1 == 'less'
namespace_1.get_variable_node('var_1').source = 5 namespace_1['var_1'].source = 5
assert namespace_2.var_1 == 'greater' assert namespace_2.var_1 == 'greater'
# Теперь тестируем интерфейс создания переменных.
def test_api(self): def test_api(self):
Namespace.reset()
with Namespace('namespace_1'): with Namespace('namespace_1'):
Variable('var_1', source='val_1') Variable('var_1', source='val_1')
@ -84,5 +154,95 @@ class TestNamespace:
assert datavars.namespace_2.var_1 == 'less' assert datavars.namespace_2.var_1 == 'less'
assert datavars.namespace_2.namespace_2_1.var_1 == 'val_1' assert datavars.namespace_2.namespace_2_1.var_1 == 'val_1'
datavars.namespace_1.get_variable_node('var_2').source = 5 datavars.namespace_1['var_2'].source = 5
assert datavars.namespace_2.var_1 == 'greater' assert datavars.namespace_2.var_1 == 'greater'
def test_rewriting_if_source_changed_to_a_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
var_1 = Variable('var_1', source='value_1')
Variable('var_2', source=Dependence(
var_1,
depend=lambda arg_1:
'from var_1: {}'.format(arg_1)))
assert datavars.namespace_1.var_1 == 'value_1'
assert datavars.namespace_1.var_2 == 'from var_1: value_1'
with Namespace('namespace_1'):
var_1 = Variable('var_1', source='value_2')
assert datavars.namespace_1.var_1 == 'value_2'
assert datavars.namespace_1.var_2 == 'from var_1: value_2'
def test_rewriting_if_source_changed_to_a_dependence(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
var_1 = Variable('var_1', source='value_1')
Variable('var_2', source=Dependence(var_1, depend=lambda arg_1:
'from var_1: {}'.
format(arg_1)))
var_3 = Variable('var_3', source='value_3')
assert datavars.namespace_1.var_1 == 'value_1'
assert datavars.namespace_1.var_2 == 'from var_1: value_1'
with Namespace('namespace_1'):
Variable('var_2', source=Dependence(var_3, depend=lambda arg_1:
'from var_3: {}'.
format(arg_1)))
assert datavars.namespace_1.var_2 == 'from var_3: value_3'
def test_cyclic_dependence(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
var_1 = Variable('var_1', source='value_1')
var_2 = Variable('var_2',
source=Dependence(var_1,
depend=lambda arg_1:
'from var_1: {}'.format(arg_1)))
def comparator(arg_1):
return 'from var_2: {}'.format(arg_1)
var_3 = Variable('var_3',
source=Dependence(var_2, depend=comparator))
Variable('var_1',
source=Dependence(var_3,
depend=lambda arg_1:
'from var_3: {}'.format(arg_1)))
with pytest.raises(CyclicVariableError):
datavars.namespace_1.var_3
def test_find_vars(self):
Namespace.reset()
datavars = Namespace.datavars
var_1 = Variable('var', source='null')
with Namespace('namespace_1'):
Variable('var_1', source='val_1')
Variable('var_2', source=2)
Variable('var_3', source=4)
with Namespace('namespace_1_1'):
Variable('var_1', source='val_1')
for var in Dependence(var_1)._get_variables(['.var_1',
'..var_3']):
print(var.fullname)
with Namespace('namespace_2'):
print('current_datavars:')
for namespace_name, namespace in datavars.namespaces.items():
print(namespace_name)
for var in Dependence(var_1)._get_variables(['namespace_1.var_2']):
print(var.fullname)
assert False

Loading…
Cancel
Save