''' The DynamicObject module supports dynamic loading of YAML defined objects into Python class objects. Object can be sub-classed to allow direct binding of methods having matching signatures. $Id$ ''' import urllib import os import re import yaml # YAML Ain't Markup Language import numpy as np import copy import inspect import PrecisionTime import time import sys import datetime import collections # Replacement Loader for PyYAML to keep dictionaries in-order: import OrderedYAML #OrderedYAML.collections class Object(object): """ Loads a YAML defined python class dynamically using the supplied URI, which may be a file, directory, web hyper-link, or hyper-linked directory. """ # Dictionary containing all known Object class names and corresponding class objects dynamicClasses = collections.OrderedDict() def __init__(self, object_uri=None, revision=None, recursive=False): if isinstance(object_uri, file): # URI is a yaml file - read it. self.yaml = file.read() elif object_uri == None: self.yaml = None elif isinstance(object_uri, str): if object_uri.endswith('.yml'): # URI is a web hyper-linked yaml file - read it. self.yaml = urllib.urlopen(object_uri).read() else: # URI is a (hyper-linked?) directory - try reading it. #print "URI is a directory." try: self.files = self.__parseLink(object_uri, recursive) except IOError: # URI is a local directory - get a list of YAML files in it self.files = self.__getYamlFiles(object_uri, recursive) # For each YAML file found, create a new DynamicObject of it: self.yaml = [] for fn in self.files: self.yaml.append(Object(fn)) else: print "Invalid URI supplied: %s"%(object_uri,) def __parseLink(self, object_uri, recursive): """ Returns a listing of all YAML files located in the hyper-link directory given by page. """ page = urllib.urlopen(object_uri).read() #print "URI is a URL directory: %s"%(object_uri,) pattern = re.compile(r'') # List of files contained in the directory at the given URL, ignoring # any "?" / GET query-string locations given: files = [x[9:-2] for x in pattern.findall(page) if not x[9:-2].startswith('?')] #print files yamlFiles = [] dirs = [] for fn in files: if not fn.startswith('/'): # Ignore absolute paths... path = os.path.join(object_uri, fn) #print path # Keep list of YAML files found... if fn.endswith('.yml'): yamlFiles.append(path) # Keep list of directories found... elif recursive and fn.endswith('/'): dirs.append(path) if recursive: #print dirs for path in dirs: yamlFiles += self.__parseLink(path,recursive) return yamlFiles def __getYamlFiles(self, local_dir, recursive): """ Returns a listing of all YAML files located in the given directory, recursing if requested. """ yamlFiles = [] dirs = [] for fn in os.listdir(local_dir): path = os.path.join(local_dir, fn) # List of YAML files found... if fn.endswith('.yml'): yamlFiles.append(path) # List of directories found... elif recursive and os.path.isdir(path): dirs.append(path) # Recurse if desired: if recursive: for path in dirs: yamlFiles += self.__getYamlFiles(path,recursive) return yamlFiles def equals(self, obj, compare_time_created=True): """ Returns True iff self has identical attributes (numerically) to obj (no extras) """ if not isinstance(obj, Object): return False self_keys = self.__dict__.keys() obj_keys = obj.__dict__.keys() if not self_keys == obj_keys: return False for key in self_keys: obj_keys.remove(key) self_value, obj_value = self.__dict__[key], obj.__dict__[key] if isinstance(self_value, Object): if not self_value.equals(obj_value, compare_time_created): return False elif isinstance(self_value, np.ndarray): m1 = map(repr,self_value.flat) m2 = map(repr,obj_value.flat) ret = m1 == m2 if not ret: return False else: if not self_value == obj_value: # Return False iff the different times are important return key == '__time_created' and not compare_time_created return obj_keys == [] # no more keys --> the objects are identical def sizeof(self): """ Recursively computes the size in bytes of the given Dynamic Object """ sz = 0 values = self.__dict__.values() for val in values: if isinstance(val, Object): sz += val.sizeof() elif isinstance(val, np.ndarray): sz += val.nbytes elif hasattr(val, 'dtype') and hasattr(val.dtype, 'itemsize'): sz += val.dtype.itemsize else: sz += sys.getsizeof(val) return sz # Automatic methods for accessing meta-data getters = ['__object_name', '__revision_number', '__revision_id', '__revision_source', '__revision_tag', '__time_created'] def getObjectName(self): return self.__class__.meta_attributes['__object_name'] def getRevisionNumber(self): return self.__class__.meta_attributes['__revision_number'] def getRevisionId(self): return self.__class__.meta_attributes['__revision_id'] def getRevisionSource(self): return self.__class__.meta_attributes['__revision_source'] def getRevisionTag(self): return self.__class__.meta_attributes['__revision_tag'] def getTimeCreated(self): return getattr(self, "__time_created") """ __getters = [('ObjectName', getObjectName), ('RevisionNumber', getRevisionNumber), ('RevisionId', getRevisionId), ('RevisionSource', getRevisionSource), ('RevisionTag', getRevisionTag)] def __repr__(self): meta_atts = repr([(x[0], x[1](self)) for x in Object.__getters]) atts = repr(self.__dict__) return "Object(%s, %s)"%(atts, meta_atts) """ class SignatureException(Exception): """ Exception thrown when a data or method signature is unknown or invalid for a particular Object. """ def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class _IDLTag(object): """ IDLTag (aka Interface Definition Language Tag) is an abstract helper class used by the Factory to define built-in tags used specifically for our IDL """ def __init__(self, yamlString): self.yamlString = yamlString def __repr__(self): return self.yamlString class _Reference(_IDLTag): """ Helper class for Factory: Objects can be composed of other objects, requiring a Reference to the other object. """ def __repr__(self): return "Ref(%s)"%(self.yamlString,) class _Method(_IDLTag): """ Helper class for Factory: Objects have methods associated with them - this tag tells the Factory that a method signature follows (in dict format) """ def __repr__(self): return "Method(%r)"%(self.yamlString,) class Binary(Object): def __init__(self, binary_type, value=None): self.binary_type = binary_type self.value = value import Lookup class BuiltinDtype(_IDLTag): """ Helper class for Factory: Object parameters each have a certain data type (either dtype.xxxx for numpy compatible data types, or one of the generic python data types (i.e. int, bool, str...) __addYamlConstructor in Factory registers all of the tags listed as keys in the dtypes dictionary.""" def __init__(self, yamlString, tag=None): self.tag = tag[1:] super(BuiltinDtype, self).__init__(yamlString) #print self.tag try: self.dtype = Lookup.numpy_dtypes[self.tag] except KeyError: self.dtype = Lookup.builtin_objects[self.tag] def __repr__(self): return "_BuiltinType(%s,%s)"%(self.yamlString, self.tag) # Register hexadecimal representation of numpy dtypes in YAML class _Parameter: """ Helper class for Factory: Contains the name, default value, and length (if an array) of an object initialization parameter. """ def __init__(self, name, hasDefault=False, default=None, length=None, classType=None): self.name = name self.hasDefault = hasDefault self.default = default self.length = length if isinstance(classType, None.__class__) and not isinstance(default, None.__class__): self.classType = default.__class__ else: self.classType = classType class _UnresolvedType: """ Used to indicate a data type which has not yet been parsed (i.e. for recursive data-types. """ def __init__(self, yamlObject): # Either the name of the class we couldn't resolve, or a dictionary # containing the name and a default value self.yamlObject = yamlObject class UnresolvedTypeException(Exception): """ Raised when a !ref tag is used, but the reference cannot be resolved """ pass def get_class(kls): """ Returns a pointer to the class instance with the name kls Function acquired from http://stackoverflow.com/questions/452969/ """ parts = kls.split('.') module = ".".join(parts[:-1]) m = __import__( module ) for comp in parts[1:]: m = getattr(m, comp) return m # Aliased constructor & representer adders for easily swapping between Ordered and non-Ordered: def add_constructor(tag, constructor): #yaml.add_constructor(tag, constructor) OrderedYAML.Loader.add_constructor(tag, constructor) def add_representer(cls, representer): #yaml.add_representer(cls, representer) OrderedYAML.Dumper.add_representer(cls, representer) # Implicit constructor for _Reference objects using the !ref tag: def __ref_constructor(loader, node): if isinstance(node, yaml.nodes.MappingNode): return _Reference(loader.construct_mapping(node)) else: return _Reference(loader.construct_scalar(node)) add_constructor(u'!ref', __ref_constructor) # Method constructor using !method tag: def __method_constructor(loader, node): if isinstance(node, yaml.nodes.MappingNode): return _Method(loader.construct_mapping(node)) else: return _Method(loader.construct_scalar(node)) add_constructor(u'!method', __method_constructor) # Generic constructor for any _BuiltinDtype def __dtype_constructor(loader, node): if isinstance(node, yaml.nodes.SequenceNode): ret = BuiltinDtype(loader.construct_sequence(node), tag=node.tag) elif isinstance(node, yaml.nodes.MappingNode): ret = BuiltinDtype(loader.construct_mapping(node), tag=node.tag) else: ret = BuiltinDtype(loader.construct_scalar(node), tag=node.tag) return ret # Register YAML constructors for each builtin type: for dtype in Lookup.numpy_dtypes.keys() + Lookup.builtin_objects.keys(): add_constructor(u'!%s'%(dtype,), __dtype_constructor) class FactoryLoader(OrderedYAML.Loader): """ A YAML Loader specifically designed to load YAML object definitions (as opposed to actual instances of the objects) """ def construct_yaml_timestamp(self, node): """ Make empty timestamps (None/null) acceptable, otherwise parse the timestamp """ if node.value == u'': name = 'YAML_DEFN_LOADED_INCORRECTLY' # in case we forget to fix the name... return _Parameter(name, hasDefault=False, classType=datetime.datetime) else: return yaml.constructor.SafeConstructor.construct_yaml_timestamp(self, node) # Override default timestamp constructor: FactoryLoader.add_constructor( u'tag:yaml.org,2002:timestamp', FactoryLoader.construct_yaml_timestamp ) import DynamicYAML class Factory: """ Load a YAML defined python class and create a class with initialization provided by this factory. This is intended as an abstract class to be sub-classed to enable complex initialization on object instantiation. Factory subclasses should override __buildClass().""" def __init__(self, dynamic_object=None, yaml=None, typeCheck='strong', parse=True, revision_dict=None): if revision_dict != None: self.revision_dict = revision_dict # Remember for when we build each individual class else: self.revision_dict = {\ "__revision_number": 0, "__revision_id": 'unknown', "__revision_source": 'unknown', "__revision_tag": 'unknown'} if parse: if dynamic_object: self.parse(dynamic_object, typeCheck=typeCheck) else: dyno = Object() dyno.yaml = yaml self.parse(dyno, typeCheck=typeCheck) def parse(self, dynamic_object, typeCheck='strong'): """ Initializer for a Factory, converting the given dynamic_object containing a (text) YAML object definition into the corresponding class-type with initializer. typeCheck parameter can be one of 'strong' or 'cast': 'strong': Class initializer should raise a TypeError when given anything but the correct type 'cast': Class initializer should attempt to cast any input to the correct type """ # Remember what kind of type-checking to do: if typeCheck not in ['strong', 'cast']: raise Exception('Incorrect input for typeCheck: %s\nExpected "strong" or "cast"'%(typeCheck)) self.typeCheck = typeCheck # Get a list of the objects to build: if isinstance(dynamic_object.yaml, list): objects = dynamic_object.yaml else: objects = [dynamic_object] # Generate a dictionary of classes from the DynamicObjects given: self.classes = dict() for obj in objects: # This loader breaks nothing anymore #everything currently loader = FactoryLoader(obj.yaml) #loader = yaml.Loader(obj.yaml) # Dictionary with method and data signatures for the current object: objDefn = [] while loader.check_data(): objDefn.append(loader.get_data()) loader.dispose() # Parse the dictionary into a class definition: objClass = self.__buildClass(objDefn) self.classes.update(objClass) def parseMethodSignature(self, sigName, methDict): """ Returns the python method corresponding to the given signature (given signature should be in the loaded YAML dict format. Override this method for recognizing complex method signatures. """ raise SignatureException("Object abstract base class doesn't support any method signatures.") def parseDataSignature(self, sigName, sig): """ Returns the Parameter object corresponding to the given signature. This method should be overridden for recognizing complex data signatures (don't forget to call super(sig) for built-in data types though!) """ # Is the object an array with explicit default elements?: if isinstance(sig.yamlString, list): #length = len(sig.yamlString) if 'dtype' in sig.tag: default = np.array(sig.yamlString, dtype=sig.dtype) elif 'binary' == sig.tag: default = Binary(sig.yamlString["type"]) else: default = sig.yamlString return _Parameter(sigName, True, default, length=None) # Is the object an array with length and default value given?: if isinstance(sig.yamlString, dict) and "len" in sig.yamlString.keys(): length = sig.yamlString["len"] # Shape is given as something like [[],[]], not [2,2] - convert if isinstance(length, list): def get_shape(lst): """ Gets the shape of a list recursively filled with empty lists """ if lst == []: return [0] return [len(lst)] + get_shape(lst[0]) if len(length) > 0: if isinstance(length[0], list): length = get_shape(length) else: pass else: length = [0] # convert [] to [0] (numpy interprets [] as [1] for shapes) if 'complex' in sig.tag: imag = sig.yamlString["default"]["imag"] real = sig.yamlString["default"]["real"] default = sig.dtype(real) + sig.dtype(imag*1j) elif 'binary' == sig.tag: default = Binary(sig.yamlString["type"]) else: default = sig.dtype(sig.yamlString["default"]) return _Parameter(sigName, True, default, length) # The object is singular, with a given value: if 'complex' in sig.tag: imag = sig.yamlString["imag"] real = sig.yamlString["real"] default = sig.dtype(real) + sig.dtype(imag*1j) return _Parameter(sigName, True, default) elif 'binary' == sig.tag: default = Binary(sig.yamlString["type"]) return _Parameter(sigName, False, default, classType=Binary) elif 'timestamp' in sig.tag: if isinstance(sig.yamlString, dict): if sig.tag in ['timestamp_picosecond', 'timestamp_ps']: try: s = sig.yamlString['second'] except KeyError: s = sig.yamlString['s'] try: ps = sig.yamlString['picosecond'] except KeyError: ps = sig.yamlString['ps'] return _Parameter(sigName, True, PrecisionTime.psTime(s, ps)) elif sig.tag in ['timestamp_nanosecond', 'timestamp_ns']: try: s = sig.yamlString['second'] except KeyError: s = sig.yamlString['s'] try: ns = sig.yamlString['nanosecond'] except KeyError: ns = sig.yamlString['ns'] return _Parameter(sigName, True, PrecisionTime.nsTime(s, ns)) else: if sig.tag in ['timestamp_picosecond', 'timestamp_ps']: return _Parameter(sigName, False, classType=PrecisionTime.psTime) elif sig.tag in ['timestamp_nanosecond', 'timestamp_ns']: return _Parameter(sigName, False, classType=PrecisionTime.nsTime) else: default = sig.dtype(sig.yamlString) return _Parameter(sigName, True, default) # not binary def __parsePythonType(self, sigName, sig): """ Returns a _Parameter object, similar to parseDataSignature, but for a basic python type. """ if isinstance(sig, collections.OrderedDict): default = dict(sig) # Type-check user-defined !!maps as dicts, not OrderedDicts. else: default = sig # The signature sig is the default value itself return _Parameter(sigName, True, default) def __parseReferenceSignature(self, sigName, ref_object, objClasses): """ Takes a reference object ref_object to be named sigName, and produces a _Parameter object with default value of None. """ # List of names of classes we've created so far: #print [x for x in objClasses] names = objClasses.keys() if ref_object.yamlString in names: defaultType = objClasses[ref_object.yamlString] return _Parameter(sigName, classType=defaultType) else: try: # Try to find the class type in globals: className = objClasses[str(ref_object.yamlString)] defaultType = get_class(className) except (ValueError, KeyError): defaultType = _UnresolvedType(ref_object.yamlString) #raise NameError("Invalid reference to module %s"%(className,)) return _Parameter(sigName, classType=defaultType) def __buildInitializer(self, className, classData): """ Constructs the initializer for an object which expects parameters listed in classData as input upon initialization. """ # Type of type-checking to use: strong = (self.typeCheck == 'strong') #cast = (self.typeCheck == 'cast') def typeCheck(param, arg): """ Checks to see if the type of arg matches that of the corresponding param, casting arg to the correct type if desired. """ if isinstance(arg, param.classType): return arg if isinstance(arg, np.ndarray) and arg.dtype.type == param.classType: if not param.hasDefault: return arg if param.default.shape == (): return arg if param.default.shape[-1] == 0: return arg if arg.shape == param.default.shape: return arg if isinstance(arg, None.__class__): return arg if strong: raise TypeError("Incorrect input type on strong type-checking."+\ " Expected %s - got %s"%(param.classType,arg.__class__)) else: # If the parameter corresponding to the given argument has a non-NoneType default # value, then attempt to cast the argument into the correct parameter type if param.hasDefault and param.default != None: if isinstance(param.default, np.ndarray): return np.array(arg, dtype=param.default.dtype) else: return param.default.__class__(arg) else: return param.classType(arg) """ attributes = {"__object_name": className, "__revision_number": self.svn_revision_number, "__revision_id": 'unknown', "__revision_source": 'unknown', "__revision_tag": 'unknown'} """ attributes = {} # Create new attributes dict for this particular class object attributes.update(self.revision_dict) # Revision info now passed into the factory attributes['__object_name'] = className def init(_self, *args, **kwargs): """ Dynamically generated initializer. """ # meta-data goes in the class, not the objects (commented the following out): """ # Initialize automatic class data for attr,value in attributes.items(): try: value = kwargs[attr] # Are we given a value to over-ride with? del kwargs[attr] # Ignore the meta attribute later except KeyError: pass setattr(_self, attr, value) """ # Set default values first (assume no parameters): for param in classData: if param.length: if isinstance(param.length, int): param.length = [param.length] default = np.empty(param.length, dtype=param.classType) if param.hasDefault: # Initialize array with default array value given: flatIter = default.flat for i in range(len(flatIter)): flatIter[i] = copy.deepcopy(param.default) else: # Initialize to None if no default given: default.fill(None) else: default = param.default setattr(_self, param.name, copy.deepcopy(default)) # Set attributes given by standard args: for i in range(len(args)): arg = typeCheck(classData[i], args[i]) setattr(_self, classData[i].name, arg) # Set named attributes (given by dictionary kwargs): for key,value in kwargs.items(): try: keyIndex = [param.name for param in classData].index(key) except ValueError: raise TypeError("'%s' is an invalid keyword argument"%(key,)) arg = typeCheck(classData[keyIndex],value) #setattr(_self, key, value) setattr(_self, key, arg) # Object instantiation / creation time (if not already present): if not kwargs.has_key('__time_created'): setattr(_self, "__time_created", np.float64(time.time())) return init, attributes def __findClass(self, className, localClasses): """ Looks for the given className first in the given dictionary of localClasses then in the global definitions, returning the corresponding class object. Raises a KeyError if the class cannot be found. """ # If class definition was in the YAML file, extend that one: if className in localClasses.keys(): return localClasses[className] # Else try finding the class definition in our global scope: try: classObj = get_class(className) except KeyError: raise KeyError("Class '%s' not found in given YAML scope or global scope."%(className,)) return classObj def __buildClass(self, objDefn): """ Takes an object definition list / dictionary objDefn (loaded from a YAML object definition file) and creates a class, dynamically binding method and data signatures to the new class. This method only performs a basic binding of method and data signatures to the new class. Object(s) having more complex initialization requirements should be given their own Factory subclass, overriding this and other methods.""" # objDefn is a list of dictionaries found in the YAML file - build each one... objClasses = dict() objClassesRev = dict() # A list of all _Parameter objects created, used to resolve recursive # or "tangled" data structures allClassData = [] for document in objDefn: # Each document can contain multiple objects - build each one. # (NOTE: objects can cross reference each other in the same document # need to resolve Reference objects as last step) for objClassName in document.keys(): # The dictionary containing method & data signatures: objDict = document[objClassName] # Extract data / attribute definitions (signatures) from the YAML dictionary # as well as method signatures and which classes this class extends: classData = [] classMethods = dict() classBases = [Object] # List structured documents result in a list of dicts each with one key: if isinstance(objDict, list): keys = [param.keys()[0] for param in objDict] # Otherwise the parameter names are just the keys of the dict else: keys = objDict.keys() # if key not found, raises AttributeError for sigName in keys: #print sigName sig = objDict[sigName] #for f in _BuiltinDtype.python_dtypes: print f.__class__ if sigName == '__extends': if isinstance(sig, str): sig = [sig] if isinstance(sig, list): for className in sig: newBase = self.__findClass(className, objClasses) # Remove Object extension if newBase extends it already: if Object in classBases and Object in inspect.getmro(newBase): classBases.remove(Object) classBases += [newBase] else: raise TypeError("Incorrect format for extending classes - %s"%(sig,)) elif isinstance(sig, BuiltinDtype): classData.append(self.parseDataSignature(sigName, sig)) elif isinstance(sig, Lookup.python_dtypes): classData.append(self.__parsePythonType(sigName, sig)) elif isinstance(sig, _Reference): classData.append(self.__parseReferenceSignature(sigName, sig, objClasses)) elif isinstance(sig, _Method): classMethods[sigName] = self.parseMethodSignature(sigName, sig.yamlString) elif isinstance(sig, (PrecisionTime.nsTime, PrecisionTime.psTime)): classData.append(_Parameter(sigName, True, sig)) elif isinstance(sig, _Parameter): # sig is already a parameter (we skipped a step) sig.name = sigName # we didn't know the name during load time - fill that in now classData.append(sig) else: msg = "Factory abstract base class doesn't " +\ "support the following signature: %r \"%s\""%(sig.__class__,str(sig)) print sig.__class__ raise SignatureException(msg) # Built-in attribute for all Dynamic Objects: classData.append(_Parameter('__time_created', classType=np.float64)) # Turn the object data / attributes into a usable __init__ method: classMethods["__init__"], meta_attributes = self.__buildInitializer(objClassName, classData) # Keep a record of the _Parameters created for later type resolution allClassData.extend(classData) """ __automaticMethods = { "getObjectName": lambda _self: getattr(_self, '__object_name'), "getRevisionNumber": lambda _self: getattr(_self, '__revision_number'), "getRevisionId": lambda _self: getattr(_self, '__revision_id'), "getRevisionSource": lambda _self: getattr(_self, '__revision_source'), "getRevisionTag": lambda _self: getattr(_self, '__revision_tag') } classMethods.update(__automaticMethods) """ # Put the method signatures into a namespace for the new class, # then dynamically build the class from this namespace. classNamespace = classMethods classNamespace["meta_attributes"] = meta_attributes cls = type(str(objClassName), tuple(classBases), classNamespace) objClasses[objClassName] = cls objClassesRev['%s.%s'%(objClassName,cls.meta_attributes["__revision_number"])] = cls # Create and register a constructor (loading) and representer (dumping) for the new class cls def construct_dynamic_object(loader, node): kwargs = loader.construct_mapping(node) # Remove revision control from loaded objects (info is in the class object!) for arg in kwargs.keys(): if arg in getattr(Object, 'getters') and arg != '__time_created': del kwargs[arg] return cls(**kwargs) revision = cls.meta_attributes["__revision_number"] DynamicYAML.Loader.add_constructor(u'!%s.%s'%(str(objClassName),revision), construct_dynamic_object) represent_dynamic_object = DynamicYAML.Dumper.represent_dynamic_object DynamicYAML.Dumper.add_representer(cls, represent_dynamic_object) def findClass(className): """ Search for the most recently added class object with given className """ try: return objClasses[className] # Look for reference to object in same YAML defn file: except KeyError: # Now look for reference to class object loaded from any YAML defn file, loading the # most recent version / revision (number) of the definition for dynClass in Object.dynamicClasses.keys()[::-1]: if dynClass.startswith(className): return Object.dynamicClasses[dynClass] # Still unresolved - raise exception: allDynamicClasses = repr(objClasses.keys() + Object.dynamicClasses.keys()) raise UnresolvedTypeException("Cannot resolve type '%s': Name not found in %s"%(className,allDynamicClasses)) def resolve(param): # Reference is just a string - that's the class name: if isinstance(param.classType.yamlObject, (str, unicode)): className = str(param.classType.yamlObject) param.classType = findClass(className) return # Reference is a dict containing class name and / or default values: if not isinstance(param.classType.yamlObject, dict): raise UnresolvedTypeException("Cannot resolve reference of type '%s'"%(param.classType.yamlObject.__class__,)) # Definitely a dict: refDict = param.classType.yamlObject # Determine the name of the class being referenced try: className = refDict["type"] except KeyError: raise KeyError("No 'type' key in reference dictionary for parameter '%s'"%(param.name,)) # Determine the class object corresponding to the class name param.classType = findClass(className) try: defaultParams = refDict["default"] except KeyError: defaultParams = None if defaultParams != None: for sub_param in defaultParams: if isinstance(sub_param.classType, _UnresolvedType): resolve(sub_param) param.default = param.classType( **defaultParams ) # Create the default object param.hasDefault = True else: param.hasDefault = False # for good measure # Is it an object array?: if "len" in refDict.keys(): param.length = refDict["len"] # Resolve any unresolved data-types: for param in allClassData: if isinstance(param.classType, _UnresolvedType): resolve(param) Object.dynamicClasses.update(objClassesRev) return objClasses def load_defn(yaml): """ Shortcut for producing a single DynamicObject class object from the provided yaml definition in string format """ return Factory(yaml=yaml).classes.values()[0]