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.

1097 lines
47 KiB

  1. # vim: fileencoding=utf-8
  2. #
  3. import re
  4. # import ast
  5. import dis
  6. from contextlib import contextmanager
  7. # from inspect import signature, getsource
  8. from inspect import signature
  9. # from types import FunctionType, LambdaType
  10. from types import FunctionType
  11. from calculate.utils.tools import Singleton
  12. from typing import List, Any, Union, Generator, Callable, Optional
  13. class DependenceError(Exception):
  14. pass
  15. class VariableError(Exception):
  16. pass
  17. class VariableTypeError(VariableError):
  18. pass
  19. class VariableNotFoundError(VariableError):
  20. pass
  21. class CyclicVariableError(VariableError):
  22. def __init__(self, *queue: List[str]):
  23. self.queue: List[str] = queue
  24. def __str__(self) -> str:
  25. return "Cyclic dependence in variables: {}".format(", ".join(
  26. self.queue[:-1]))
  27. class VariableType:
  28. '''Базовый класс для типов.'''
  29. name: str = 'undefined'
  30. python_type: type = None
  31. @classmethod
  32. def process_value(cls, value: Any, variable: "VariableNode") -> Any:
  33. return value
  34. @classmethod
  35. def readonly(cls, variable_object: "VariableNode") -> None:
  36. variable_object.variable_type = cls
  37. variable_object.readonly = True
  38. class IniType(VariableType):
  39. '''Класс, соответствующий типу переменных созданных в calculate.ini файлах.
  40. '''
  41. name: str = 'ini'
  42. python_type: type = str
  43. @classmethod
  44. def process_value(cls, value: Any, variable: "VariableNode") -> Any:
  45. return value
  46. class StringType(VariableType):
  47. '''Класс, соответствующий типу переменных с строковым значением.'''
  48. name: str = 'string'
  49. python_type: type = str
  50. @classmethod
  51. def process_value(cls, value: Any, variable: "VariableNode") -> str:
  52. if isinstance(value, str):
  53. return value
  54. else:
  55. try:
  56. return str(value)
  57. except Exception as error:
  58. raise VariableTypeError("can not set value '{value}' to"
  59. " string variable: {reason}".format(
  60. value=value,
  61. reason=str(error)))
  62. class IntegerType(VariableType):
  63. '''Класс, соответствующий типу переменных с целочисленным значением.'''
  64. name: str = 'integer'
  65. python_type: type = int
  66. @classmethod
  67. def process_value(cls, value: Any, variable: "VariableNode") -> int:
  68. if isinstance(value, int):
  69. return value
  70. else:
  71. try:
  72. return int(value)
  73. except Exception as error:
  74. raise VariableTypeError("can not set value '{value}' to"
  75. " interger variable: {reason}".format(
  76. value=value,
  77. reason=str(error)))
  78. class FloatType(VariableType):
  79. '''Класс, соответствующий типу переменных с вещественным значением.'''
  80. name: str = 'float'
  81. python_type: type = float
  82. @classmethod
  83. def process_value(cls, value: Any, variable: "VariableNode") -> float:
  84. if isinstance(value, float):
  85. return value
  86. else:
  87. try:
  88. return float(value)
  89. except Exception as error:
  90. raise VariableTypeError("can not set value '{value}' to"
  91. " float variable: {reason}".format(
  92. value=value,
  93. reason=str(error)))
  94. class BooleanType(VariableType):
  95. '''Класс, соответствующий типу переменных с булевым значением.'''
  96. name: str = 'bool'
  97. python_type: type = bool
  98. true_values: set = {'True', 'true'}
  99. false_values: set = {'False', 'false'}
  100. @classmethod
  101. def process_value(cls, value: Any, variable: "VariableNode") -> bool:
  102. if isinstance(value, bool):
  103. return value
  104. elif isinstance(value, str):
  105. if value in cls.true_values:
  106. return True
  107. if value in cls.false_values:
  108. return False
  109. try:
  110. return bool(value)
  111. except Exception as error:
  112. raise VariableTypeError("can not set value '{value}' to"
  113. " bool variable: {reason}".format(
  114. value=value,
  115. reason=str(error)))
  116. class ListType(VariableType):
  117. name = 'list'
  118. python_type: type = list
  119. @classmethod
  120. def process_value(cls, value: Any, variable: "VariableNode") -> list:
  121. if isinstance(value, list):
  122. return value
  123. elif isinstance(value, str):
  124. output_list = list()
  125. values = value.split(',')
  126. for value in values:
  127. output_list.append(value.strip())
  128. return output_list
  129. try:
  130. return list(value)
  131. except Exception as error:
  132. raise VariableTypeError("can not set value '{value}' to"
  133. " list variable: {reason}".format(
  134. value=value,
  135. reason=str(error)))
  136. class HashValue:
  137. '''Класс значения хэша, передающий некоторые характеристики переменной
  138. хозяина, что позволяет инвалидировать подписки переменной хозяина при любом
  139. изменении хэша или инвалидировать весь хэш при изменении одной из
  140. зависимостей.'''
  141. def __init__(self, key: Any, value: Any,
  142. master_variable: "VariableNode",
  143. parent: "Hash"):
  144. self.key: Any = key
  145. self.value: Any = value
  146. self.master_variable: "VariableNode" = master_variable
  147. self.parent: "Hash" = parent
  148. @property
  149. def subscriptions(self) -> set:
  150. return self.master_variable.subscriptions
  151. @property
  152. def subscribers(self) -> set:
  153. return self.master_variable.subscribers
  154. def get_value(self) -> str:
  155. '''Метод для получения значения хэша. Перед возвращением значения
  156. обновляет себя на наиболее актуальную версию значения хэша.'''
  157. self = self.master_variable.get_value()[self.key]
  158. return self.value
  159. def set(self, value: Any) -> None:
  160. '''Метод для задания одного значения хэша без изменения источника
  161. значения.'''
  162. current_hash = self.master_variable.get_value().get_hash()
  163. current_hash[self.key] = value
  164. self.master_variable.set(current_hash)
  165. def reset(self):
  166. '''Метод для сброса значения, установленного поверх источника.'''
  167. self.master_variable.reset()
  168. class Hash:
  169. '''Класс реализующий контейнер для хранения хэша в переменной
  170. соответствующего типа.'''
  171. def __init__(self, values: dict,
  172. master_variable: "VariableNode"):
  173. self.fixed: bool = master_variable.fixed
  174. self._values: dict = dict()
  175. self._fields: set = set()
  176. for key, value in values.items():
  177. self._values.update({key: HashValue(key, value, master_variable,
  178. self)})
  179. if self.fixed:
  180. self._fields.add(key)
  181. self.master_variable: "VariableNode" = master_variable
  182. self.row_index: Union[int, None] = None
  183. def get_hash(self) -> dict:
  184. '''Метод для получения словаря из значений хэша.'''
  185. dict_value = {}
  186. for key in self._values.keys():
  187. dict_value.update({key: self._values[key].get_value()})
  188. return dict_value
  189. def update_hash(self, values: dict) -> None:
  190. '''Метод для обновления значения хэша.'''
  191. for key, value in values.items():
  192. if key in self._values:
  193. self._values[key].value = value
  194. elif self.fixed:
  195. raise VariableError("key '{}' is unavailable for fixed"
  196. " hash, available keys: '{}'.".
  197. format(key, ', '.join(self._fields)))
  198. else:
  199. self._values[key] = HashValue(key, value, self.master_variable,
  200. self)
  201. return self
  202. def __getattr__(self, key: str):
  203. '''Метод возвращает ноду пространства имен или значение переменной.'''
  204. if key in self._values:
  205. return self._values[key].get_value()
  206. else:
  207. raise VariableError(("'{key}' is not found in the hash"
  208. " '{hash_name}'").format(
  209. key=key,
  210. hash_name=self.master_variable.get_fullname()))
  211. def __getitem__(self, key: str) -> HashValue:
  212. if key in self._values:
  213. return self._values[key]
  214. else:
  215. raise VariableError(("'{key}' is not found in the hash"
  216. " '{hash_name}'").format(
  217. key=key,
  218. hash_name=self.master_variable.get_fullname()))
  219. def __iter__(self) -> Generator[Any, None, None]:
  220. for key in self._values.keys():
  221. yield key
  222. def __contains__(self, key: str) -> bool:
  223. return key in self._values
  224. def __str__(self):
  225. return str(self.get_hash())
  226. class HashType(VariableType):
  227. '''Класс, соответствующий типу переменных хэшей.'''
  228. name: str = 'hash'
  229. python_type: type = dict
  230. @classmethod
  231. def process_value(cls, values: Any, variable: "VariableNode") -> Hash:
  232. if not isinstance(values, dict):
  233. raise VariableTypeError("can not set value with type '{_type}' to"
  234. " hash variable: value must be 'dict' type"
  235. .format(_type=type(values)))
  236. if variable.value is not None:
  237. updated_hash = variable.value.update_hash(values)
  238. else:
  239. updated_hash = Hash(values, variable)
  240. return updated_hash
  241. @classmethod
  242. def fixed(cls, variable_object: "VariableNode") -> None:
  243. '''Метод, который передается переменной вместо типа, если нужно задать
  244. тип фиксированного хэша.'''
  245. variable_object.variable_type = cls
  246. variable_object.fixed = True
  247. class Table:
  248. '''Класс, соответствующий типу переменных таблиц.'''
  249. def __init__(self, values: List[dict], master_variable: "VariableNode",
  250. fields: Union[List[str], None] = None):
  251. self._rows: List[dict] = list()
  252. self.master_variable: "VariableNode" = master_variable
  253. self.columns: set = set()
  254. if fields is not None:
  255. self.columns.update(fields)
  256. else:
  257. self.columns = set(values[0].keys())
  258. for row in values:
  259. if isinstance(row, dict):
  260. self._check_columns(row)
  261. self._rows.append(row)
  262. else:
  263. raise VariableTypeError("can not create table using value '{}'"
  264. " with type '{}'".format(row,
  265. type(row)))
  266. def get_table(self) -> List[dict]:
  267. '''Метод для получения всего списка строк таблицы.'''
  268. return self._rows
  269. def add_row(self, row: dict) -> None:
  270. '''Метод для добавления строк в таблицу.'''
  271. self._check_columns(row)
  272. self._rows.append(row)
  273. def change_row(self, row: dict, index: int) -> None:
  274. '''Метод для замены существующей строки.'''
  275. self._check_columns(row)
  276. self._rows[index] = row
  277. def clear(self) -> None:
  278. self._rows.clear()
  279. def _check_columns(self, row: dict) -> None:
  280. '''Метод для проверки наличия в хэше только тех полей, которые
  281. соответствуют заданным для таблицы колонкам.'''
  282. for column in row:
  283. if column not in self.columns:
  284. raise VariableError("unknown column value '{}'"
  285. " available: '{}'".format(
  286. column,
  287. ', '.join(self.columns)))
  288. def __getitem__(self, index: int) -> Hash:
  289. if isinstance(index, int):
  290. if index < len(self._rows):
  291. return self._rows[index]
  292. else:
  293. return {key: None for key in self.columns}
  294. raise VariableError("'{index}' index value is out of range"
  295. .format(index=index))
  296. else:
  297. raise VariableError("Table value is not subscriptable")
  298. def __iter__(self) -> Generator[dict, None, None]:
  299. for row in self._rows:
  300. yield row
  301. def __contains__(self, key: str) -> bool:
  302. if isinstance(key, str):
  303. return key in self._values
  304. return False
  305. def __len__(self) -> int:
  306. return len(self._rows)
  307. class TableType(VariableType):
  308. name: str = 'table'
  309. # TODO Сомнительно.
  310. python_type: type = list
  311. @classmethod
  312. def process_value(self, value: List[dict],
  313. variable: "VariableNode") -> Table:
  314. if not isinstance(value, list) and not isinstance(value, Table):
  315. raise VariableTypeError("can not set value with type '{_type}' to"
  316. " hash variable: value must be 'dict' type"
  317. .format(_type=type(value)))
  318. else:
  319. if isinstance(value, Table):
  320. return value
  321. if variable.fields:
  322. return Table(value, variable, fields=variable.fields)
  323. else:
  324. return Table(value, variable)
  325. class Static:
  326. '''Класс для указания в качестве аргументов зависимостей статичных
  327. значений, а не только переменных.'''
  328. def __init__(self, value: Any):
  329. self.value: Any = value
  330. def get_value(self) -> Any:
  331. return self.value
  332. class VariableWrapper:
  333. '''Класс обертки для переменных, с помощью которого отслеживается
  334. применение переменной в образовании значения переменной от нее зависящей.
  335. '''
  336. def __init__(self, variable: "VariableNode",
  337. subscriptions: set):
  338. self._variable: "VariableNode" = variable
  339. self._subscriptions: set = subscriptions
  340. @property
  341. def value(self):
  342. '''Метод возвращающий значение переменной и при этом добавляющий его в
  343. подписки.'''
  344. if not isinstance(self._variable, Static):
  345. self._subscriptions.add(self._variable)
  346. value = self._variable.get_value()
  347. if isinstance(value, Hash):
  348. value = value.get_hash()
  349. elif isinstance(value, Table):
  350. value = value.get_table()
  351. return value
  352. @property
  353. def subscriptions(self):
  354. return self._subscriptions
  355. @subscriptions.setter
  356. def subscriptions(self, subscriptions):
  357. self._subscriptions = subscriptions
  358. class DependenceSource:
  359. '''Класс зависимости как источника значения переменной.'''
  360. def __init__(self, variables: tuple,
  361. depend: Optional[Callable] = None):
  362. self._args: Union[tuple, list] = variables
  363. self.depend_function: Union[Callable, None] = depend
  364. self._subscriptions: set = set()
  365. self._args_founded: bool = False
  366. def check(self) -> None:
  367. '''Метод для запуска проверки корректности функции зависимости, а также
  368. сопоставления ее числу заданных зависимостей.'''
  369. if self.depend_function is None:
  370. if len(self._args) > 1:
  371. raise DependenceError('the depend function is needed if the'
  372. ' number of dependencies is more than'
  373. ' one')
  374. elif len(self._args) == 0:
  375. raise DependenceError('dependence is set without variables')
  376. else:
  377. self._check_function(self.depend_function)
  378. def calculate_value(self) -> Any:
  379. '''Метод для расчета значения переменной с использованием зависимостей
  380. и заданной для них функции.'''
  381. self._subscriptions = set()
  382. args = tuple(VariableWrapper(arg, self._subscriptions)
  383. for arg in self._args)
  384. try:
  385. if self.depend_function is None:
  386. return args[-1].value
  387. return self.depend_function(*args)
  388. except CyclicVariableError:
  389. raise
  390. except Exception as error:
  391. raise DependenceError('can not calculate using dependencies: {}'
  392. ' reason: {}'.format(', '.join(
  393. [subscription.get_fullname()
  394. for subscription
  395. in self._args]),
  396. str(error)))
  397. def _get_args(self, namespace: "NamespaceNode") -> None:
  398. '''Метод для преобразования списка аргументов функции зависимости,
  399. содержащего переменные и строки, в список аргументов состоящий только
  400. из нод переменных и значений хэшей.'''
  401. if not self._args_founded:
  402. self._args = self.find_variables(self._args, namespace)
  403. self._args_founded = True
  404. def find_variables(self, variables: Union[tuple, list],
  405. namespace: "NamespaceNode") -> List["VariableNode"]:
  406. '''Метод для поиска переменных по заданным путям к ним.'''
  407. output = []
  408. for variable in variables:
  409. if isinstance(variable, str):
  410. variable = Dependence._get_variable(
  411. variable,
  412. current_namespace=namespace)
  413. if variable is None:
  414. raise DependenceError("variable '{}' not found".
  415. format(variable))
  416. output.append(variable)
  417. return output
  418. @property
  419. def subscriptions(self) -> set:
  420. return self._subscriptions
  421. def _check_function(self, function_to_check: FunctionType) -> None:
  422. '''Метод для проверки того, возращает ли функция какое-либо значение, а
  423. также того, совпадает ли число подписок с числом аргументов этой
  424. функции.'''
  425. # if not isinstance(function_to_check, LambdaType):
  426. # # Если функция не лямбда, проверяем есть ли у нее возвращаемое
  427. # # значение.
  428. # source_code = getsource(function_to_check)
  429. # for node in ast.walk(ast.parse(source_code)):
  430. # if isinstance(node, ast.Return):
  431. # break
  432. # else:
  433. # raise VariableError("the depend function does not return"
  434. # " anything in variable")
  435. # Проверяем совпадение количества аргументов функции и заданных для
  436. # функции переменных.
  437. self.check_signature(function_to_check, self._args)
  438. @staticmethod
  439. def check_signature(function_to_check: Callable,
  440. arguments: Union[tuple, list]) -> None:
  441. '''Метод для проверки соответствия сигнатуры функции и заданного для
  442. нее набора аргументов.'''
  443. function_signature = signature(function_to_check)
  444. if not len(arguments) == len(function_signature.parameters):
  445. raise DependenceError("the depend function takes {} arguments,"
  446. " while {} is given".format(
  447. len(function_signature.parameters),
  448. len(arguments)))
  449. def __ne__(self, other: Any) -> bool:
  450. if not isinstance(other, DependenceSource):
  451. return True
  452. return not self == other
  453. def __eq__(self, other: Any) -> bool:
  454. if not isinstance(other, DependenceSource):
  455. return False
  456. # Сначала сравниваем аргументы.
  457. for l_var, r_var in zip(self._args, other._args):
  458. if l_var != r_var:
  459. return False
  460. if self.depend_function == other.depend_function:
  461. return True
  462. if (self.depend_function is None or other.depend_function is None
  463. or not self._compare_depend_functions(self.depend_function,
  464. other.depend_function)):
  465. return False
  466. return True
  467. def _compare_depend_functions(self, l_func: FunctionType,
  468. r_func: FunctionType) -> bool:
  469. '''Метод для сравнения функций путем сравнения инструкций, полученных
  470. в результате их дизассемблирования.'''
  471. l_instructions = list(dis.get_instructions(l_func))
  472. r_instructions = list(dis.get_instructions(r_func))
  473. for l_instr, r_instr in zip(l_instructions, r_instructions):
  474. if l_instr.opname != r_instr.opname:
  475. return False
  476. if l_instr.arg != r_instr.arg:
  477. return False
  478. if ((l_instr.argval != l_instr.argrepr) and
  479. (r_instr.argval != r_instr.argrepr)):
  480. if r_instr.argval != l_instr.argval:
  481. return False
  482. return True
  483. class VariableNode:
  484. '''Класс ноды соответствующей переменной в дереве переменных.'''
  485. def __init__(self, name: str, namespace: "NamespaceNode",
  486. variable_type: type = VariableType,
  487. source: Any = None, fields: list = []):
  488. self.name: str = name
  489. if issubclass(variable_type, VariableType):
  490. self.variable_type: type = variable_type
  491. else:
  492. raise VariableTypeError('variable_type must be VariableType'
  493. ', but not {}'.format(type(variable_type)))
  494. self.calculating: bool = False
  495. self.fields: list = fields
  496. self.namespace: "NamespaceNode" = namespace
  497. self.namespace.add_variable(self)
  498. self.subscribers: set = set()
  499. # Список текущих подписок, для проверки их актуальности при
  500. # динамическом связывании.
  501. self._subscriptions: set = set()
  502. self.value: Any = None
  503. self._invalidated: bool = True
  504. # Флаг имеющий значение только для переменных типа HashType.
  505. # Предназначен для включения проверки соответствия полей хэша при
  506. # установке значения.
  507. self._fixed: bool = False
  508. # Источник значения переменной, может быть значением, а может быть
  509. # зависимостью.
  510. self._source: Any = source
  511. if source is not None:
  512. self.update_value()
  513. # Флаг, указывающий, что значение было изменено в процессе работы
  514. # утилит или с помощью тега set из шаблона.
  515. self.set_by_user: bool = False
  516. self._readonly: bool = False
  517. def update_value(self) -> None:
  518. '''Метод для обновления значения переменной с помощью указанного
  519. источника ее значения.'''
  520. if self.calculating:
  521. raise CyclicVariableError(self.name)
  522. if self._source is None:
  523. raise VariableError("No sources to update variable '{}'".
  524. format(self.get_fullname()))
  525. if isinstance(self._source, DependenceSource):
  526. self._source._get_args(self.namespace)
  527. with self._start_calculate():
  528. try:
  529. value = self._source.calculate_value()
  530. # Обновляем подписки. Сначала убираем лишние.
  531. for subscription in self._subscriptions:
  532. if subscription not in self._source.subscriptions:
  533. subscription.subscribers.remove(self)
  534. # Теперь добавляем новые.
  535. for subscription in self._source.subscriptions:
  536. subscription.subscribers.add(self)
  537. self._subscriptions = self._source.subscriptions
  538. except CyclicVariableError as error:
  539. raise CyclicVariableError(self.name, *error.queue)
  540. except DependenceError as error:
  541. raise VariableError('{}: {}'.format(self.get_fullname(),
  542. str(error)))
  543. else:
  544. value = self._source
  545. if self.variable_type is TableType:
  546. self.value = self.variable_type.process_value(value, self)
  547. else:
  548. self.value = self.variable_type.process_value(value, self)
  549. self._invalidated = False
  550. def set_variable_type(self, variable_type: VariableType,
  551. readonly: Union[bool, None] = None,
  552. fixed: Union[bool, None] = None) -> None:
  553. '''Метод для установки типа переменной.'''
  554. if readonly is not None and isinstance(readonly, bool):
  555. self._readonly = readonly
  556. elif fixed is not None and isinstance(fixed, bool):
  557. self._fixed = fixed
  558. if self.variable_type is VariableType:
  559. if isinstance(variable_type, type):
  560. if issubclass(variable_type, VariableType):
  561. self.variable_type = variable_type
  562. else:
  563. raise VariableError('variable type object must be'
  564. ' VariableType or its class method,'
  565. ' not {}'.format(type(variable_type)))
  566. elif callable(variable_type):
  567. variable_type(self)
  568. def set(self, value: Any) -> None:
  569. '''Метод для установки временного пользовательского значения
  570. переменной.'''
  571. # Сбрасываем флаги, чтобы провести инвалидацию.
  572. self._invalidated = False
  573. self.set_by_user = False
  574. self.value = self.variable_type.process_value(value, self)
  575. self._invalidate(set_by_user=True)
  576. def reset(self) -> None:
  577. '''Метод для сброса пользовательского значения.'''
  578. if self.set_by_user:
  579. self._invalidated = False
  580. self.set_by_user = False
  581. self._invalidate()
  582. @property
  583. def source(self) -> None:
  584. return self._source
  585. @source.setter
  586. def source(self, source: Any) -> None:
  587. # Если источники не совпадают или текущее значение переменной было
  588. # установлено пользователем, то инвалидируем переменную и меняем
  589. # источник.
  590. if self._source != source or self.set_by_user:
  591. self.set_by_user = False
  592. self._invalidate()
  593. self._source = source
  594. @property
  595. def readonly(self) -> bool:
  596. return self._readonly
  597. @readonly.setter
  598. def readonly(self, value: bool) -> None:
  599. # if self.value is None and self._source is not None:
  600. # # TODO выводить предупреждение если переменная инвалидирована,
  601. # # нет источника и при этом устанавливается флаг readonly.
  602. # self.update_value()
  603. self._readonly = value
  604. @property
  605. def fixed(self) -> bool:
  606. return self._fixed
  607. @fixed.setter
  608. def fixed(self, value: bool) -> bool:
  609. self._fixed = value
  610. def _invalidate(self, set_by_user: bool = False) -> None:
  611. '''Метод для инвалидации данной переменной и всех зависящих от нее
  612. переменных.'''
  613. if not self._invalidated and not self.set_by_user:
  614. self._invalidated = True
  615. self.set_by_user = set_by_user
  616. for subscriber in self.subscribers:
  617. subscriber._invalidate()
  618. @contextmanager
  619. def _start_calculate(self) -> Generator["VariableNode", None, None]:
  620. '''Менеджер контекста устанавливающий флаг, указывающий, что данная
  621. переменная в состоянии расчета. В данном случае необходима только для
  622. того, чтобы при получении значения параметра внутри метода для расчета
  623. не иницировалось ее обновление, которое может привести к рекурсии.'''
  624. try:
  625. self.calculating = True
  626. yield self
  627. finally:
  628. self.calculating = False
  629. def get_value(self) -> Any:
  630. '''Метод для получения значения переменной.'''
  631. if self._invalidated and self._source is None and self._value is None:
  632. return None
  633. if self._invalidated and not self.set_by_user:
  634. self.update_value()
  635. return self.value
  636. def get_fullname(self) -> str:
  637. if self.namespace:
  638. return "{}.{}".format(self.namespace.get_fullname(), self.name)
  639. else:
  640. return self.name
  641. def __getitem__(self, key: str) -> HashValue:
  642. if self.variable_type is HashType:
  643. if key in self.get_value():
  644. return self.value[key]
  645. else:
  646. raise VariableNotFoundError("value '{}' is not found in hash"
  647. " variable '{}'".format(
  648. key, self.get_fullname()))
  649. else:
  650. raise VariableError("'{}' variable type is not subscriptable.".
  651. format(self.variable_type.name))
  652. def __repr__(self) -> str:
  653. return '<Variable: {} with value: {}>'.format(self.get_fullname(),
  654. self.value or
  655. 'INVALIDATED')
  656. class NamespaceNode:
  657. '''Класс ноды соответствующей пространству имен в дереве переменных.'''
  658. def __init__(self, name: str = '',
  659. parent: Optional["NamespaceNode"] = None):
  660. self._name = name
  661. self._variables = dict()
  662. self._namespaces = dict()
  663. self._parent = parent
  664. def add_variable(self, variable: VariableNode) -> None:
  665. '''Метод для добавления переменной в пространство имен.'''
  666. if variable.name in self._namespaces:
  667. raise VariableError("namespace with the name '{}' is already in"
  668. " the namespace '{}'".format(
  669. variable.name,
  670. self.get_fullname()))
  671. self._variables.update({variable.name: variable})
  672. variable.namespace = self
  673. def add_namespace(self, namespace: "NamespaceNode") -> None:
  674. '''Метод для добавления пространства имен в пространство имен.'''
  675. if namespace._name in self._variables:
  676. raise VariableError("variable with the name '{}' is already in"
  677. " the namespace '{}'".format(
  678. namespace._name,
  679. self.get_fullname()))
  680. self._namespaces.update({namespace._name: namespace})
  681. namespace._parent = self
  682. def clear(self) -> None:
  683. '''Метод для очистки пространства имен. Очищает и пространства имен
  684. и переменные. Предназначен только для использования в calculate.ini.'''
  685. for namespace_name in self._namespaces.keys():
  686. self._namespaces[namespace_name].clear()
  687. self._variables.clear()
  688. self._namespaces.clear()
  689. def get_fullname(self) -> str:
  690. '''Метод для получения полного имени пространства имен.'''
  691. if self._parent is not None and self._parent._name != '<root>':
  692. return '{}.{}'.format(self._parent.get_fullname(), self._name)
  693. else:
  694. return self._name
  695. def get_package_name(self) -> str:
  696. if self._parent._name == '<root>':
  697. return self._name
  698. else:
  699. return self._parent.get_package_name()
  700. def __getattr__(self, name: str) -> Any:
  701. '''Метод возвращает ноду пространства имен или значение переменной.'''
  702. if name in self._namespaces:
  703. return self._namespaces[name]
  704. elif name in self._variables:
  705. variable = self._variables[name]
  706. if variable.variable_type is TableType:
  707. return variable.get_value()
  708. return variable.get_value()
  709. else:
  710. if self.get_package_name() == "custom":
  711. return None
  712. raise VariableNotFoundError("'{variable_name}' is not found in the"
  713. " namespace '{namespace_name}'".format(
  714. variable_name=name,
  715. namespace_name=self._name))
  716. def __getitem__(self, name: str) -> None:
  717. '''Метод возвращает ноду пространства имен или ноду переменной.'''
  718. if name in self._namespaces:
  719. return self._namespaces[name]
  720. elif name in self._variables:
  721. return self._variables[name]
  722. else:
  723. if self.get_package_name() == "custom":
  724. return None
  725. raise VariableNotFoundError("'{variable_name}' is not found in the"
  726. " namespace '{namespace_name}'".format(
  727. variable_name=name,
  728. namespace_name=self._name))
  729. def __contains__(self, name: str) -> bool:
  730. return name in self._namespaces or name in self._variables
  731. def __repr__(self) -> str:
  732. return '<Namespace: {}>'.format(self.get_fullname())
  733. def __deepcopy__(self, memo: dict) -> "NamespaceNode":
  734. '''Пространство имен не копируется даже при глубоком копировании.'''
  735. return self
  736. class DependenceAPI(metaclass=Singleton):
  737. '''Класс образующий интерфейс для создания зависимостей.'''
  738. def __init__(self):
  739. self.current_namespace: NamespaceNode = None
  740. self.datavars_root = None
  741. def __call__(self, *variables: list,
  742. depend: Union[Callable, None] = None) -> DependenceSource:
  743. subscriptions = list()
  744. for variable in variables:
  745. if not (isinstance(variable, str) or
  746. isinstance(variable, VariableNode) or
  747. isinstance(variable, Static)):
  748. variable = Static(variable)
  749. subscriptions.append(variable)
  750. return DependenceSource(subscriptions, depend=depend)
  751. def _get_variable(self, variable_name: str,
  752. current_namespace: Union[NamespaceNode, None] = None
  753. ) -> VariableNode:
  754. '''Метод для поиска переменной в пространствах имен.'''
  755. if current_namespace is None:
  756. current_namespace = self.current_namespace
  757. return self.find_variable(variable_name, self.datavars_root,
  758. current_namespace=current_namespace)
  759. @staticmethod
  760. def find_variable(variable_name: str,
  761. datavars_root,
  762. current_namespace: Union[NamespaceNode, None] = None
  763. ) -> VariableNode:
  764. '''Метод для поиска переменных по строковому пути от корня переменных
  765. или от текущего пространства имен.'''
  766. name_parts = variable_name.split('.')
  767. if not name_parts[0]:
  768. if current_namespace is not None:
  769. namespace = current_namespace
  770. for index in range(1, len(name_parts)):
  771. if not name_parts[index]:
  772. namespace = namespace._parent
  773. else:
  774. name_parts = name_parts[index:]
  775. break
  776. else:
  777. raise VariableNotFoundError(
  778. f"variable '{variable_name}' is not found.")
  779. else:
  780. namespace = datavars_root
  781. search_result = namespace
  782. for part in name_parts:
  783. search_result = search_result[part]
  784. return search_result
  785. class CopyAPI(metaclass=Singleton):
  786. '''Класс для создания зависимостей представляющих собой простое копирование
  787. значения переменной в зависимую переменную.'''
  788. def __call__(self, variable: Union[str, VariableNode]) -> "Dependence":
  789. return Dependence(variable)
  790. class FormatAPI(metaclass=Singleton):
  791. '''Класс для создания зависимостей представляющих собой форматируемую
  792. строку с указанием переменных в тех местах, где их значения должны быть
  793. подставлены в строку.'''
  794. pattern = re.compile(
  795. r'{\s*([a-zA-Z][a-zA-Z_0-9]+)?(.[a-zA-Z][a-zA-Z_0-9]+)+\s*}')
  796. def __call__(self, string: str) -> "Dependence":
  797. vars_list = []
  798. def subfunc(matchobj: re.Match):
  799. vars_list.append(matchobj.group(0)[1:-1].strip())
  800. return '{}'
  801. format_string = self.pattern.sub(subfunc, string)
  802. def depend_function(*args):
  803. values = [arg.value for arg in args]
  804. return format_string.format(*values)
  805. return Dependence(*vars_list, depend=depend_function)
  806. class CalculateAPI(metaclass=Singleton):
  807. '''Метод для создания зависимостей, представляющих собой функцию для
  808. вычисления значения зависимой переменной на основе значений указанных
  809. переменных.'''
  810. def __call__(self, *args) -> "Dependence":
  811. depend_function = args[0]
  812. return Dependence(*args[1:], depend=depend_function)
  813. class VariableAPI(metaclass=Singleton):
  814. '''Класс для создания переменных при задании их через
  815. python-скрипты.'''
  816. def __init__(self):
  817. self.current_namespace: Union[NamespaceNode, None] = None
  818. # TODO Продумать другой способ обработки ошибок.
  819. self.errors: list = []
  820. def __call__(self, name: str,
  821. source: Any = None,
  822. type=VariableType,
  823. readonly: bool = False,
  824. fixed: bool = False,
  825. force: bool = False,
  826. fields: Union[list, None] = None) -> "Dependence":
  827. '''Метод для создания переменных внутри with Namespace('name').'''
  828. if name not in self.current_namespace._variables:
  829. variable = VariableNode(name, self.current_namespace)
  830. else:
  831. variable = self.current_namespace[name]
  832. if isinstance(source, DependenceSource):
  833. try:
  834. source.check()
  835. variable.source = source
  836. except DependenceError as error:
  837. raise VariableError('Dependence error: {} in variable: {}'.
  838. format(str(error),
  839. variable.get_fullname()))
  840. else:
  841. variable.source = source
  842. if fields:
  843. variable.fields = fields
  844. if readonly:
  845. variable.set_variable_type(type, readonly=True)
  846. elif fixed:
  847. variable.set_variable_type(type, fixed=True)
  848. else:
  849. variable.set_variable_type(type)
  850. return variable
  851. class NamespaceAPI(metaclass=Singleton):
  852. '''Класс для создания пространств имен при задании переменных через
  853. python-скрипты.'''
  854. def __init__(self, var_fabric: VariableAPI,
  855. dependence_fabric: DependenceAPI,
  856. datavars_root=NamespaceNode('<root>')):
  857. self._datavars = datavars_root
  858. self.current_namespace = self._datavars
  859. # Привязываем фабрику переменных.
  860. self._variables_fabric = var_fabric
  861. self._variables_fabric.current_namespace = self._datavars
  862. # Привязываем фабрику зависимостей.
  863. self._dependence_fabric = dependence_fabric
  864. self._dependence_fabric.current_namespace = self._datavars
  865. self._dependence_fabric.datavars_root = self._datavars
  866. @property
  867. def datavars(self) -> NamespaceNode:
  868. '''Метод для получения корневого пространства имен, через которое далее
  869. можно получить доступ к переменным.'''
  870. return self._datavars
  871. def set_datavars(self, datavars) -> None:
  872. '''Метод для установки корневого пространства имен, которое пока что
  873. будет использоваться для предоставления доступа к переменным.'''
  874. self._datavars = datavars
  875. self._variables_fabric.current_namespace = self._datavars
  876. def reset(self) -> None:
  877. '''Метод для сброса корневого пространства имен.'''
  878. if isinstance(self._datavars, NamespaceNode):
  879. self._datavars = NamespaceNode('<root>')
  880. else:
  881. self._datavars.reset()
  882. self.current_namespace = self._datavars
  883. self._variables_fabric.current_namespace = self._datavars
  884. self._dependence_fabric.current_namespace = self._datavars
  885. def set_current_namespace(self, namespace: NamespaceNode) -> None:
  886. '''Метод для установки текущего пространства имен, в которое будут
  887. добавляться далее переменные и пространства имен.'''
  888. self.current_namespace = namespace
  889. self._variables_fabric.current_namespace = namespace
  890. self._dependence_fabric.current_namespace = namespace
  891. @contextmanager
  892. def __call__(self, namespace_name: str
  893. ) -> Generator["NamespaceAPI", None, None]:
  894. '''Метод для создания пространств имен с помощью with.'''
  895. if namespace_name not in self.current_namespace._namespaces:
  896. namespace = NamespaceNode(namespace_name,
  897. parent=self.current_namespace)
  898. else:
  899. namespace = self.current_namespace._namespaces[namespace_name]
  900. self.current_namespace.add_namespace(namespace)
  901. self.current_namespace = namespace
  902. # Устанавливаем текущее пространство имен фабрике переменных.
  903. self._variables_fabric.current_namespace = self.current_namespace
  904. # Устанавливаем текущее пространство имен фабрике зависимостей.
  905. self._dependence_fabric.current_namespace = namespace
  906. self._dependence_fabric.datavars_root = self._datavars
  907. try:
  908. yield self
  909. finally:
  910. self.current_namespace = self.current_namespace._parent
  911. self._variables_fabric.current_namespace = self.current_namespace
  912. self._dependence_fabric.current_namespace = self.current_namespace
  913. Dependence = DependenceAPI()
  914. Copy = CopyAPI()
  915. Format = FormatAPI()
  916. Calculate = CalculateAPI()
  917. Variable = VariableAPI()
  918. Namespace = NamespaceAPI(Variable, Dependence)