|
|
|
@ -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]
|
|
|
|
|
|
|
|
|
|