123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 |
- """
- Copyright (c) Contributors to the Open 3D Engine Project.
- For complete copyright and license terms please see the LICENSE at the root of this distribution.
- SPDX-License-Identifier: Apache-2.0 OR MIT
- """
- # -------------------------------------------------------------------------
- # this has to be at the beginning
- from __future__ import division
- # -------------------------------------------------------------------------
- # -------------------------------------------------------------------------
- # node.py
- # simple base Node Class, useful in tool creation.
- # version: 0.1
- # author: Gallowj
- # -------------------------------------------------------------------------
- # -------------------------------------------------------------------------
- """
- Module docstring:
- A Simple Node Base Class Module, for creating basic nodes within a hierarchy.
- """
- __author__ = 'HogJonny'
- # built-ins
- import os
- import copy
- import traceback
- import string
- import logging
- # using hashids to generate unique name identifiers for nodes
- import hashids
- import cachetools
- from sched import scheduler
- # local imports
- from azpy.shared.noodely.helpers import display_cached_value
- from azpy.shared.noodely.find_arg import find_arg
- from azpy.shared.noodely.synth import synthesize
- import azpy
- from azpy.env_bool import env_bool
- from azpy.constants import ENVAR_DCCSI_GDEBUG
- from azpy.constants import ENVAR_DCCSI_DEV_MODE
- # global space
- # To Do: update to dynaconf dynamic env and settings?
- _DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
- _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
- _MODULENAME = 'azpy.shared.noodely.node'
- _log_level = int(20)
- if _DCCSI_GDEBUG:
- _log_level = int(10)
- _LOGGER = azpy.initialize_logger(_MODULENAME,
- log_to_file=False,
- default_log_level=_log_level)
- _LOGGER.debug('Starting:: {}.'.format({_MODULENAME}))
- # -------------------------------------------------------------------------
- # -------------------------------------------------------------------------
- # quick test code (remove later)
- from hashids import Hashids
- hashids = Hashids(min_length=16, salt='DCCsi')
- if _DCCSI_GDEBUG:
- print (hashids.encrypt(193487)) # test hash
- # -------------------------------------------------------------------------
- # -------------------------------------------------------------------------
- # set up logger
- _G_LOGGER = logging.getLogger(__name__)
- # -------------------------------------------------------------------------
- # -------------------------------------------------------------------------
- # Use unicode strings
- _base = str # Python 3 str (=unicode), or Python 2 bytes.
- if os.path.supports_unicode_filenames:
- try:
- _base = unicode # Python 2 unicode.
- except NameError:
- pass
- # -------------------------------------------------------------------------
- ###########################################################################
- # HELPER method functions
- # -------------------------------------------------------------------------
- def return_node_from_hashid(hashid):
- if not isinstance(hashid, str):
- raise TypeError("{0},{1}: Accepts hashids as str types!\r"
- "Input hashid:{2}\r"
- "".format('noodly',
- 'return_node_from_hashid(hashid)',
- type(hashid)))
- temp_node = Node(temp_node=True).get_sibling_node_from_hashid('{0}'.format(hashid))
- return temp_node
- # -------------------------------------------------------------------------
- # -------------------------------------------------------------------------
- class Node(object):
- """Class constructor: makes a node."""
- # share the debug state
- _DEBUG = _DCCSI_GDEBUG
- # logger
- _LOGGER = _G_LOGGER
- # class header
- message_header = 'noodly, Node(): Message'
- # class variable
- _cls_node_count = 0
- _cls_node_list = []
- _cls_node_dict = {}
- # --BASE-METHODS-------------------------------------------------------
- # --constructor-
- def __init__(self, node_name=None, parent_node=None, *args, **kwargs):
- self._logger = Node._LOGGER
- self._node_type = self.__class__.__name__
- # a dict to store properties/attrs
- # in the event an object is re-built / re-init
- # it is important to store anything here that needs retention
- self._kwargs_dict = {}
- self._children = []
- # private local access to the cls_node_list
- self._cls_node_list = Node._cls_node_list
- # -- secret keyword -----------------------------------------------
- self._temp_node = False
- temp_node, kwargs = find_arg(arg_pos_index=None, arg_tag='temp_node',
- remove_kwarg=True, in_args=args,
- in_kwargs=kwargs) # <-- kwarg only
- self._temp_node = temp_node
- if self._temp_node:
- self._kwargs_dict['temp_node'] = self._temp_node
- # -----------------------------------------------------------------
- # -- store message header -----------------------------------------
- # setup the .message_header <-- kwarg only
- message_header, kwargs = find_arg(arg_pos_index=None, arg_tag='message_header',
- remove_kwarg=True, in_args=args, in_kwargs=kwargs,
- default_value=('{0}(), Message'
- .format(self._node_type)))
- self._message_header = message_header
- # -----------------------------------------------------------------
- # -- hashid -------------------------------------------------------
- self._name_is_uni_hashid = False
- self._node_class_index = len(Node._cls_node_list)
- if Node._DEBUG:
- print ('__init__.node_class_index: {0}'.format(self._node_class_index))
- self._uni_hashid = hashids.encrypt(self._node_class_index)
- if Node._DEBUG:
- print ('__init__.uni_hashid: {0}'.format(self._uni_hashid))
- # update class dict
- if not self._temp_node:
- Node._cls_node_dict[self._uni_hashid] = self
- # -----------------------------------------------------------------
- # -- store the node name ------------------------------------------
- self._node_name = node_name
- if (self._node_class_index == 0 and self._node_name == None):
- self._node_name = 'PRIME'
- elif (self._node_class_index > 0 and self._node_name == None):
- # set a default node_name if none, based on the unihashid
- if not self._name_is_uni_hashid:
- self._node_name = self._uni_hashid
- self._name_is_uni_hashid = True
- if Node._DEBUG:
- print ('__init__.node_name: {0}'.format(self._node_name))
- # -----------------------------------------------------------------
- # -- node parent_node --------------------------------------------------
- # set up the parent_node property
- self._parent_node = parent_node
- if self._parent_node != None:
- # add this node, to the parent_nodes list of children
- try:
- self._parent_node.add_child(self)
- except:
- pass # <-- parent_node object passed is NOT a noodly.node?
- # Update class variables
- Node.cls_node_count_up(self)
- Node.cls_node_list_append(self)
- # -----------------------------------------------------------------
- # -----------------------------------------------------------------
- # arbitrary argument properties
- # check postions *args and **kwargs
- # any kwargs left will be used to synthesize a property
- try:
- # checking import due to the way the code is structured
- # the code was passing even if module was not imported
- synthesize
- synthExists = True
- except Exception as e:
- print(e)
- raise e
- for key, value in kwargs.items():
- self._kwargs_dict[key] = value
- try:
- synthesize(self, key, value)
- except Exception as e: # <-- maybe it can't synthesize?
- # in which case fall back to setting the property
- print(e)
- code = compile(r'self._{0}={1}'.format(key, value), 'synthProp', 'exec')
- pass
- if Node._DEBUG:
- print("{0}:{1}".format(key, value))
- # -----------------------------------------------------------------
- # if temp node, adjust the class counter
- if temp_node:
- self.cls_node_count_down()
- self.cls_node_list_remove()
- # -- properties ------------------------------------------------------------
- @property
- def logger(self):
- return self._logger
- @logger.setter
- def logger(self, logger):
- self._logger = logger
- return self._logger
- @logger.getter
- def logger(self):
- return self._logger
- @property
- def kwargs_dict(self):
- return self._kwargs_dict
- @kwargs_dict.getter
- def kwargs_dict(self):
- return self._kwargs_dict
- @property
- def message_header(self):
- return self._message_header
- @message_header.setter
- def message_header(self, message_header):
- self._message_header = message_header
- return self._message_header
- @property
- def node_type(self):
- return self._node_type
- @node_type.setter
- def node_type(self, node_type):
- self._node_type = node_type
- return self._node_type
- @node_type.getter
- def node_type(self):
- return self._node_type
- @property
- def temp_node(self):
- return self._temp_node
- @temp_node.setter
- def temp_node(self, temp_node):
- self._temp_node = temp_node
- return self._temp_node
- @temp_node.getter
- def temp_node(self):
- return self._temp_node
- @property
- def node_name(self):
- return self._node_name
- @node_name.setter
- def node_name(self, nameStr):
- if nameStr != None:
- if not isinstance(nameStr, str):
- raise TypeError("{0}, {1}: Accepts str types!"
- "".format(self.__class__.__name__,
- self._node_name.__name__))
- try:
- self._node_name = nameStr
- except:
- synthesize(self, '_node_name', None)
- return self._node_name
- @node_name.getter
- def node_name(self):
- return self._node_name
- @property
- def name_is_uni_hashid(self):
- return self._name_is_uni_hashid
- @node_type.setter
- def name_is_uni_hashid(self, value):
- self._name_is_uni_hashid = value
- return self._name_is_uni_hashid
- @node_type.getter
- def name_is_uni_hashid(self):
- return self._name_is_uni_hashid
- @property
- def node_class_index(self):
- return self._node_class_index
- @property
- def uni_hashid(self):
- return self._uni_hashid
- @property
- def cls_node_dict(self):
- return Node._cls_node_dict
- # ---------------------------------------------------------------------
- # --method-set---------------------------------------------------------
- def cls_node_count_up(self):
- Node._cls_node_count += 1
- return Node._cls_node_count
- def cls_node_count_down(self):
- Node._cls_node_count -= 1
- return Node._cls_node_count
- def cls_node_list_append(self):
- Node._cls_node_list.append(self)
- return Node._cls_node_list
- def cls_node_list_remove(self):
- Node._cls_node_list.remove(self)
- return Node._cls_node_list
- # ---------------------------------------------------------------------
- # --method-set---------------------------------------------------------
- @property
- def parent_node(self):
- return self._parent_node
- @parent_node.setter
- def parent_node(self, parent_node):
- self._parent_node = parent_node
- return self._parent_node
- @parent_node.getter
- def parent_node(self):
- return self._parent_node
- def add_child(self, child):
- self._children.append(child)
- # --method--
- def remove_child(self, child):
- self._children.remove(child)
- @property
- def children(self):
- return self._children
- def child(self, row):
- return self._children[row]
- def child_count(self):
- return len(self._children)
- def row(self):
- if self._parent_node != None:
- return self._parent_node._children.index(self)
- # ---------------------------------------------------------------------
- # --method-------------------------------------------------------------
- def get_sibling_node_from_hashid(self, hashid):
- if not isinstance(hashid, str):
- raise TypeError("{0}.{1}: Accepts hashids as str types!"
- "".format(self.__class__.__name__,
- 'get_sibling_node_from_hashid(hashid)'))
- if hashid in self.cls_node_dict.keys():
- return self.cls_node_dict[hashid]
- else:
- return None
- # --method-------------------------------------------------------------
- def clear_node_dep(self):
- self.clear_children
- self.clear_node_list()
- self.clear_node_count()
- return self
- def clear_node_list(self):
- Node._cls_node_list = []
- return Node._cls_node_list
- def clear_node_count(self):
- Node._cls_node_count = 0
- return Node._cls_node_count
- def clear_children(self):
- self._children = []
- return self._children
- # ---------------------------------------------------------------------
- # ---------------------------------------------------------------------
- @cachetools.cached(cachetools.LFUCache(maxsize=2048))
- def cache_node(self):
- return self
- # ---------------------------------------------------------------------
- # --method-------------------------------------------------------------
- def hierarchy(self, tab_level=-1):
- output = ''
- tab_level += 1
- for i in range(tab_level):
- output += '\t'
- output += ('{tab}/------node_name:: "{0}"\n'
- '{1} |type:: {2}\n'
- '{1} |_uni_hashid:: "{3}"\r'
- ''.format(self._node_name,
- '\t' * tab_level,
- self._node_type,
- self._uni_hashid,
- tab=tab_level))
- # TO DO:: object hierarchy "'hips'|'rightLeg'|'etc'"
- for child in self._children:
- output += child.hierarchy(tab_level)
- tab_level -= 1
- # output += '\n'
- return output
- def log_hierarchy(self):
- # Not implemented
- self._logger.autolog(self.hierarchy(),
- "return_node_from_hashid('{0}').log_hierarchy()"
- "".format(self._uni_hashid()))
- return
- # ---------------------------------------------------------------------
- # --method-------------------------------------------------------------
- # representation
- def __str__(self):
- '''Returns a nice string representation of the object.'''
- # TO DO: need to improve this
- if self.node_name == None or self.name_is_uni_hashid == True:
- # open parenthesis only
- output = ("{0}(node_name='{1}'"
- "".format(self.__class__.__name__,
- self.uni_hashid))
- else:
- output = ("{0}(node_name='{1}'"
- "".format(self.__class__.__name__,
- self.node_name))
- if self.parent_node != None:
- output += (", parent_node=return_node_from_hashid('{0}')"
- "".format(self.parent_node.uni_hashid))
- if len(self.kwargs_dict) > 0:
- for key, value in self.kwargs_dict.items():
- if not isinstance(value, str):
- output += (", {0}={1}".format(key, value))
- else: # if a str add the extra quotes
- output += (", {0}='{1}'".format(key, value))
- # add the close parenthesis
- output += ')'
- if self.name_is_uni_hashid == True or self.temp_node:
- output = ("return_node_from_hashid('{0}')"
- "".format(self.node_name))
- # Node(self, node_name, parent_node=None, path='')
- return output
- # representation
- def __repr__(self):
- return '{0}({1})\r'.format(self.__class__.__name__, self.__dict__)
- # --Class End--------------------------------------------------------------
- class ClassProperty(property):
- """Decorator"""
- def __get__(self, cls, owner):
- return self.fget.__get__(None, owner)()
- # --Class End--------------------------------------------------------------
- ###########################################################################
- # tests(), code block for testing module
- # -------------------------------------------------------------------------
- def tests():
- default_node = Node()
- print(str(default_node))
- print(repr(default_node))
- print(default_node.uni_hashid)
- print(default_node.kwargs_dict)
- print(default_node.message_header)
- print(default_node.node_name)
- print(default_node.parent_node)
- print(default_node.node_class_index)
- # print(default_node.cls_node_dict)
- # temp_node = Node()
- temp_node = Node(temp_node=True)
- print(temp_node)
- test_default_node = Node(temp_node=True).get_sibling_node_from_hashid('kxYLm0XQeXJ7jWaP')
- print(test_default_node)
- another_test = return_node_from_hashid('kxYLm0XQeXJ7jWaP')
- print(another_test)
- second_node = Node(node_name='foo', parent_node=default_node)
- print(str(second_node))
- print(second_node.uni_hashid)
- print(second_node.kwargs_dict)
- print(second_node.message_header)
- print(second_node.node_name)
- print(second_node.parent_node.node_name)
- print(second_node.node_class_index)
- # print(second_node.cls_node_dict)
- second_child = Node(node_name='fooey', parent_node=another_test)
- print(str(second_child))
- print(second_child.uni_hashid)
- print(second_child.kwargs_dict)
- print(second_child.message_header)
- print(second_child.node_name)
- print(second_child.parent_node.node_name)
- print(second_child.node_class_index)
- # print(second_child.cls_node_dict)
- third_node = Node(node_name='kablooey', parent_node=second_node,
- message_header='Node(): CUSTOM Message')
- print(str(third_node))
- print(third_node.uni_hashid)
- print(third_node.kwargs_dict)
- print(third_node.message_header)
- print(third_node.node_name)
- print(third_node.parent_node.node_name)
- print(third_node.node_class_index)
- fourth_node = Node(parent_node=second_node,
- message_header='Node(): CUSTOM Message')
- print(str(fourth_node))
- print(fourth_node.uni_hashid)
- print(fourth_node.kwargs_dict)
- print(fourth_node.message_header)
- print(fourth_node.node_name)
- print(fourth_node.parent_node.node_name)
- print(fourth_node.node_class_index)
- kwarg_test_child = Node(node_name='kwargChild', parent_node=default_node, garble=1001001) # custom kwarg
- print(str(kwarg_test_child))
- print(kwarg_test_child.uni_hashid)
- print(kwarg_test_child.node_name)
- print(kwarg_test_child.parent_node.node_name)
- print(kwarg_test_child.kwargs_dict)
- # check the custom arg/property garble
- print(kwarg_test_child.garble)
- # check the node hierarchy
- print(default_node.hierarchy())
- # retreive a node from it's known hashid
- prime_node = return_node_from_hashid('kxYLm0XQeXJ7jWaP')
- print(prime_node.uni_hashid) # verify hasid
- # should return the same node as default_node
- print(prime_node.node_name) # should be 'PRIME'
- return
- # -------------------------------------------------------------------------
- def cache_tests():
- cache = cachetools.LFUCache(maxsize=128)
- runner = scheduler()
- cache["HogJonny"] = 1001001
- runner.enter(2, 1, display_cached_value,
- kwargs={'cache': cache, 'cache_key': 'HogJonny'})
- runner.enter(6, 1, display_cached_value,
- kwargs={'cache': cache, 'cache_key': 'HogJonny'})
- runner.run()
- return
- # -------------------------------------------------------------------------
- def main():
- return
- # - END, main() --
- ###########################################################################
- # --call block-------------------------------------------------------------
- if __name__ == "__main__":
- print ("# ----------------------------------------------------------------------- #")
- print ('~ noodly.Node ... Running script as __main__')
- print ("# ----------------------------------------------------------------------- #\r")
- # run simple tests
- tests()
- cache_tests()
|