You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
632 lines
21 KiB
Python
632 lines
21 KiB
Python
"""
|
|
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()
|