Implemented dynamic subscribing for variables of the table and hash types.

packages
Иванов Денис 4 years ago
parent 0a5776ff85
commit 91d3f4f3cb

@ -1,6 +1,6 @@
import ast import ast
import dis import dis
from typing import List, Union from typing import List, Union, Any
from contextlib import contextmanager from contextlib import contextmanager
from inspect import signature, getsource from inspect import signature, getsource
from types import FunctionType, LambdaType from types import FunctionType, LambdaType
@ -160,12 +160,10 @@ class HashValue:
def subscribers(self): def subscribers(self):
return self.master_variable.subscribers return self.master_variable.subscribers
def get_value(self): def get_value(self) -> str:
if self.master_variable.variable_type is HashType: '''Метод для получения значения хэша. Перед возвращением значения
self = self.master_variable.get_value()[self.key] обновляет себя на наиболее актуальную версию значения хэша.'''
elif self.master_variable.variable_type is TableType: self = self.master_variable.get_value()[self.key]
self = self.master_variable.get_value()[
self.parent.row_index][self.key]
return self.value return self.value
@ -180,6 +178,12 @@ class Hash:
self.master_variable = master_variable self.master_variable = master_variable
self.row_index = None 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 __getattr__(self, key: str): def __getattr__(self, key: str):
'''Метод возвращает ноду пространства имен или значение переменной.''' '''Метод возвращает ноду пространства имен или значение переменной.'''
if key in self._values: if key in self._values:
@ -222,8 +226,7 @@ class HashType(VariableType):
class Table: class Table:
'''Класс, соответствующий типу переменных таблиц.''' '''Класс, соответствующий типу переменных таблиц.'''
def __init__(self, values: Union[List[Hash], List[dict]], def __init__(self, values: List[dict], master_variable, fields=None):
master_variable, fields=None):
self._rows = list() self._rows = list()
self.master_variable = master_variable self.master_variable = master_variable
@ -231,21 +234,20 @@ class Table:
if fields is not None: if fields is not None:
self.columns.update(self.fields) self.columns.update(self.fields)
else: else:
self.columns = set(values[0]) self.columns = set(values[0].keys())
for row in values: for row in values:
if isinstance(row, Hash): if isinstance(row, dict):
self._check_columns(row) self._check_columns(row)
self._rows.append(row) self._rows.append(row)
elif isinstance(row, dict):
self._check_columns(row)
row_hash = Hash(row, master_variable)
self._rows.append(row_hash)
else: else:
raise VariableTypeError("can not create table using value '{}'" raise VariableTypeError("can not create table using value '{}'"
" with type '{}'".format(row, " with type '{}'".format(row,
type(row))) type(row)))
def get_table(self):
return self._rows
def _check_columns(self, value: Union[List[Hash], List[dict]]) -> None: def _check_columns(self, value: Union[List[Hash], List[dict]]) -> None:
'''Метод для проверки наличия в хэше только тех полей, которые '''Метод для проверки наличия в хэше только тех полей, которые
соответствуют заданным для таблицы колонкам.''' соответствуют заданным для таблицы колонкам.'''
@ -280,8 +282,7 @@ class TableType(VariableType):
name = 'table' name = 'table'
@classmethod @classmethod
def process_value(self, value: Union[Table, List[dict], List[Hash]], def process_value(self, value: List[dict], variable) -> Table:
variable) -> Table:
print('PROCESS TABLE') print('PROCESS TABLE')
if not isinstance(value, list) and not isinstance(value, Table): if not isinstance(value, list) and not isinstance(value, Table):
raise VariableTypeError("can not set value with type '{_type}' to" raise VariableTypeError("can not set value with type '{_type}' to"
@ -294,14 +295,22 @@ class TableType(VariableType):
class VariableWrapper: class VariableWrapper:
'''Класс обертки для переменных, с помощью которого отслеживается
применение переменной в образовании значения переменной от нее зависящей.
'''
def __init__(self, variable, subscriptions): def __init__(self, variable, subscriptions):
self._variable = variable self._variable = variable
self._subscriptions = subscriptions self._subscriptions = subscriptions
@property @property
def value(self): def value(self):
'''Метод возвращающий значение переменной и при этом добавляющий его в
подписки.'''
self._subscriptions.add(self._variable) self._subscriptions.add(self._variable)
return self._variable.get_value() value = self._variable.get_value()
if isinstance(value, Hash):
value = value.get_hash()
return value
@property @property
def subscriptions(self): def subscriptions(self):
@ -313,9 +322,7 @@ class VariableWrapper:
class DependenceSource: class DependenceSource:
current_namespace = None '''Класс зависимости как источника значения переменной.'''
datavars_root = None
def __init__(self, variables, depend=None): def __init__(self, variables, depend=None):
self.error = None self.error = None
self._args = variables self._args = variables
@ -335,7 +342,7 @@ class DependenceSource:
else: else:
self._check_function(self.depend_function) self._check_function(self.depend_function)
def calculate_value(self): def calculate_value(self) -> Any:
'''Метод для расчета значения переменной с использованием зависимостей '''Метод для расчета значения переменной с использованием зависимостей
и заданной для них функции.''' и заданной для них функции.'''
self._subscriptions = set() self._subscriptions = set()
@ -435,6 +442,8 @@ class VariableNode:
self.namespace.add_variable(self) self.namespace.add_variable(self)
self.subscribers = set() self.subscribers = set()
# Список текущих подписок, для проверки их актуальности при
# динамическом связывании.
self._subscriptions = set() self._subscriptions = set()
# Если значение переменной None -- значит она обнулена. # Если значение переменной None -- значит она обнулена.
@ -446,6 +455,10 @@ class VariableNode:
if source is not None: if source is not None:
self.update_value() self.update_value()
# Флаг, указывающий, что значение было изменено в процессе работы
# утилит или с помощью тега set из шаблона.
self.set_by_user = False
self._readonly = False self._readonly = False
def update_value(self) -> None: def update_value(self) -> None:
@ -463,10 +476,12 @@ class VariableNode:
with self._start_calculate(): with self._start_calculate():
try: try:
value = self._source.calculate_value() value = self._source.calculate_value()
# Обновляем подписки # Обновляем подписки. Сначала убираем лишние.
for subscription in self._subscriptions: for subscription in self._subscriptions:
if subscription not in self._source.subscriptions: if subscription not in self._source.subscriptions:
subscription.subscribers.remove(self) subscription.subscribers.remove(self)
# Теперь добавляем новые.
for subscription in self._source.subscriptions: for subscription in self._source.subscriptions:
subscription.subscribers.add(self) subscription.subscribers.add(self)
self._subscriptions = self._source.subscriptions self._subscriptions = self._source.subscriptions
@ -521,7 +536,7 @@ class VariableNode:
finally: finally:
self.calculating = False self.calculating = False
def get_value(self): def get_value(self) -> Any:
'''Метод для получения значения переменной.''' '''Метод для получения значения переменной.'''
if self.value is None: if self.value is None:
self.update_value() self.update_value()
@ -559,12 +574,12 @@ class NamespaceNode:
self.namespaces = dict() self.namespaces = dict()
self.parent = parent self.parent = parent
def add_variable(self, variable: VariableNode): def add_variable(self, variable: VariableNode) -> None:
'''Метод для добавления переменной в пространство имен.''' '''Метод для добавления переменной в пространство имен.'''
self.variables.update({variable.name: variable}) self.variables.update({variable.name: variable})
variable.namespace = self variable.namespace = self
def add_namespace(self, namespace): def add_namespace(self, namespace) -> None:
'''Метод для добавления пространства имен в пространство имен.''' '''Метод для добавления пространства имен в пространство имен.'''
self.namespaces.update({namespace.name: namespace}) self.namespaces.update({namespace.name: namespace})
namespace.parent = self namespace.parent = self
@ -576,7 +591,7 @@ class NamespaceNode:
else: else:
return self.name return self.name
def __getattr__(self, name: str): def __getattr__(self, name: str) -> None:
'''Метод возвращает ноду пространства имен или значение переменной.''' '''Метод возвращает ноду пространства имен или значение переменной.'''
if name in self.namespaces: if name in self.namespaces:
return self.namespaces[name] return self.namespaces[name]
@ -588,7 +603,7 @@ class NamespaceNode:
variable_name=name, variable_name=name,
namespace_name=self.name)) namespace_name=self.name))
def __getitem__(self, name: str): def __getitem__(self, name: str) -> None:
'''Метод возвращает ноду пространства имен или ноду переменной.''' '''Метод возвращает ноду пространства имен или ноду переменной.'''
if name in self.namespaces: if name in self.namespaces:
return self.namespaces[name] return self.namespaces[name]

@ -280,6 +280,23 @@ class TestNamespace:
assert datavars.namespace_1.var_4.key_2 == 'other_value' assert datavars.namespace_1.var_4.key_2 == 'other_value'
assert datavars.namespace_1.var_4.key_3 == 'another_value' assert datavars.namespace_1.var_4.key_3 == 'another_value'
def test_getting_all_hash_to_depend_function(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):
return arg_1.value['key_1']
Variable('var_4', type=StringType,
source=Dependence('.var_2', depend=depend_hash))
assert datavars.namespace_1.var_4 == 'value_1'
def test_readonly(self): def test_readonly(self):
Namespace.reset() Namespace.reset()
datavars = Namespace.datavars datavars = Namespace.datavars

Loading…
Cancel
Save