|
|
|
|
# vim: fileencoding=utf-8
|
|
|
|
|
#
|
|
|
|
|
import re
|
|
|
|
|
import ast
|
|
|
|
|
import dis
|
|
|
|
|
from typing import List, Any, Union
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
from inspect import signature, getsource
|
|
|
|
|
from types import FunctionType, LambdaType
|
|
|
|
|
from calculate.utils.tools import Singleton
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DependenceError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableTypeError(VariableError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableNotFoundError(VariableError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CyclicVariableError(VariableError):
|
|
|
|
|
def __init__(self, *queue):
|
|
|
|
|
self.queue = queue
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return "Cyclic dependence in variables: {}".format(", ".join(
|
|
|
|
|
self.queue[:-1]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableType:
|
|
|
|
|
'''Базовый класс для типов.'''
|
|
|
|
|
name = 'undefined'
|
|
|
|
|
python_type = None
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
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 файлах.
|
|
|
|
|
'''
|
|
|
|
|
name = 'ini'
|
|
|
|
|
python_type: type = str
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(cls, value, variable):
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StringType(VariableType):
|
|
|
|
|
'''Класс, соответствующий типу переменных с строковым значением.'''
|
|
|
|
|
name = 'string'
|
|
|
|
|
python_type: type = str
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(cls, 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):
|
|
|
|
|
'''Класс, соответствующий типу переменных с целочисленным значением.'''
|
|
|
|
|
name = 'integer'
|
|
|
|
|
python_type: type = int
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(cls, value, variable) -> int:
|
|
|
|
|
if isinstance(value, int):
|
|
|
|
|
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):
|
|
|
|
|
'''Класс, соответствующий типу переменных с вещественным значением.'''
|
|
|
|
|
name = 'float'
|
|
|
|
|
python_type: type = float
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(cls, value, variable) -> float:
|
|
|
|
|
if isinstance(value, float):
|
|
|
|
|
return 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):
|
|
|
|
|
'''Класс, соответствующий типу переменных с булевым значением.'''
|
|
|
|
|
name = 'bool'
|
|
|
|
|
python_type: type = bool
|
|
|
|
|
true_values = {'True', 'true'}
|
|
|
|
|
false_values = {'False', 'false'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(cls, value, variable) -> bool:
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
return value
|
|
|
|
|
elif isinstance(value, str):
|
|
|
|
|
if value in cls.true_values:
|
|
|
|
|
return True
|
|
|
|
|
if value in cls.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):
|
|
|
|
|
name = 'list'
|
|
|
|
|
python_type: type = list
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(cls, value, variable) -> list:
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
return value
|
|
|
|
|
elif isinstance(value, str):
|
|
|
|
|
output_list = list()
|
|
|
|
|
values = value.split(',')
|
|
|
|
|
for value in values:
|
|
|
|
|
output_list.append(value.strip())
|
|
|
|
|
return output_list
|
|
|
|
|
try:
|
|
|
|
|
return list(value)
|
|
|
|
|
except Exception as error:
|
|
|
|
|
raise VariableTypeError("can not set value '{value}' to"
|
|
|
|
|
" list variable: {reason}".format(
|
|
|
|
|
value=value,
|
|
|
|
|
reason=str(error)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HashValue:
|
|
|
|
|
'''Класс значения хэша, передающий некоторые характеристики переменной
|
|
|
|
|
хозяина, что позволяет инвалидировать подписки переменной хозяина при любом
|
|
|
|
|
изменении хэша или инвалидировать весь хэш при изменении одной из
|
|
|
|
|
зависимостей.'''
|
|
|
|
|
def __init__(self, key: str, value, master_variable, parent):
|
|
|
|
|
self.key = key
|
|
|
|
|
self.value = value
|
|
|
|
|
self.master_variable = master_variable
|
|
|
|
|
self.parent = parent
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def subscriptions(self):
|
|
|
|
|
return self.master_variable.subscriptions
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def subscribers(self):
|
|
|
|
|
return self.master_variable.subscribers
|
|
|
|
|
|
|
|
|
|
def get_value(self) -> str:
|
|
|
|
|
'''Метод для получения значения хэша. Перед возвращением значения
|
|
|
|
|
обновляет себя на наиболее актуальную версию значения хэша.'''
|
|
|
|
|
self = self.master_variable.get_value()[self.key]
|
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
|
|
def set(self, value) -> None:
|
|
|
|
|
'''Метод для задания одного значения хэша без изменения источника
|
|
|
|
|
значения.'''
|
|
|
|
|
current_hash = self.master_variable.get_value().get_hash()
|
|
|
|
|
current_hash[self.key] = value
|
|
|
|
|
self.master_variable.set(current_hash)
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
'''Метод для сброса значения, установленного поверх источника.'''
|
|
|
|
|
self.master_variable.reset()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Hash:
|
|
|
|
|
'''Класс реализующий контейнер для хранения хэша в переменной
|
|
|
|
|
соответствующего типа.'''
|
|
|
|
|
def __init__(self, values: dict, master_variable, parent=None):
|
|
|
|
|
self.fixed = master_variable.fixed
|
|
|
|
|
self._values = dict()
|
|
|
|
|
self._fields = set()
|
|
|
|
|
for key, value in values.items():
|
|
|
|
|
self._values.update({key: HashValue(key, value, master_variable,
|
|
|
|
|
self)})
|
|
|
|
|
if self.fixed:
|
|
|
|
|
self._fields.add(key)
|
|
|
|
|
self.master_variable = master_variable
|
|
|
|
|
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:
|
|
|
|
|
'''Метод для обновления значения хэша.'''
|
|
|
|
|
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:
|
|
|
|
|
return self._values[key].get_value()
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError(("'{key}' is not found in the hash"
|
|
|
|
|
" '{hash_name}'").format(
|
|
|
|
|
key=key,
|
|
|
|
|
hash_name=self.master_variable.get_fullname()))
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key: str) -> HashValue:
|
|
|
|
|
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.master_variable.get_fullname()))
|
|
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
|
for key in self._values.keys():
|
|
|
|
|
yield key
|
|
|
|
|
|
|
|
|
|
def __contains__(self, key: str) -> bool:
|
|
|
|
|
return key in self._values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HashType(VariableType):
|
|
|
|
|
'''Класс, соответствующий типу переменных хэшей.'''
|
|
|
|
|
name = 'hash'
|
|
|
|
|
python_type: type = dict
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
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(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:
|
|
|
|
|
'''Класс, соответствующий типу переменных таблиц.'''
|
|
|
|
|
def __init__(self, values: List[dict], master_variable, fields=None):
|
|
|
|
|
self._rows = list()
|
|
|
|
|
self.master_variable = master_variable
|
|
|
|
|
|
|
|
|
|
self.columns = set()
|
|
|
|
|
if fields is not None:
|
|
|
|
|
self.columns.update(self.fields)
|
|
|
|
|
else:
|
|
|
|
|
self.columns = set(values[0].keys())
|
|
|
|
|
|
|
|
|
|
for row in values:
|
|
|
|
|
if isinstance(row, dict):
|
|
|
|
|
self._check_columns(row)
|
|
|
|
|
self._rows.append(row)
|
|
|
|
|
else:
|
|
|
|
|
raise VariableTypeError("can not create table using value '{}'"
|
|
|
|
|
" with type '{}'".format(row,
|
|
|
|
|
type(row)))
|
|
|
|
|
|
|
|
|
|
def get_table(self) -> List[dict]:
|
|
|
|
|
'''Метод для получения всего списка строк таблицы.'''
|
|
|
|
|
return self._rows
|
|
|
|
|
|
|
|
|
|
def add_row(self, row: dict):
|
|
|
|
|
'''Метод для добавления строк в таблицу.'''
|
|
|
|
|
self._check_columns(row)
|
|
|
|
|
self._rows.append(row)
|
|
|
|
|
|
|
|
|
|
def change_row(self, row: dict, index: int) -> None:
|
|
|
|
|
'''Метод для замены существующей строки.'''
|
|
|
|
|
self._check_columns(row)
|
|
|
|
|
self._rows[index] = row
|
|
|
|
|
|
|
|
|
|
def clear(self) -> None:
|
|
|
|
|
self._rows.clear()
|
|
|
|
|
|
|
|
|
|
def _check_columns(self, row: dict) -> None:
|
|
|
|
|
'''Метод для проверки наличия в хэше только тех полей, которые
|
|
|
|
|
соответствуют заданным для таблицы колонкам.'''
|
|
|
|
|
for column in row:
|
|
|
|
|
if column not in self.columns:
|
|
|
|
|
raise VariableError("unknown column value '{}'"
|
|
|
|
|
" available: '{}'".format(
|
|
|
|
|
column,
|
|
|
|
|
', '.join(self.columns)))
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, index: int) -> Hash:
|
|
|
|
|
if isinstance(index, int):
|
|
|
|
|
if index < len(self._rows):
|
|
|
|
|
return self._rows[index]
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError("'{index}' index value is out of range"
|
|
|
|
|
.format(index=index))
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError("Table value is not subscriptable")
|
|
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
|
for row in self._rows:
|
|
|
|
|
yield row
|
|
|
|
|
|
|
|
|
|
def __contains__(self, key: str) -> bool:
|
|
|
|
|
if isinstance(key, str):
|
|
|
|
|
return key in self._values
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
|
return len(self._rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TableType(VariableType):
|
|
|
|
|
name = 'table'
|
|
|
|
|
# TODO Сомнительно.
|
|
|
|
|
python_type: type = list
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def process_value(self, value: List[dict], variable) -> Table:
|
|
|
|
|
if not isinstance(value, list) and not isinstance(value, Table):
|
|
|
|
|
raise VariableTypeError("can not set value with type '{_type}' to"
|
|
|
|
|
" hash variable: value must be 'dict' type"
|
|
|
|
|
.format(_type=type(value)))
|
|
|
|
|
else:
|
|
|
|
|
if isinstance(value, Table):
|
|
|
|
|
return value
|
|
|
|
|
return Table(value, variable)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Static:
|
|
|
|
|
'''Класс для указания в качестве аргументов зависимостей статичных
|
|
|
|
|
значений, а не только переменных.'''
|
|
|
|
|
def __init__(self, value):
|
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
|
|
def get_value(self):
|
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableWrapper:
|
|
|
|
|
'''Класс обертки для переменных, с помощью которого отслеживается
|
|
|
|
|
применение переменной в образовании значения переменной от нее зависящей.
|
|
|
|
|
'''
|
|
|
|
|
def __init__(self, variable, subscriptions):
|
|
|
|
|
self._variable = variable
|
|
|
|
|
self._subscriptions = subscriptions
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def value(self):
|
|
|
|
|
'''Метод возвращающий значение переменной и при этом добавляющий его в
|
|
|
|
|
подписки.'''
|
|
|
|
|
if not isinstance(self._variable, Static):
|
|
|
|
|
self._subscriptions.add(self._variable)
|
|
|
|
|
value = self._variable.get_value()
|
|
|
|
|
if isinstance(value, Hash):
|
|
|
|
|
value = value.get_hash()
|
|
|
|
|
elif isinstance(value, Table):
|
|
|
|
|
value = value.get_table()
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def subscriptions(self):
|
|
|
|
|
return self._subscriptions
|
|
|
|
|
|
|
|
|
|
@subscriptions.setter
|
|
|
|
|
def subscriptions(self, subscriptions):
|
|
|
|
|
self._subscriptions = subscriptions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DependenceSource:
|
|
|
|
|
'''Класс зависимости как источника значения переменной.'''
|
|
|
|
|
def __init__(self, variables: tuple, depend=None):
|
|
|
|
|
self.error = None
|
|
|
|
|
self._args = variables
|
|
|
|
|
self.depend_function = depend
|
|
|
|
|
self._subscriptions = set()
|
|
|
|
|
self._args_founded = False
|
|
|
|
|
|
|
|
|
|
def check(self) -> None:
|
|
|
|
|
'''Метод для запуска проверки корректности функции зависимости, а также
|
|
|
|
|
сопоставления ее числу заданных зависимостей.'''
|
|
|
|
|
if self.depend_function is None:
|
|
|
|
|
if len(self._args) > 1:
|
|
|
|
|
raise DependenceError('the depend function is needed if the'
|
|
|
|
|
' number of dependencies is more than'
|
|
|
|
|
' one')
|
|
|
|
|
elif len(self._args) == 0:
|
|
|
|
|
raise DependenceError('dependence is set without variables')
|
|
|
|
|
else:
|
|
|
|
|
self._check_function(self.depend_function)
|
|
|
|
|
|
|
|
|
|
def calculate_value(self) -> Any:
|
|
|
|
|
'''Метод для расчета значения переменной с использованием зависимостей
|
|
|
|
|
и заданной для них функции.'''
|
|
|
|
|
self._subscriptions = set()
|
|
|
|
|
args = tuple(VariableWrapper(arg, self._subscriptions)
|
|
|
|
|
for arg in self._args)
|
|
|
|
|
try:
|
|
|
|
|
if self.depend_function is None:
|
|
|
|
|
return args[-1].value
|
|
|
|
|
return self.depend_function(*args)
|
|
|
|
|
except CyclicVariableError:
|
|
|
|
|
raise
|
|
|
|
|
except Exception as error:
|
|
|
|
|
raise DependenceError('can not calculate using dependencies: {}'
|
|
|
|
|
' reason: {}'.format(', '.join(
|
|
|
|
|
[subscription.get_fullname()
|
|
|
|
|
for subscription
|
|
|
|
|
in self._args]),
|
|
|
|
|
str(error)))
|
|
|
|
|
|
|
|
|
|
def _get_args(self, namespace):
|
|
|
|
|
'''Метод для преобразования списка аргументов функции зависимости,
|
|
|
|
|
содержащего переменные и строки, в список аргументов состоящий только
|
|
|
|
|
из нод переменных и значений хэшей.'''
|
|
|
|
|
if not self._args_founded:
|
|
|
|
|
self._args = self.find_variables(self._args, namespace)
|
|
|
|
|
self._args_founded = True
|
|
|
|
|
|
|
|
|
|
def find_variables(self, variables, namespace):
|
|
|
|
|
'''Метод для поиска переменных по заданным путям к ним.'''
|
|
|
|
|
output = []
|
|
|
|
|
for variable in variables:
|
|
|
|
|
if isinstance(variable, str):
|
|
|
|
|
variable = Dependence._get_variable(
|
|
|
|
|
variable,
|
|
|
|
|
current_namespace=namespace)
|
|
|
|
|
if variable is None:
|
|
|
|
|
raise DependenceError("variable '{}' not found".
|
|
|
|
|
format(variable))
|
|
|
|
|
output.append(variable)
|
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def subscriptions(self) -> set:
|
|
|
|
|
return self._subscriptions
|
|
|
|
|
|
|
|
|
|
def _check_function(self, function_to_check: FunctionType) -> None:
|
|
|
|
|
'''Метод для проверки того, возращает ли функция какое-либо значение, а
|
|
|
|
|
также того, совпадает ли число подписок с числом аргументов этой
|
|
|
|
|
функции.'''
|
|
|
|
|
if not isinstance(function_to_check, LambdaType):
|
|
|
|
|
# Если функция не лямбда, проверяем есть ли у нее возвращаемое
|
|
|
|
|
# значение.
|
|
|
|
|
for node in ast.walk(ast.parse(getsource(function_to_check))):
|
|
|
|
|
if isinstance(node, ast.Return):
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError("the depend function does not return"
|
|
|
|
|
" anything in variable")
|
|
|
|
|
|
|
|
|
|
# Проверяем совпадение количества аргументов функции и заданных для
|
|
|
|
|
# функции переменных.
|
|
|
|
|
self.check_signature(function_to_check, self._args)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def check_signature(function_to_check, arguments):
|
|
|
|
|
'''Метод для проверки соответствия сигнатуры функции и заданного для
|
|
|
|
|
нее набора аргументов.'''
|
|
|
|
|
function_signature = signature(function_to_check)
|
|
|
|
|
if not len(arguments) == len(function_signature.parameters):
|
|
|
|
|
raise DependenceError("the depend function takes {} arguments,"
|
|
|
|
|
" while {} is given".format(
|
|
|
|
|
len(function_signature.parameters),
|
|
|
|
|
len(arguments)))
|
|
|
|
|
|
|
|
|
|
def __ne__(self, other) -> bool:
|
|
|
|
|
if not isinstance(other, DependenceSource):
|
|
|
|
|
return True
|
|
|
|
|
return not self == other
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other) -> bool:
|
|
|
|
|
if not isinstance(other, DependenceSource):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Сначала сравниваем аргументы.
|
|
|
|
|
for l_var, r_var in zip(self._args, other._args):
|
|
|
|
|
if l_var != r_var:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if not self._compare_depend_functions(self.depend_function,
|
|
|
|
|
other.depend_function):
|
|
|
|
|
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=VariableType,
|
|
|
|
|
source=None):
|
|
|
|
|
self.name = name
|
|
|
|
|
if issubclass(variable_type, VariableType):
|
|
|
|
|
self.variable_type = variable_type
|
|
|
|
|
else:
|
|
|
|
|
raise VariableTypeError('variable_type must be VariableType'
|
|
|
|
|
', but not {}'.format(type(variable_type)))
|
|
|
|
|
self.calculating = False
|
|
|
|
|
|
|
|
|
|
self.namespace = namespace
|
|
|
|
|
self.namespace.add_variable(self)
|
|
|
|
|
|
|
|
|
|
self.subscribers = set()
|
|
|
|
|
# Список текущих подписок, для проверки их актуальности при
|
|
|
|
|
# динамическом связывании.
|
|
|
|
|
self._subscriptions = set()
|
|
|
|
|
|
|
|
|
|
self.value = None
|
|
|
|
|
self._invalidated = True
|
|
|
|
|
|
|
|
|
|
# Флаг имеющий значение только для переменных типа HashType.
|
|
|
|
|
# Предназначен для включения проверки соответствия полей хэша при
|
|
|
|
|
# установке значения.
|
|
|
|
|
self._fixed = False
|
|
|
|
|
|
|
|
|
|
# Источник значения переменной, может быть значением, а может быть
|
|
|
|
|
# зависимостью.
|
|
|
|
|
self._source = source
|
|
|
|
|
if source is not None:
|
|
|
|
|
self.update_value()
|
|
|
|
|
|
|
|
|
|
# Флаг, указывающий, что значение было изменено в процессе работы
|
|
|
|
|
# утилит или с помощью тега set из шаблона.
|
|
|
|
|
self.set_by_user = False
|
|
|
|
|
|
|
|
|
|
self._readonly = False
|
|
|
|
|
|
|
|
|
|
def update_value(self) -> None:
|
|
|
|
|
'''Метод для обновления значения переменной с помощью указанного
|
|
|
|
|
источника ее значения.'''
|
|
|
|
|
if self.calculating:
|
|
|
|
|
raise CyclicVariableError(self.name)
|
|
|
|
|
|
|
|
|
|
if self._source is None:
|
|
|
|
|
raise VariableError("No sources to update variable '{}'".
|
|
|
|
|
format(self.get_fullname()))
|
|
|
|
|
|
|
|
|
|
if isinstance(self._source, DependenceSource):
|
|
|
|
|
self._source._get_args(self.namespace)
|
|
|
|
|
with self._start_calculate():
|
|
|
|
|
try:
|
|
|
|
|
value = self._source.calculate_value()
|
|
|
|
|
# Обновляем подписки. Сначала убираем лишние.
|
|
|
|
|
for subscription in self._subscriptions:
|
|
|
|
|
if subscription not in self._source.subscriptions:
|
|
|
|
|
subscription.subscribers.remove(self)
|
|
|
|
|
|
|
|
|
|
# Теперь добавляем новые.
|
|
|
|
|
for subscription in self._source.subscriptions:
|
|
|
|
|
subscription.subscribers.add(self)
|
|
|
|
|
self._subscriptions = self._source.subscriptions
|
|
|
|
|
except CyclicVariableError as error:
|
|
|
|
|
raise CyclicVariableError(self.name, *error.queue)
|
|
|
|
|
except DependenceError as error:
|
|
|
|
|
raise VariableError('{}: {}'.format(self.get_fullname(),
|
|
|
|
|
str(error)))
|
|
|
|
|
else:
|
|
|
|
|
value = self._source
|
|
|
|
|
|
|
|
|
|
self.value = self.variable_type.process_value(value, self)
|
|
|
|
|
self._invalidated = False
|
|
|
|
|
|
|
|
|
|
def set_variable_type(self, variable_type: VariableType,
|
|
|
|
|
readonly: str = None, fixed: str = None) -> 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._invalidated = False
|
|
|
|
|
self.set_by_user = False
|
|
|
|
|
|
|
|
|
|
self.value = self.variable_type.process_value(value, self)
|
|
|
|
|
self._invalidate(set_by_user=True)
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
'''Метод для сброса пользовательского значения.'''
|
|
|
|
|
if self.set_by_user:
|
|
|
|
|
self._invalidated = False
|
|
|
|
|
self.set_by_user = False
|
|
|
|
|
self._invalidate()
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def source(self):
|
|
|
|
|
return self._source
|
|
|
|
|
|
|
|
|
|
@source.setter
|
|
|
|
|
def source(self, source) -> None:
|
|
|
|
|
# Если источники не совпадают или текущее значение переменной было
|
|
|
|
|
# установлено пользователем, то инвалидируем переменную и меняем
|
|
|
|
|
# источник.
|
|
|
|
|
if self._source != source or self.set_by_user:
|
|
|
|
|
self.set_by_user = False
|
|
|
|
|
self._invalidate()
|
|
|
|
|
self._source = source
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def readonly(self) -> bool:
|
|
|
|
|
return self._readonly
|
|
|
|
|
|
|
|
|
|
@readonly.setter
|
|
|
|
|
def readonly(self, value: bool) -> None:
|
|
|
|
|
# if self.value is None and self._source is not None:
|
|
|
|
|
# # TODO выводить предупреждение если переменная инвалидирована,
|
|
|
|
|
# # нет источника и при этом устанавливается флаг readonly.
|
|
|
|
|
# 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, set_by_user=False) -> None:
|
|
|
|
|
'''Метод для инвалидации данной переменной и всех зависящих от нее
|
|
|
|
|
переменных.'''
|
|
|
|
|
if not self._invalidated and not self.set_by_user:
|
|
|
|
|
self._invalidated = True
|
|
|
|
|
self.set_by_user = set_by_user
|
|
|
|
|
|
|
|
|
|
for subscriber in self.subscribers:
|
|
|
|
|
subscriber._invalidate()
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
|
def _start_calculate(self):
|
|
|
|
|
'''Менеджер контекста устанавливающий флаг, указывающий, что данная
|
|
|
|
|
переменная в состоянии расчета. В данном случае необходима только для
|
|
|
|
|
того, чтобы при получении значения параметра внутри метода для расчета
|
|
|
|
|
не иницировалось ее обновление, которое может привести к рекурсии.'''
|
|
|
|
|
try:
|
|
|
|
|
self.calculating = True
|
|
|
|
|
yield self
|
|
|
|
|
finally:
|
|
|
|
|
self.calculating = False
|
|
|
|
|
|
|
|
|
|
def get_value(self) -> Any:
|
|
|
|
|
'''Метод для получения значения переменной.'''
|
|
|
|
|
if self._invalidated and self._source is None and self._value is None:
|
|
|
|
|
return None
|
|
|
|
|
if self._invalidated and not self.set_by_user:
|
|
|
|
|
self.update_value()
|
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
|
|
def get_fullname(self) -> str:
|
|
|
|
|
if self.namespace:
|
|
|
|
|
return "{}.{}".format(self.namespace.get_fullname(), self.name)
|
|
|
|
|
else:
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key: str) -> HashValue:
|
|
|
|
|
if self.variable_type is HashType:
|
|
|
|
|
if key in self.get_value():
|
|
|
|
|
return self.value[key]
|
|
|
|
|
else:
|
|
|
|
|
raise VariableNotFoundError("value '{}' is not found in hash"
|
|
|
|
|
" variable '{}'".format(
|
|
|
|
|
key, self.get_fullname()))
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError("'{}' variable type is not subscriptable.".
|
|
|
|
|
format(self.variable_type.name))
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return '<Variable: {} with value: {}>'.format(self.get_fullname(),
|
|
|
|
|
self.value or
|
|
|
|
|
'INVALIDATED')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamespaceNode:
|
|
|
|
|
'''Класс ноды соответствующей пространству имен в дереве переменных.'''
|
|
|
|
|
def __init__(self, name='', parent=None):
|
|
|
|
|
self.name = name
|
|
|
|
|
self.variables = dict()
|
|
|
|
|
self.namespaces = dict()
|
|
|
|
|
self.parent = parent
|
|
|
|
|
|
|
|
|
|
def add_variable(self, variable: VariableNode) -> None:
|
|
|
|
|
'''Метод для добавления переменной в пространство имен.'''
|
|
|
|
|
if variable.name in self.namespaces:
|
|
|
|
|
raise VariableError("namespace with the name '{}' is already in"
|
|
|
|
|
" the namespace '{}'".format(
|
|
|
|
|
variable.name,
|
|
|
|
|
self.get_fullname()))
|
|
|
|
|
self.variables.update({variable.name: variable})
|
|
|
|
|
variable.namespace = self
|
|
|
|
|
|
|
|
|
|
def add_namespace(self, namespace) -> None:
|
|
|
|
|
'''Метод для добавления пространства имен в пространство имен.'''
|
|
|
|
|
if namespace.name in self.variables:
|
|
|
|
|
raise VariableError("variable with the name '{}' is already in"
|
|
|
|
|
" the namespace '{}'".format(
|
|
|
|
|
namespace.name,
|
|
|
|
|
self.get_fullname()))
|
|
|
|
|
self.namespaces.update({namespace.name: namespace})
|
|
|
|
|
namespace.parent = self
|
|
|
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
|
'''Метод для очистки пространства имен. Очищает и пространства имен
|
|
|
|
|
и переменные. Предназначен только для использования в calculate.ini.'''
|
|
|
|
|
for namespace_name in self.namespaces.keys():
|
|
|
|
|
self.namespaces[namespace_name].clear()
|
|
|
|
|
self.variables.clear()
|
|
|
|
|
self.namespaces.clear()
|
|
|
|
|
|
|
|
|
|
def get_fullname(self) -> str:
|
|
|
|
|
'''Метод для получения полного имени пространства имен.'''
|
|
|
|
|
if self.parent is not None and self.parent.name != '<root>':
|
|
|
|
|
return '{}.{}'.format(self.parent.get_fullname(), self.name)
|
|
|
|
|
else:
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
def get_package_name(self) -> str:
|
|
|
|
|
if self.parent.name == '<root>':
|
|
|
|
|
return self.name
|
|
|
|
|
else:
|
|
|
|
|
return self.parent.get_package_name()
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name: str):
|
|
|
|
|
'''Метод возвращает ноду пространства имен или значение переменной.'''
|
|
|
|
|
if name in self.namespaces:
|
|
|
|
|
return self.namespaces[name]
|
|
|
|
|
elif name in self.variables:
|
|
|
|
|
variable = self.variables[name]
|
|
|
|
|
if variable.variable_type is TableType:
|
|
|
|
|
return variable.get_value().get_table()
|
|
|
|
|
return variable.get_value()
|
|
|
|
|
else:
|
|
|
|
|
raise VariableNotFoundError("'{variable_name}' is not found in the"
|
|
|
|
|
" namespace '{namespace_name}'".format(
|
|
|
|
|
variable_name=name,
|
|
|
|
|
namespace_name=self.name))
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, name: str) -> None:
|
|
|
|
|
'''Метод возвращает ноду пространства имен или ноду переменной.'''
|
|
|
|
|
if name in self.namespaces:
|
|
|
|
|
return self.namespaces[name]
|
|
|
|
|
elif name in self.variables:
|
|
|
|
|
return self.variables[name]
|
|
|
|
|
else:
|
|
|
|
|
raise VariableNotFoundError("'{variable_name}' is not found in the"
|
|
|
|
|
" namespace '{namespace_name}'".format(
|
|
|
|
|
variable_name=name,
|
|
|
|
|
namespace_name=self.name))
|
|
|
|
|
|
|
|
|
|
def __contains__(self, name):
|
|
|
|
|
return name in self.namespaces or name in self.variables
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return '<Namespace: {}>'.format(self.get_fullname())
|
|
|
|
|
|
|
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DependenceAPI(metaclass=Singleton):
|
|
|
|
|
'''Класс образующий интерфейс для создания зависимостей.'''
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.current_namespace = None
|
|
|
|
|
self.datavars_root = None
|
|
|
|
|
|
|
|
|
|
def __call__(self, *variables, depend=None):
|
|
|
|
|
subscriptions = list()
|
|
|
|
|
for variable in variables:
|
|
|
|
|
if not (isinstance(variable, str) or
|
|
|
|
|
isinstance(variable, VariableNode) or
|
|
|
|
|
isinstance(variable, Static)):
|
|
|
|
|
variable = Static(variable)
|
|
|
|
|
subscriptions.append(variable)
|
|
|
|
|
return DependenceSource(subscriptions, depend=depend)
|
|
|
|
|
|
|
|
|
|
def _get_variable(self, variable_name, current_namespace=None):
|
|
|
|
|
'''Метод для поиска переменной в пространствах имен.'''
|
|
|
|
|
if current_namespace is None:
|
|
|
|
|
current_namespace = self.current_namespace
|
|
|
|
|
return self.find_variable(variable_name, self.datavars_root,
|
|
|
|
|
current_namespace=current_namespace)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def find_variable(variable_name, datavars_root, current_namespace=None):
|
|
|
|
|
'''Метод для поиска переменных по строковому пути от корня переменных
|
|
|
|
|
или от текущего пространства имен.'''
|
|
|
|
|
name_parts = variable_name.split('.')
|
|
|
|
|
if not name_parts[0]:
|
|
|
|
|
if current_namespace is not None:
|
|
|
|
|
namespace = current_namespace
|
|
|
|
|
for index in range(1, len(name_parts)):
|
|
|
|
|
if not name_parts[index]:
|
|
|
|
|
namespace = namespace.parent
|
|
|
|
|
else:
|
|
|
|
|
name_parts = name_parts[index:]
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
raise VariableNotFoundError(
|
|
|
|
|
f"variable '{variable_name}' is not found.")
|
|
|
|
|
else:
|
|
|
|
|
namespace = datavars_root
|
|
|
|
|
|
|
|
|
|
search_result = namespace
|
|
|
|
|
for part in name_parts:
|
|
|
|
|
search_result = search_result[part]
|
|
|
|
|
return search_result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CopyAPI(metaclass=Singleton):
|
|
|
|
|
'''Класс для создания зависимостей представляющих собой простое копирование
|
|
|
|
|
значения переменной в зависимую переменную.'''
|
|
|
|
|
def __call__(self, variable: Union[str, VariableNode]):
|
|
|
|
|
return Dependence(variable)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FormatAPI(metaclass=Singleton):
|
|
|
|
|
'''Класс для создания зависимостей представляющих собой форматируемую
|
|
|
|
|
строку с указанием переменных в тех местах, где их значения должны быть
|
|
|
|
|
подставлены в строку.'''
|
|
|
|
|
pattern = re.compile(
|
|
|
|
|
r'{\s*([a-zA-Z][a-zA-Z_0-9]+)?(.[a-zA-Z][a-zA-Z_0-9]+)+\s*}')
|
|
|
|
|
|
|
|
|
|
def __call__(self, string: str):
|
|
|
|
|
vars_list = []
|
|
|
|
|
|
|
|
|
|
def subfunc(matchobj):
|
|
|
|
|
vars_list.append(matchobj.group(0)[1:-1].strip())
|
|
|
|
|
return '{}'
|
|
|
|
|
format_string = self.pattern.sub(subfunc, string)
|
|
|
|
|
|
|
|
|
|
def depend_function(*args):
|
|
|
|
|
values = [arg.value for arg in args]
|
|
|
|
|
return format_string.format(*values)
|
|
|
|
|
|
|
|
|
|
return Dependence(*vars_list, depend=depend_function)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CalculateAPI(metaclass=Singleton):
|
|
|
|
|
'''Метод для создания зависимостей, представляющих собой функцию для
|
|
|
|
|
вычисления значения зависимой переменной на основе значений указанных
|
|
|
|
|
переменных.'''
|
|
|
|
|
def __call__(self, *args):
|
|
|
|
|
depend_function = args[0]
|
|
|
|
|
return Dependence(*args[1:], depend=depend_function)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableAPI(metaclass=Singleton):
|
|
|
|
|
'''Класс для создания переменных при задании их через
|
|
|
|
|
python-скрипты.'''
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.current_namespace = None
|
|
|
|
|
# TODO Продумать другой способ обработки ошибок.
|
|
|
|
|
self.errors = []
|
|
|
|
|
|
|
|
|
|
def __call__(self, name: str, source=None, type=VariableType,
|
|
|
|
|
readonly=False, fixed=False, force=False):
|
|
|
|
|
'''Метод для создания переменных внутри 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, DependenceSource):
|
|
|
|
|
try:
|
|
|
|
|
source.check()
|
|
|
|
|
variable.source = source
|
|
|
|
|
except DependenceError as error:
|
|
|
|
|
raise VariableError('Dependence error: {} in variable: {}'.
|
|
|
|
|
format(str(error),
|
|
|
|
|
variable.get_fullname()))
|
|
|
|
|
else:
|
|
|
|
|
variable.source = source
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamespaceAPI(metaclass=Singleton):
|
|
|
|
|
'''Класс для создания пространств имен при задании переменных через
|
|
|
|
|
python-скрипты.'''
|
|
|
|
|
def __init__(self, var_fabric: VariableAPI,
|
|
|
|
|
dependence_fabric: DependenceAPI(),
|
|
|
|
|
datavars_root=NamespaceNode('<root>')):
|
|
|
|
|
self._datavars = datavars_root
|
|
|
|
|
self.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
# Привязываем фабрику переменных.
|
|
|
|
|
self._variables_fabric = var_fabric
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
# Привязываем фабрику зависимостей.
|
|
|
|
|
self._dependence_fabric = dependence_fabric
|
|
|
|
|
self._dependence_fabric.current_namespace = self._datavars
|
|
|
|
|
self._dependence_fabric.datavars_root = self._datavars
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def datavars(self):
|
|
|
|
|
'''Метод для получения корневого пространства имен, через которое далее
|
|
|
|
|
можно получить доступ к переменным.'''
|
|
|
|
|
return self._datavars
|
|
|
|
|
|
|
|
|
|
def set_datavars(self, datavars):
|
|
|
|
|
'''Метод для установки корневого пространства имен, которое пока что
|
|
|
|
|
будет использоваться для предоставления доступа к переменным.'''
|
|
|
|
|
self._datavars = datavars
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
'''Метод для сброса корневого пространства имен.'''
|
|
|
|
|
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
|
|
|
|
|
self._dependence_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
def set_current_namespace(self, namespace: NamespaceNode):
|
|
|
|
|
'''Метод для установки текущего пространства имен, в которое будут
|
|
|
|
|
добавляться далее переменные и пространства имен.'''
|
|
|
|
|
self.current_namespace = namespace
|
|
|
|
|
self._variables_fabric.current_namespace = namespace
|
|
|
|
|
self._dependence_fabric.current_namespace = namespace
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
|
def __call__(self, namespace_name):
|
|
|
|
|
'''Метод для создания пространств имен с помощью with.'''
|
|
|
|
|
if namespace_name not in self.current_namespace.namespaces:
|
|
|
|
|
namespace = NamespaceNode(namespace_name,
|
|
|
|
|
parent=self.current_namespace)
|
|
|
|
|
else:
|
|
|
|
|
namespace = self.current_namespace.namespaces[namespace_name]
|
|
|
|
|
|
|
|
|
|
self.current_namespace.add_namespace(namespace)
|
|
|
|
|
self.current_namespace = namespace
|
|
|
|
|
|
|
|
|
|
# Устанавливаем текущее пространство имен фабрике переменных.
|
|
|
|
|
self._variables_fabric.current_namespace = self.current_namespace
|
|
|
|
|
|
|
|
|
|
# Устанавливаем текущее пространство имен фабрике зависимостей.
|
|
|
|
|
self._dependence_fabric.current_namespace = namespace
|
|
|
|
|
self._dependence_fabric.datavars_root = self._datavars
|
|
|
|
|
try:
|
|
|
|
|
yield self
|
|
|
|
|
finally:
|
|
|
|
|
self.current_namespace = self.current_namespace.parent
|
|
|
|
|
self._variables_fabric.current_namespace = self.current_namespace
|
|
|
|
|
self._dependence_fabric.current_namespace = self.current_namespace
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dependence = DependenceAPI()
|
|
|
|
|
Copy = CopyAPI()
|
|
|
|
|
Format = FormatAPI()
|
|
|
|
|
Calculate = CalculateAPI()
|
|
|
|
|
|
|
|
|
|
Variable = VariableAPI()
|
|
|
|
|
Namespace = NamespaceAPI(Variable, Dependence)
|