node.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. # -------------------------------------------------------------------------
  7. # this has to be at the beginning
  8. from __future__ import division
  9. # -------------------------------------------------------------------------
  10. # -------------------------------------------------------------------------
  11. # node.py
  12. # simple base Node Class, useful in tool creation.
  13. # version: 0.1
  14. # author: Gallowj
  15. # -------------------------------------------------------------------------
  16. # -------------------------------------------------------------------------
  17. """
  18. Module docstring:
  19. A Simple Node Base Class Module, for creating basic nodes within a hierarchy.
  20. """
  21. __author__ = 'HogJonny'
  22. # built-ins
  23. import os
  24. import copy
  25. import traceback
  26. import string
  27. import logging
  28. # using hashids to generate unique name identifiers for nodes
  29. import hashids
  30. import cachetools
  31. from sched import scheduler
  32. # local imports
  33. from azpy.shared.noodely.helpers import display_cached_value
  34. from azpy.shared.noodely.find_arg import find_arg
  35. from azpy.shared.noodely.synth import synthesize
  36. import azpy
  37. from azpy.env_bool import env_bool
  38. from azpy.constants import ENVAR_DCCSI_GDEBUG
  39. from azpy.constants import ENVAR_DCCSI_DEV_MODE
  40. # global space
  41. # To Do: update to dynaconf dynamic env and settings?
  42. _DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
  43. _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
  44. _MODULENAME = 'azpy.shared.noodely.node'
  45. _log_level = int(20)
  46. if _DCCSI_GDEBUG:
  47. _log_level = int(10)
  48. _LOGGER = azpy.initialize_logger(_MODULENAME,
  49. log_to_file=False,
  50. default_log_level=_log_level)
  51. _LOGGER.debug('Starting:: {}.'.format({_MODULENAME}))
  52. # -------------------------------------------------------------------------
  53. # -------------------------------------------------------------------------
  54. # quick test code (remove later)
  55. from hashids import Hashids
  56. hashids = Hashids(min_length=16, salt='DCCsi')
  57. if _DCCSI_GDEBUG:
  58. print (hashids.encrypt(193487)) # test hash
  59. # -------------------------------------------------------------------------
  60. # -------------------------------------------------------------------------
  61. # set up logger
  62. _G_LOGGER = logging.getLogger(__name__)
  63. # -------------------------------------------------------------------------
  64. # -------------------------------------------------------------------------
  65. # Use unicode strings
  66. _base = str # Python 3 str (=unicode), or Python 2 bytes.
  67. if os.path.supports_unicode_filenames:
  68. try:
  69. _base = unicode # Python 2 unicode.
  70. except NameError:
  71. pass
  72. # -------------------------------------------------------------------------
  73. ###########################################################################
  74. # HELPER method functions
  75. # -------------------------------------------------------------------------
  76. def return_node_from_hashid(hashid):
  77. if not isinstance(hashid, str):
  78. raise TypeError("{0},{1}: Accepts hashids as str types!\r"
  79. "Input hashid:{2}\r"
  80. "".format('noodly',
  81. 'return_node_from_hashid(hashid)',
  82. type(hashid)))
  83. temp_node = Node(temp_node=True).get_sibling_node_from_hashid('{0}'.format(hashid))
  84. return temp_node
  85. # -------------------------------------------------------------------------
  86. # -------------------------------------------------------------------------
  87. class Node(object):
  88. """Class constructor: makes a node."""
  89. # share the debug state
  90. _DEBUG = _DCCSI_GDEBUG
  91. # logger
  92. _LOGGER = _G_LOGGER
  93. # class header
  94. message_header = 'noodly, Node(): Message'
  95. # class variable
  96. _cls_node_count = 0
  97. _cls_node_list = []
  98. _cls_node_dict = {}
  99. # --BASE-METHODS-------------------------------------------------------
  100. # --constructor-
  101. def __init__(self, node_name=None, parent_node=None, *args, **kwargs):
  102. self._logger = Node._LOGGER
  103. self._node_type = self.__class__.__name__
  104. # a dict to store properties/attrs
  105. # in the event an object is re-built / re-init
  106. # it is important to store anything here that needs retention
  107. self._kwargs_dict = {}
  108. self._children = []
  109. # private local access to the cls_node_list
  110. self._cls_node_list = Node._cls_node_list
  111. # -- secret keyword -----------------------------------------------
  112. self._temp_node = False
  113. temp_node, kwargs = find_arg(arg_pos_index=None, arg_tag='temp_node',
  114. remove_kwarg=True, in_args=args,
  115. in_kwargs=kwargs) # <-- kwarg only
  116. self._temp_node = temp_node
  117. if self._temp_node:
  118. self._kwargs_dict['temp_node'] = self._temp_node
  119. # -----------------------------------------------------------------
  120. # -- store message header -----------------------------------------
  121. # setup the .message_header <-- kwarg only
  122. message_header, kwargs = find_arg(arg_pos_index=None, arg_tag='message_header',
  123. remove_kwarg=True, in_args=args, in_kwargs=kwargs,
  124. default_value=('{0}(), Message'
  125. .format(self._node_type)))
  126. self._message_header = message_header
  127. # -----------------------------------------------------------------
  128. # -- hashid -------------------------------------------------------
  129. self._name_is_uni_hashid = False
  130. self._node_class_index = len(Node._cls_node_list)
  131. if Node._DEBUG:
  132. print ('__init__.node_class_index: {0}'.format(self._node_class_index))
  133. self._uni_hashid = hashids.encrypt(self._node_class_index)
  134. if Node._DEBUG:
  135. print ('__init__.uni_hashid: {0}'.format(self._uni_hashid))
  136. # update class dict
  137. if not self._temp_node:
  138. Node._cls_node_dict[self._uni_hashid] = self
  139. # -----------------------------------------------------------------
  140. # -- store the node name ------------------------------------------
  141. self._node_name = node_name
  142. if (self._node_class_index == 0 and self._node_name == None):
  143. self._node_name = 'PRIME'
  144. elif (self._node_class_index > 0 and self._node_name == None):
  145. # set a default node_name if none, based on the unihashid
  146. if not self._name_is_uni_hashid:
  147. self._node_name = self._uni_hashid
  148. self._name_is_uni_hashid = True
  149. if Node._DEBUG:
  150. print ('__init__.node_name: {0}'.format(self._node_name))
  151. # -----------------------------------------------------------------
  152. # -- node parent_node --------------------------------------------------
  153. # set up the parent_node property
  154. self._parent_node = parent_node
  155. if self._parent_node != None:
  156. # add this node, to the parent_nodes list of children
  157. try:
  158. self._parent_node.add_child(self)
  159. except:
  160. pass # <-- parent_node object passed is NOT a noodly.node?
  161. # Update class variables
  162. Node.cls_node_count_up(self)
  163. Node.cls_node_list_append(self)
  164. # -----------------------------------------------------------------
  165. # -----------------------------------------------------------------
  166. # arbitrary argument properties
  167. # check postions *args and **kwargs
  168. # any kwargs left will be used to synthesize a property
  169. try:
  170. # checking import due to the way the code is structured
  171. # the code was passing even if module was not imported
  172. synthesize
  173. synthExists = True
  174. except Exception as e:
  175. print(e)
  176. raise e
  177. for key, value in kwargs.items():
  178. self._kwargs_dict[key] = value
  179. try:
  180. synthesize(self, key, value)
  181. except Exception as e: # <-- maybe it can't synthesize?
  182. # in which case fall back to setting the property
  183. print(e)
  184. code = compile(r'self._{0}={1}'.format(key, value), 'synthProp', 'exec')
  185. pass
  186. if Node._DEBUG:
  187. print("{0}:{1}".format(key, value))
  188. # -----------------------------------------------------------------
  189. # if temp node, adjust the class counter
  190. if temp_node:
  191. self.cls_node_count_down()
  192. self.cls_node_list_remove()
  193. # -- properties ------------------------------------------------------------
  194. @property
  195. def logger(self):
  196. return self._logger
  197. @logger.setter
  198. def logger(self, logger):
  199. self._logger = logger
  200. return self._logger
  201. @logger.getter
  202. def logger(self):
  203. return self._logger
  204. @property
  205. def kwargs_dict(self):
  206. return self._kwargs_dict
  207. @kwargs_dict.getter
  208. def kwargs_dict(self):
  209. return self._kwargs_dict
  210. @property
  211. def message_header(self):
  212. return self._message_header
  213. @message_header.setter
  214. def message_header(self, message_header):
  215. self._message_header = message_header
  216. return self._message_header
  217. @property
  218. def node_type(self):
  219. return self._node_type
  220. @node_type.setter
  221. def node_type(self, node_type):
  222. self._node_type = node_type
  223. return self._node_type
  224. @node_type.getter
  225. def node_type(self):
  226. return self._node_type
  227. @property
  228. def temp_node(self):
  229. return self._temp_node
  230. @temp_node.setter
  231. def temp_node(self, temp_node):
  232. self._temp_node = temp_node
  233. return self._temp_node
  234. @temp_node.getter
  235. def temp_node(self):
  236. return self._temp_node
  237. @property
  238. def node_name(self):
  239. return self._node_name
  240. @node_name.setter
  241. def node_name(self, nameStr):
  242. if nameStr != None:
  243. if not isinstance(nameStr, str):
  244. raise TypeError("{0}, {1}: Accepts str types!"
  245. "".format(self.__class__.__name__,
  246. self._node_name.__name__))
  247. try:
  248. self._node_name = nameStr
  249. except:
  250. synthesize(self, '_node_name', None)
  251. return self._node_name
  252. @node_name.getter
  253. def node_name(self):
  254. return self._node_name
  255. @property
  256. def name_is_uni_hashid(self):
  257. return self._name_is_uni_hashid
  258. @node_type.setter
  259. def name_is_uni_hashid(self, value):
  260. self._name_is_uni_hashid = value
  261. return self._name_is_uni_hashid
  262. @node_type.getter
  263. def name_is_uni_hashid(self):
  264. return self._name_is_uni_hashid
  265. @property
  266. def node_class_index(self):
  267. return self._node_class_index
  268. @property
  269. def uni_hashid(self):
  270. return self._uni_hashid
  271. @property
  272. def cls_node_dict(self):
  273. return Node._cls_node_dict
  274. # ---------------------------------------------------------------------
  275. # --method-set---------------------------------------------------------
  276. def cls_node_count_up(self):
  277. Node._cls_node_count += 1
  278. return Node._cls_node_count
  279. def cls_node_count_down(self):
  280. Node._cls_node_count -= 1
  281. return Node._cls_node_count
  282. def cls_node_list_append(self):
  283. Node._cls_node_list.append(self)
  284. return Node._cls_node_list
  285. def cls_node_list_remove(self):
  286. Node._cls_node_list.remove(self)
  287. return Node._cls_node_list
  288. # ---------------------------------------------------------------------
  289. # --method-set---------------------------------------------------------
  290. @property
  291. def parent_node(self):
  292. return self._parent_node
  293. @parent_node.setter
  294. def parent_node(self, parent_node):
  295. self._parent_node = parent_node
  296. return self._parent_node
  297. @parent_node.getter
  298. def parent_node(self):
  299. return self._parent_node
  300. def add_child(self, child):
  301. self._children.append(child)
  302. # --method--
  303. def remove_child(self, child):
  304. self._children.remove(child)
  305. @property
  306. def children(self):
  307. return self._children
  308. def child(self, row):
  309. return self._children[row]
  310. def child_count(self):
  311. return len(self._children)
  312. def row(self):
  313. if self._parent_node != None:
  314. return self._parent_node._children.index(self)
  315. # ---------------------------------------------------------------------
  316. # --method-------------------------------------------------------------
  317. def get_sibling_node_from_hashid(self, hashid):
  318. if not isinstance(hashid, str):
  319. raise TypeError("{0}.{1}: Accepts hashids as str types!"
  320. "".format(self.__class__.__name__,
  321. 'get_sibling_node_from_hashid(hashid)'))
  322. if hashid in self.cls_node_dict.keys():
  323. return self.cls_node_dict[hashid]
  324. else:
  325. return None
  326. # --method-------------------------------------------------------------
  327. def clear_node_dep(self):
  328. self.clear_children
  329. self.clear_node_list()
  330. self.clear_node_count()
  331. return self
  332. def clear_node_list(self):
  333. Node._cls_node_list = []
  334. return Node._cls_node_list
  335. def clear_node_count(self):
  336. Node._cls_node_count = 0
  337. return Node._cls_node_count
  338. def clear_children(self):
  339. self._children = []
  340. return self._children
  341. # ---------------------------------------------------------------------
  342. # ---------------------------------------------------------------------
  343. @cachetools.cached(cachetools.LFUCache(maxsize=2048))
  344. def cache_node(self):
  345. return self
  346. # ---------------------------------------------------------------------
  347. # --method-------------------------------------------------------------
  348. def hierarchy(self, tab_level=-1):
  349. output = ''
  350. tab_level += 1
  351. for i in range(tab_level):
  352. output += '\t'
  353. output += ('{tab}/------node_name:: "{0}"\n'
  354. '{1} |type:: {2}\n'
  355. '{1} |_uni_hashid:: "{3}"\r'
  356. ''.format(self._node_name,
  357. '\t' * tab_level,
  358. self._node_type,
  359. self._uni_hashid,
  360. tab=tab_level))
  361. # TO DO:: object hierarchy "'hips'|'rightLeg'|'etc'"
  362. for child in self._children:
  363. output += child.hierarchy(tab_level)
  364. tab_level -= 1
  365. # output += '\n'
  366. return output
  367. def log_hierarchy(self):
  368. # Not implemented
  369. self._logger.autolog(self.hierarchy(),
  370. "return_node_from_hashid('{0}').log_hierarchy()"
  371. "".format(self._uni_hashid()))
  372. return
  373. # ---------------------------------------------------------------------
  374. # --method-------------------------------------------------------------
  375. # representation
  376. def __str__(self):
  377. '''Returns a nice string representation of the object.'''
  378. # TO DO: need to improve this
  379. if self.node_name == None or self.name_is_uni_hashid == True:
  380. # open parenthesis only
  381. output = ("{0}(node_name='{1}'"
  382. "".format(self.__class__.__name__,
  383. self.uni_hashid))
  384. else:
  385. output = ("{0}(node_name='{1}'"
  386. "".format(self.__class__.__name__,
  387. self.node_name))
  388. if self.parent_node != None:
  389. output += (", parent_node=return_node_from_hashid('{0}')"
  390. "".format(self.parent_node.uni_hashid))
  391. if len(self.kwargs_dict) > 0:
  392. for key, value in self.kwargs_dict.items():
  393. if not isinstance(value, str):
  394. output += (", {0}={1}".format(key, value))
  395. else: # if a str add the extra quotes
  396. output += (", {0}='{1}'".format(key, value))
  397. # add the close parenthesis
  398. output += ')'
  399. if self.name_is_uni_hashid == True or self.temp_node:
  400. output = ("return_node_from_hashid('{0}')"
  401. "".format(self.node_name))
  402. # Node(self, node_name, parent_node=None, path='')
  403. return output
  404. # representation
  405. def __repr__(self):
  406. return '{0}({1})\r'.format(self.__class__.__name__, self.__dict__)
  407. # --Class End--------------------------------------------------------------
  408. class ClassProperty(property):
  409. """Decorator"""
  410. def __get__(self, cls, owner):
  411. return self.fget.__get__(None, owner)()
  412. # --Class End--------------------------------------------------------------
  413. ###########################################################################
  414. # tests(), code block for testing module
  415. # -------------------------------------------------------------------------
  416. def tests():
  417. default_node = Node()
  418. print(str(default_node))
  419. print(repr(default_node))
  420. print(default_node.uni_hashid)
  421. print(default_node.kwargs_dict)
  422. print(default_node.message_header)
  423. print(default_node.node_name)
  424. print(default_node.parent_node)
  425. print(default_node.node_class_index)
  426. # print(default_node.cls_node_dict)
  427. # temp_node = Node()
  428. temp_node = Node(temp_node=True)
  429. print(temp_node)
  430. test_default_node = Node(temp_node=True).get_sibling_node_from_hashid('kxYLm0XQeXJ7jWaP')
  431. print(test_default_node)
  432. another_test = return_node_from_hashid('kxYLm0XQeXJ7jWaP')
  433. print(another_test)
  434. second_node = Node(node_name='foo', parent_node=default_node)
  435. print(str(second_node))
  436. print(second_node.uni_hashid)
  437. print(second_node.kwargs_dict)
  438. print(second_node.message_header)
  439. print(second_node.node_name)
  440. print(second_node.parent_node.node_name)
  441. print(second_node.node_class_index)
  442. # print(second_node.cls_node_dict)
  443. second_child = Node(node_name='fooey', parent_node=another_test)
  444. print(str(second_child))
  445. print(second_child.uni_hashid)
  446. print(second_child.kwargs_dict)
  447. print(second_child.message_header)
  448. print(second_child.node_name)
  449. print(second_child.parent_node.node_name)
  450. print(second_child.node_class_index)
  451. # print(second_child.cls_node_dict)
  452. third_node = Node(node_name='kablooey', parent_node=second_node,
  453. message_header='Node(): CUSTOM Message')
  454. print(str(third_node))
  455. print(third_node.uni_hashid)
  456. print(third_node.kwargs_dict)
  457. print(third_node.message_header)
  458. print(third_node.node_name)
  459. print(third_node.parent_node.node_name)
  460. print(third_node.node_class_index)
  461. fourth_node = Node(parent_node=second_node,
  462. message_header='Node(): CUSTOM Message')
  463. print(str(fourth_node))
  464. print(fourth_node.uni_hashid)
  465. print(fourth_node.kwargs_dict)
  466. print(fourth_node.message_header)
  467. print(fourth_node.node_name)
  468. print(fourth_node.parent_node.node_name)
  469. print(fourth_node.node_class_index)
  470. kwarg_test_child = Node(node_name='kwargChild', parent_node=default_node, garble=1001001) # custom kwarg
  471. print(str(kwarg_test_child))
  472. print(kwarg_test_child.uni_hashid)
  473. print(kwarg_test_child.node_name)
  474. print(kwarg_test_child.parent_node.node_name)
  475. print(kwarg_test_child.kwargs_dict)
  476. # check the custom arg/property garble
  477. print(kwarg_test_child.garble)
  478. # check the node hierarchy
  479. print(default_node.hierarchy())
  480. # retreive a node from it's known hashid
  481. prime_node = return_node_from_hashid('kxYLm0XQeXJ7jWaP')
  482. print(prime_node.uni_hashid) # verify hasid
  483. # should return the same node as default_node
  484. print(prime_node.node_name) # should be 'PRIME'
  485. return
  486. # -------------------------------------------------------------------------
  487. def cache_tests():
  488. cache = cachetools.LFUCache(maxsize=128)
  489. runner = scheduler()
  490. cache["HogJonny"] = 1001001
  491. runner.enter(2, 1, display_cached_value,
  492. kwargs={'cache': cache, 'cache_key': 'HogJonny'})
  493. runner.enter(6, 1, display_cached_value,
  494. kwargs={'cache': cache, 'cache_key': 'HogJonny'})
  495. runner.run()
  496. return
  497. # -------------------------------------------------------------------------
  498. def main():
  499. return
  500. # - END, main() --
  501. ###########################################################################
  502. # --call block-------------------------------------------------------------
  503. if __name__ == "__main__":
  504. print ("# ----------------------------------------------------------------------- #")
  505. print ('~ noodly.Node ... Running script as __main__')
  506. print ("# ----------------------------------------------------------------------- #\r")
  507. # run simple tests
  508. tests()
  509. cache_tests()