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

@ -1,6 +1,7 @@
import pytest
from calculate.vars.alt_datavars import NamespaceNode, VariableNode,\
Namespace, Variable, Dependence
Namespace, Variable, Dependence,\
CyclicVariableError
@pytest.mark.alt_vars
@ -23,6 +24,9 @@ class TestNamespace:
assert namespace_1.variables == dict()
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.variables == dict()
assert namespace_2.parent == namespace_1
@ -39,11 +43,75 @@ class TestNamespace:
assert namespace_1.fullname == 'namespace_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.get_variable_node('var_1').fullname ==\
'namespace_1.var_1'
assert namespace_1.get_variable_node('var_2').fullname ==\
'namespace_1.var_2'
assert namespace_1['var_2'] == variable_2
assert namespace_1['var_1'].fullname == 'namespace_1.var_1'
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):
namespace_1 = NamespaceNode('namespace_1')
@ -58,10 +126,12 @@ class TestNamespace:
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'
# Теперь тестируем интерфейс создания переменных.
def test_api(self):
Namespace.reset()
with Namespace('namespace_1'):
Variable('var_1', source='val_1')
@ -84,5 +154,95 @@ class TestNamespace:
assert datavars.namespace_2.var_1 == 'less'
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'
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