You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
6.6 KiB

import re
import ast
from inspect import signature, getsource
from types import FunctionType, LambdaType
class VariableType:
'''Базовый класс для типов.'''
@classmethod
def check(self, value: str) -> bool:
return True
class VariableError(Exception):
pass
class VariableTypeError(Exception):
pass
class StringType(VariableType):
pass
class IntegerType(VariableType):
integer_pattern = re.compile(r"^-?\d+$")
@classmethod
def check(self, value: str) -> bool:
return self.integer_pattern.match(value)
class FloatType(VariableType):
float_pattern = re.compile(r"^-?\d+(\.\d+)?$")
@classmethod
def check(self, value: str) -> bool:
return self.integer_pattern.match(value)
class BooleanType(VariableType):
pass
class ListType(VariableType):
pass
class ReadonlyType(VariableType):
pass
class VariableNode:
def __init__(self, name, namespace, variable_type=StringType, value=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.set_function = None
# Если значение переменной None -- значит она обнулена.
self.value = None
self.subscribers = set()
self.subscriptions = tuple()
if value is not None:
self.update_value(value)
self.namespace = namespace
namespace.add_variable(self)
def update_value(self, value=None):
self._invalidate()
if value is not None:
self.value = value
elif self.set_function is not None and self.subscriptions:
self.value = self.set_function(*(subscription.value for
subscription in
self.subscriptions))
elif self.subscriptions:
# Пока что будем полагать, что в такой ситуации берем значение из
# последней подписки.
self.value = self.subscriptions[-1].get_value()
else:
raise VariableError("No sources to update variable '{}'".
format(self.fullname))
def _invalidate(self):
self.value = None
for subscriber in self.subscribers:
subscriber._invalidate()
def get_value(self):
if self.value is None:
self.update_value()
return self.value
def subscribe(self, *variables, set_function=None):
self.subscriptions = variables
for subscription in self.subscriptions:
subscription.subscribers.add(self)
self._check_function(set_function)
self.set_function = set_function
def subscribe(self, *variables, set_function=None):
def _check_function(self, function_to_check: FunctionType):
'''Метод для проверки того, возращает ли функция какое-либо значение, а
также того, совпадает ли число подписок с числом аргументов этой
функции.'''
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("Variable set function does not return"
" anything in variable '{}'.".format(
self.fullname))
# Проверяем совпадение количества аргументов функции и заданных для
# функции переменных.
function_signature = signature(function_to_check)
if not len(self.subscriptions) == len(function_signature.parameters):
raise VariableError("Variable set function takes {} arguments,"
" while {} is given for in variable '{}'."
.format(len(function_signature.parameters),
len(self.subscriptions),
self.fullname))
@property
def fullname(self) -> str:
if self.namespace:
return "{}.{}".format(self.namespace.fullname, self.name)
else:
return self.name
def __repr__(self):
return '<Variable: {} with value: {}>'.format(self.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):
self.variables.update({variable.name: variable})
variable.namespace = self
def add_namespace(self, namespace):
self.namespaces.update({namespace.name: namespace})
namespace.parent = self
@property
def fullname(self) -> str:
if self.parent is not None:
return '{}.{}'.format(self.parent.fullname, self.name)
else:
return self.name
def get_variable_node(self, name):
if name in self.variables:
return self.variables[name]
else:
raise VariableError("Variable '{variable_name}' is not found in"
" the namespace '{namespace_name}'".format(
variable_name=name,
namespace_name=self.name))
def __getattr__(self, name: str):
if name in self.namespaces:
return self.namespaces[name]
elif name in self.variables:
return self.variables[name].get_value()
else:
raise VariableError("'{variable_name}' is not found in the"
" namespace '{namespace_name}'".format(
variable_name=name,
namespace_name=self.name))
def __repr__(self):
return '<Namespace: {}>'.format(self.fullname)