Hash type and a number of other types was implemented.

packages
Иванов Денис 4 years ago
parent 88554c3295
commit 4c775e76d7

@ -1,4 +1,3 @@
import re
import ast
import dis
from contextlib import contextmanager
@ -33,53 +32,160 @@ class CyclicVariableError(VariableError):
class VariableType:
'''Базовый класс для типов.'''
name = 'base'
@classmethod
def check(self, value: str) -> bool:
return True
def process_value(self, value, variable):
pass
class StringType(VariableType):
pass
name = 'string'
@classmethod
def process_value(self, value, variable) -> str:
if isinstance(value, str):
return value
else:
try:
return str(value)
except Exception as error:
raise VariableTypeError("can not set value '{value}' to"
" string variable: {reason}".format(
value=value,
reason=str(error)))
class IntegerType(VariableType):
integer_pattern = re.compile(r"^-?\d+$")
name = 'integer'
@classmethod
def check(self, value) -> bool:
def process_value(self, value, variable) -> int:
if isinstance(value, int):
return True
return self.integer_pattern.match(value)
return value
else:
try:
return int(value)
except Exception as error:
raise VariableTypeError("can not set value '{value}' to"
" interger variable: {reason}".format(
value=value,
reason=str(error)))
class FloatType(VariableType):
float_pattern = re.compile(r"^-?\d+(\.\d+)?$")
name = 'float'
@classmethod
def check(self, value) -> bool:
def process_value(self, value, variable) -> float:
if isinstance(value, float):
return True
return self.integer_pattern.match(value)
else:
try:
return float(value)
except Exception as error:
raise VariableTypeError("can not set value '{value}' to"
" float variable: {reason}".format(
value=value,
reason=str(error)))
class BooleanType(VariableType):
bool_available = {'True', 'False'}
name = 'bool'
true_values = {'True', 'true'}
false_values = {'False', 'false'}
@classmethod
def check(self, value) -> bool:
return isinstance(value, bool) or value in self.bool_available
def process_value(self, value, variable) -> bool:
if isinstance(value, bool):
return value
elif isinstance(value, str):
if value in self.true_values:
return True
if value in self.false_values:
return False
try:
return bool(value)
except Exception as error:
raise VariableTypeError("can not set value '{value}' to"
" bool variable: {reason}".format(
value=value,
reason=str(error)))
class ListType(VariableType):
pass
name = 'list'
@classmethod
def process_value(self, value, variable) -> list:
# TODO нормально все сделать.
return value
class ReadonlyType(VariableType):
# TODO Убрать его, добавить соответствующий флаг нодам переменных.
pass
class HashValue:
def __init__(self, key, value, master_variable):
self.key = key
self.value = value
self.master_variable = master_variable
@property
def subscriptions(self):
return self.master_variable.subscriptions
@property
def subscribers(self):
return self.master_variable.subscribers
def get_value(self):
self = self.master_variable.get_value()[self.key]
return self.value
class Hash:
name = 'hash'
def __init__(self, values: dict, master_variable):
self._values = dict()
for key, value in values.items():
self._values.update({key: HashValue(key, value, master_variable)})
self.name = None
self.master_variable = master_variable
def __getattr__(self, key: str):
'''Метод возвращает ноду пространства имен или значение переменной.'''
if key in self._values:
return self._values[key].get_value()
else:
raise VariableError(("'{key}' is not found in the hash"
" '{hash_name}'").format(key=key,
hash_name=self.name))
def __getitem__(self, key: str):
if key in self._values:
return self._values[key]
else:
raise VariableError(("'{key}' is not found in the hash"
" '{hash_name}'").format(key=key,
hash_name=self.name))
def __contains__(self, key: str):
return key in self._values
class HashType(VariableType):
pass
@classmethod
def process_value(self, value, variable) -> Hash:
print('IN PROCESS HASH VALUE')
if not isinstance(value, dict):
raise VariableTypeError("can not set value with type '{_type}' to"
" hash variable: value must be 'dict' type"
.format(_type=type(value)))
return Hash(value, variable)
class TableType(VariableType):
@ -190,11 +296,6 @@ class DependenceSource:
return True
class HashSource:
def __init__(self):
pass
class VariableNode:
'''Класс ноды соответствующей переменной в дереве переменных.'''
def __init__(self, name, namespace, variable_type=StringType, source=None):
@ -213,15 +314,19 @@ class VariableNode:
# Если значение переменной None -- значит она обнулена.
self.value = None
# Источник значения переменной, может быть значением, а может быть
# зависимостью.
self._source = source
if source is not None:
self.update_value()
self._readonly = False
def update_value(self):
'''Метод для обновления значения переменной с помощью указанного
источника ее значения.'''
print('updating {}'.format(self.fullname))
if self.calculating:
raise CyclicVariableError(self.name)
@ -232,11 +337,13 @@ class VariableNode:
if isinstance(self._source, DependenceSource):
with self._start_calculate():
try:
self.value = self._source.calculate_value()
value = self._source.calculate_value()
except CyclicVariableError as error:
raise CyclicVariableError(self.name, *error.queue)
else:
self.value = self._source
value = self._source
self.value = self.variable_type.process_value(value, self)
@property
def source(self):
@ -244,13 +351,28 @@ class VariableNode:
@source.setter
def source(self, source):
if self._readonly:
raise VariableError("can not change the variable '{}': read only")
if self._source != source:
self._invalidate()
self._source = source
if isinstance(source, DependenceSource):
for subscription in source._subscriptions:
for subscription in source.subscriptions:
subscription.subscribers.add(self)
@property
def readonly(self) -> bool:
return self._readonly
@readonly.setter
def readonly(self, value: bool):
if self.value is None and self._source is not None:
# TODO выводить предупреждение если переменная инвалидирована,
# нет источника и при этом устанавливается флаг readonly.
self.update_value()
self._readonly = value
def _invalidate(self):
'''Метод для инвалидации данной переменной и всех зависящих от нее
переменных.'''
@ -283,6 +405,17 @@ class VariableNode:
else:
return self.name
def __getitem__(self, key):
if self.variable_type is HashType:
if key in self.get_value():
return self.value[key]
else:
raise VariableError("value '{}' is not found in hash variable"
" '{}'".format(key, self.fullname))
else:
raise VariableError("'{}' variable type is not subscriptable.".
format(self.variable_type.name))
def __repr__(self):
return '<Variable: {} with value: {}>'.format(self.fullname,
self.value or
@ -345,35 +478,6 @@ class NamespaceNode:
return '<Namespace: {}>'.format(self.fullname)
class HashNode:
def __init__(self, name, namespace, sources, types=dict()):
self.name = name
self.namespace = namespace
self.calculating = False
self.namespace.add_variable(self)
self.sources = sources
self.subscribers = dict()
self.values = dict()
self.types = dict()
for key, source in sources:
self.values.update({key: None})
if key in types:
if issubclass(types[key], VariableType):
self.types.update({key: types[key]})
else:
raise VariableTypeError('variable_type must be'
' VariableType, but not {}'.format(
type(types[key])))
else:
self.types.update({key: StringType})
# Источник значения переменной, может быть значением, а может быть
# зависимостью.
self.update_values()
class DependenceAPI:
def __init__(self):
self.current_namespace = None
@ -422,10 +526,12 @@ class VariableAPI:
# TODO Продумать другой способ обработки ошибок.
self.errors = []
def __call__(self, name: str, source=None):
def __call__(self, name: str, source=None, type=StringType,
readonly=False):
'''Метод для создания переменных внутри with Namespace('name').'''
if name not in self.current_namespace.variables:
variable = VariableNode(name, self.current_namespace)
variable = VariableNode(name, self.current_namespace,
variable_type=type)
else:
variable = self.current_namespace[name]

@ -1,7 +1,8 @@
import pytest
from calculate.vars.alt_datavars import NamespaceNode, VariableNode,\
Namespace, Variable, Dependence,\
CyclicVariableError
CyclicVariableError, HashType,\
StringType, IntegerType, VariableError
@pytest.mark.alt_vars
@ -57,8 +58,8 @@ class TestNamespace:
with Namespace('namespace_1'):
Variable('var_1', source='val_1')
Variable('var_2', source=2)
Variable('var_3', source=4)
Variable('var_2', source=2, type=IntegerType)
Variable('var_3', source=4, type=IntegerType)
with Namespace('namespace_1_1'):
Variable('var_1', source='val_1')
@ -74,8 +75,10 @@ class TestNamespace:
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)
variable_1 = VariableNode('var_1', namespace_1, source=2,
variable_type=IntegerType)
variable_2 = VariableNode('var_2', namespace_1, source=4,
variable_type=IntegerType)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
@ -88,8 +91,10 @@ class TestNamespace:
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)
variable_1 = VariableNode('var_1', namespace_1, source=2,
variable_type=IntegerType)
variable_2 = VariableNode('var_2', namespace_1, source=4,
variable_type=IntegerType)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
@ -105,8 +110,10 @@ class TestNamespace:
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)
variable_1 = VariableNode('var_1', namespace_1, source=2,
variable_type=IntegerType)
variable_2 = VariableNode('var_2', namespace_1, source=4,
variable_type=IntegerType)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
@ -119,8 +126,10 @@ class TestNamespace:
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)
variable_1 = VariableNode('var_1', namespace_1, source=2,
variable_type=IntegerType)
variable_2 = VariableNode('var_2', namespace_1, source=4,
variable_type=IntegerType)
dependence_1 = Dependence(variable_1, variable_2,
depend=lambda arg_1, arg_2:
@ -136,8 +145,10 @@ class TestNamespace:
def test_if_a_variable_subscribed_to_two_other_variables_using_set_function__the_(self):
namespace_1 = NamespaceNode('namespace_1')
variable_1 = VariableNode('var_1', namespace_1, source=2)
variable_2 = VariableNode('var_2', namespace_1, source=4)
variable_1 = VariableNode('var_1', namespace_1, source=2,
variable_type=IntegerType)
variable_2 = VariableNode('var_2', namespace_1, source=4,
variable_type=IntegerType)
namespace_2 = NamespaceNode('namespace_2')
variable = VariableNode('var_1', namespace_2)
@ -156,9 +167,9 @@ class TestNamespace:
with Namespace('namespace_1'):
Variable('var_1', source='val_1')
var_2 = Variable('var_2', source=2)
var_2 = Variable('var_2', source=2, type=IntegerType)
Variable('var_3', source=4)
Variable('var_3', source=4, type=IntegerType)
with Namespace('namespace_2'):
Variable('var_1', source=Dependence(
@ -239,3 +250,42 @@ class TestNamespace:
with pytest.raises(CyclicVariableError):
datavars.namespace_1.var_3
def test_hash(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
Variable('var_1', source='value_1', type=StringType)
Variable('var_2', type=HashType, source={'key_1': 'value_1',
'key_2': 'value_2'})
Variable('var_3', source='value_3', type=StringType)
def depend_hash(arg_1, arg_2):
return {'key_1': 'value',
'key_2': arg_1,
'key_3': arg_2}
Variable('var_4', type=HashType,
source=Dependence('.var_1', '.var_2.key_1',
depend=depend_hash))
assert datavars.namespace_1.var_2.key_1 == 'value_1'
assert datavars.namespace_1.var_4.key_1 == 'value'
assert datavars.namespace_1.var_4.key_2 == 'value_1'
assert datavars.namespace_1.var_4.key_3 == 'value_1'
with Namespace('namespace_1'):
Variable('var_1', source='other_value', type=StringType)
Variable('var_2', source={'key_1': 'another_value',
'key_2': 'value_2'})
assert datavars.namespace_1.var_2.key_1 == 'another_value'
assert datavars.namespace_1.var_4.key_2 == 'other_value'
assert datavars.namespace_1.var_4.key_3 == 'another_value'
def test_readonly(self):
with Namespace('namespace_1'):
Variable('var_1', source='value_1', type=StringType, readonly=True)
with Namespace('namespace_1'):
with pytest.raises(VariableError):
Variable('var_1', source='value_2', type=StringType)

Loading…
Cancel
Save