|
|
|
@ -1,6 +1,6 @@
|
|
|
|
|
import ast
|
|
|
|
|
import dis
|
|
|
|
|
from typing import List, Union, Any
|
|
|
|
|
from typing import List, Any
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
from inspect import signature, getsource
|
|
|
|
|
from types import FunctionType, LambdaType
|
|
|
|
@ -36,9 +36,14 @@ class VariableType:
|
|
|
|
|
name = 'undefined'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable):
|
|
|
|
|
def process_value(cls, value, variable):
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def readonly(cls, variable_object) -> None:
|
|
|
|
|
variable_object.variable_type = cls
|
|
|
|
|
variable_object.readonly = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IniType(VariableType):
|
|
|
|
|
'''Класс, соответствующий типу переменных созданных в calculate.ini файлах.
|
|
|
|
@ -46,7 +51,7 @@ class IniType(VariableType):
|
|
|
|
|
name = 'ini'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable):
|
|
|
|
|
def process_value(cls, value, variable):
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -55,7 +60,7 @@ class StringType(VariableType):
|
|
|
|
|
name = 'string'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable) -> str:
|
|
|
|
|
def process_value(cls, value, variable) -> str:
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
return value
|
|
|
|
|
else:
|
|
|
|
@ -73,7 +78,7 @@ class IntegerType(VariableType):
|
|
|
|
|
name = 'integer'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable) -> int:
|
|
|
|
|
def process_value(cls, value, variable) -> int:
|
|
|
|
|
if isinstance(value, int):
|
|
|
|
|
return value
|
|
|
|
|
else:
|
|
|
|
@ -91,7 +96,7 @@ class FloatType(VariableType):
|
|
|
|
|
name = 'float'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable) -> float:
|
|
|
|
|
def process_value(cls, value, variable) -> float:
|
|
|
|
|
if isinstance(value, float):
|
|
|
|
|
return value
|
|
|
|
|
else:
|
|
|
|
@ -111,13 +116,13 @@ class BooleanType(VariableType):
|
|
|
|
|
false_values = {'False', 'false'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable) -> bool:
|
|
|
|
|
def process_value(cls, value, variable) -> bool:
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
return value
|
|
|
|
|
elif isinstance(value, str):
|
|
|
|
|
if value in self.true_values:
|
|
|
|
|
if value in cls.true_values:
|
|
|
|
|
return True
|
|
|
|
|
if value in self.false_values:
|
|
|
|
|
if value in cls.false_values:
|
|
|
|
|
return False
|
|
|
|
|
try:
|
|
|
|
|
return bool(value)
|
|
|
|
@ -132,7 +137,7 @@ class ListType(VariableType):
|
|
|
|
|
name = 'list'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable) -> list:
|
|
|
|
|
def process_value(cls, value, variable) -> list:
|
|
|
|
|
# TODO нормально все сделать.
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
return value
|
|
|
|
@ -181,6 +186,7 @@ class Hash:
|
|
|
|
|
'''Класс реализующий контейнер для хранения хэша в переменной
|
|
|
|
|
соответствующего типа.'''
|
|
|
|
|
def __init__(self, values: dict, master_variable, parent=None):
|
|
|
|
|
self.fixed = master_variable.fixed
|
|
|
|
|
self._values = dict()
|
|
|
|
|
for key, value in values.items():
|
|
|
|
|
self._values.update({key: HashValue(key, value, master_variable,
|
|
|
|
@ -189,11 +195,27 @@ class Hash:
|
|
|
|
|
self.row_index = None
|
|
|
|
|
|
|
|
|
|
def get_hash(self) -> dict:
|
|
|
|
|
'''Метод для получения словаря из значений хэша.'''
|
|
|
|
|
dict_value = {}
|
|
|
|
|
for key in self._values.keys():
|
|
|
|
|
dict_value.update({key: self._values[key].get_value()})
|
|
|
|
|
return dict_value
|
|
|
|
|
|
|
|
|
|
def update_hash(self, values: dict) -> None:
|
|
|
|
|
'''Метод для обновления значения хэша.'''
|
|
|
|
|
print('UPDATE HASH')
|
|
|
|
|
for key, value in values.items():
|
|
|
|
|
if key in self._values:
|
|
|
|
|
self._values[key].value = value
|
|
|
|
|
elif self.fixed:
|
|
|
|
|
raise VariableError("key '{}' is unavailable for fixed"
|
|
|
|
|
" hash, available keys: '{}'".
|
|
|
|
|
format(key, ', '.join(self._fields)))
|
|
|
|
|
else:
|
|
|
|
|
self._values[key] = HashValue(key, value, self.master_variable,
|
|
|
|
|
self)
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, key: str):
|
|
|
|
|
'''Метод возвращает ноду пространства имен или значение переменной.'''
|
|
|
|
|
if key in self._values:
|
|
|
|
@ -226,12 +248,21 @@ class HashType(VariableType):
|
|
|
|
|
name = 'hash'
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value, variable) -> Hash:
|
|
|
|
|
if not isinstance(value, dict):
|
|
|
|
|
def process_value(cls, values, variable) -> Hash:
|
|
|
|
|
if not isinstance(values, 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)
|
|
|
|
|
.format(_type=type(values)))
|
|
|
|
|
if variable.value is not None:
|
|
|
|
|
updated_hash = variable.value.update_hash(values)
|
|
|
|
|
else:
|
|
|
|
|
updated_hash = Hash(values, variable)
|
|
|
|
|
return updated_hash
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def fixed(cls, variable_object) -> None:
|
|
|
|
|
variable_object.variable_type = cls
|
|
|
|
|
variable_object.fixed = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Table:
|
|
|
|
@ -487,8 +518,8 @@ class VariableNode:
|
|
|
|
|
# динамическом связывании.
|
|
|
|
|
self._subscriptions = set()
|
|
|
|
|
|
|
|
|
|
# Если значение переменной None -- значит она обнулена.
|
|
|
|
|
self.value = None
|
|
|
|
|
self._invalidated = True
|
|
|
|
|
|
|
|
|
|
# Источник значения переменной, может быть значением, а может быть
|
|
|
|
|
# зависимостью.
|
|
|
|
@ -501,6 +532,10 @@ class VariableNode:
|
|
|
|
|
self.set_by_user = False
|
|
|
|
|
|
|
|
|
|
self._readonly = False
|
|
|
|
|
# Флаг имеющий значение только для переменных типа HashType.
|
|
|
|
|
# Предназначен для включения проверки соответствия полей хэша при
|
|
|
|
|
# установке значения.
|
|
|
|
|
self._fixed = False
|
|
|
|
|
|
|
|
|
|
def update_value(self) -> None:
|
|
|
|
|
'''Метод для обновления значения переменной с помощью указанного
|
|
|
|
@ -537,6 +572,25 @@ class VariableNode:
|
|
|
|
|
value = self._source
|
|
|
|
|
|
|
|
|
|
self.value = self.variable_type.process_value(value, self)
|
|
|
|
|
self._invalidated = False
|
|
|
|
|
|
|
|
|
|
def set_variable_type(self, variable_type, readonly=None, fixed=None):
|
|
|
|
|
'''Метод для установки типа переменной.'''
|
|
|
|
|
if readonly is not None and isinstance(readonly, bool):
|
|
|
|
|
self._readonly = readonly
|
|
|
|
|
elif fixed is not None and isinstance(fixed, bool):
|
|
|
|
|
self._fixed = fixed
|
|
|
|
|
|
|
|
|
|
if self.variable_type is VariableType:
|
|
|
|
|
if isinstance(variable_type, type):
|
|
|
|
|
if issubclass(variable_type, VariableType):
|
|
|
|
|
self.variable_type = variable_type
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError('variable type object must be'
|
|
|
|
|
' VariableType or its class method,'
|
|
|
|
|
' not {}'.format(type(variable_type)))
|
|
|
|
|
elif callable(variable_type):
|
|
|
|
|
variable_type(self)
|
|
|
|
|
|
|
|
|
|
def set(self, value):
|
|
|
|
|
self._invalidate()
|
|
|
|
@ -550,7 +604,8 @@ class VariableNode:
|
|
|
|
|
@source.setter
|
|
|
|
|
def source(self, source) -> None:
|
|
|
|
|
if self._readonly:
|
|
|
|
|
raise VariableError("can not change the variable '{}': read only")
|
|
|
|
|
raise VariableError("can not change the variable '{}': read only".
|
|
|
|
|
format(self.get_fullname()))
|
|
|
|
|
|
|
|
|
|
# Если источники не совпадают или текущее значение переменной было
|
|
|
|
|
# установлено пользователем, то инвалидируем переменную и меняем
|
|
|
|
@ -572,12 +627,22 @@ class VariableNode:
|
|
|
|
|
# self.update_value()
|
|
|
|
|
self._readonly = value
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def fixed(self) -> bool:
|
|
|
|
|
return self._fixed
|
|
|
|
|
|
|
|
|
|
@fixed.setter
|
|
|
|
|
def fixed(self, value) -> bool:
|
|
|
|
|
self._fixed = value
|
|
|
|
|
|
|
|
|
|
def _invalidate(self) -> None:
|
|
|
|
|
'''Метод для инвалидации данной переменной и всех зависящих от нее
|
|
|
|
|
переменных.'''
|
|
|
|
|
# print('{} is invalidated'.format(self.get_fullname()))
|
|
|
|
|
if self.value is not None and not self.set_by_user:
|
|
|
|
|
self.value = None
|
|
|
|
|
if not self._invalidated and not self.set_by_user:
|
|
|
|
|
if self.variable_type is not HashType:
|
|
|
|
|
self.value = None
|
|
|
|
|
self._invalidated = True
|
|
|
|
|
for subscriber in self.subscribers:
|
|
|
|
|
subscriber._invalidate()
|
|
|
|
|
|
|
|
|
@ -593,7 +658,7 @@ class VariableNode:
|
|
|
|
|
|
|
|
|
|
def get_value(self) -> Any:
|
|
|
|
|
'''Метод для получения значения переменной.'''
|
|
|
|
|
if self.value is None and not self.set_by_user:
|
|
|
|
|
if self._invalidated and not self.set_by_user:
|
|
|
|
|
self.update_value()
|
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
|
@ -641,8 +706,8 @@ class NamespaceNode:
|
|
|
|
|
def clear(self):
|
|
|
|
|
'''Метод для очистки пространства имен. Очищает и пространства имен
|
|
|
|
|
и переменные. Предназначен только для использования в calculate.ini.'''
|
|
|
|
|
for namespace in self.namespaces:
|
|
|
|
|
namespace.clear()
|
|
|
|
|
for namespace_name in self.namespaces.keys():
|
|
|
|
|
self.namespaces[namespace_name].clear()
|
|
|
|
|
self.variables.clear()
|
|
|
|
|
self.namespaces.clear()
|
|
|
|
|
|
|
|
|
@ -653,7 +718,7 @@ class NamespaceNode:
|
|
|
|
|
else:
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name: str) -> None:
|
|
|
|
|
def __getattr__(self, name: str):
|
|
|
|
|
'''Метод возвращает ноду пространства имен или значение переменной.'''
|
|
|
|
|
if name in self.namespaces:
|
|
|
|
|
return self.namespaces[name]
|
|
|
|
@ -747,14 +812,13 @@ class VariableAPI:
|
|
|
|
|
self.errors = []
|
|
|
|
|
|
|
|
|
|
def __call__(self, name: str, source=None, type=VariableType,
|
|
|
|
|
readonly=False, force=False):
|
|
|
|
|
readonly=False, fixed=False, force=False):
|
|
|
|
|
'''Метод для создания переменных внутри with Namespace('name').'''
|
|
|
|
|
if name not in self.current_namespace.variables:
|
|
|
|
|
print('CREATE VARIABLE: {}'.format('{}.{}'.format(
|
|
|
|
|
self.current_namespace.get_fullname(),
|
|
|
|
|
name)))
|
|
|
|
|
variable = VariableNode(name, self.current_namespace,
|
|
|
|
|
variable_type=type)
|
|
|
|
|
variable = VariableNode(name, self.current_namespace)
|
|
|
|
|
else:
|
|
|
|
|
print('MODIFY VARIABLE: {}'.format(name))
|
|
|
|
|
variable = self.current_namespace[name]
|
|
|
|
@ -770,7 +834,12 @@ class VariableAPI:
|
|
|
|
|
else:
|
|
|
|
|
variable.source = source
|
|
|
|
|
|
|
|
|
|
variable.readonly = readonly
|
|
|
|
|
if readonly:
|
|
|
|
|
variable.set_variable_type(type, readonly=True)
|
|
|
|
|
elif fixed:
|
|
|
|
|
variable.set_variable_type(type, fixed=True)
|
|
|
|
|
else:
|
|
|
|
|
variable.set_variable_type(type)
|
|
|
|
|
return variable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -798,15 +867,18 @@ class NamespaceAPI:
|
|
|
|
|
можно получить доступ к переменным.'''
|
|
|
|
|
return self._datavars
|
|
|
|
|
|
|
|
|
|
def set_root(self, datavars_root: NamespaceNode):
|
|
|
|
|
def set_datavars(self, datavars):
|
|
|
|
|
'''Метод для установки корневого пространства имен, которое пока что
|
|
|
|
|
будет использоваться для предоставления доступа к переменным.'''
|
|
|
|
|
self._datavars = datavars_root
|
|
|
|
|
self._datavars = datavars
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
'''Метод для сброса корневого пространства имен.'''
|
|
|
|
|
self._datavars = NamespaceNode('<root>')
|
|
|
|
|
if isinstance(self._datavars, NamespaceNode):
|
|
|
|
|
self._datavars = NamespaceNode('<root>')
|
|
|
|
|
else:
|
|
|
|
|
self._datavars.reset()
|
|
|
|
|
self.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|