diff options
author | Doug Hammond <doughammond@hamsterfight.co.uk> | 2010-11-18 00:28:22 +0300 |
---|---|---|
committer | Doug Hammond <doughammond@hamsterfight.co.uk> | 2010-11-18 00:28:22 +0300 |
commit | 9183f20fb4828bbd562cb6a2b8f65c8750c3c27b (patch) | |
tree | 756297ad1532112b2a4f61a3278e47643b7117a7 /release | |
parent | b99a11bc3ce7ccc3bae35ac253d8171d2f02a285 (diff) |
extensions_framework: lots of docs and code formatting to be more pep8-like
Diffstat (limited to 'release')
7 files changed, 386 insertions, 227 deletions
diff --git a/release/scripts/modules/extensions_framework/__init__.py b/release/scripts/modules/extensions_framework/__init__.py index fdb7ddd706d..6c4ced376c5 100644 --- a/release/scripts/modules/extensions_framework/__init__.py +++ b/release/scripts/modules/extensions_framework/__init__.py @@ -24,131 +24,170 @@ # # ***** END GPL LICENCE BLOCK ***** # +import time -import os, time import bpy -#---------------------------------------------------------------------------------------------------------------------- +from extensions_framework.ui import EF_OT_msg + +bpy.types.register(EF_OT_msg) +del EF_OT_msg + def log(str, popup=False, module_name='EF'): - print("[%s %s] %s" % (module_name, time.strftime('%Y-%b-%d %H:%M:%S'), str)) + """Print a message to the console, prefixed with the module_name + and the current time. If the popup flag is True, the message will + be raised in the UI as a warning using the operator bpy.ops.ef.msg. + + """ + print("[%s %s] %s" % + (module_name, time.strftime('%Y-%b-%d %H:%M:%S'), str)) if popup: bpy.ops.ef.msg( msg_type='WARNING', msg_text=str ) -#---------------------------------------------------------------------------------------------------------------------- - -from .ui import EF_OT_msg - -bpy.types.register(EF_OT_msg) -ef_path = os.path.realpath( os.path.dirname(__file__) ) -# log('Extensions_Framework detected and loaded from %s'%ef_path) - -del EF_OT_msg, os - -#---------------------------------------------------------------------------------------------------------------------- - -class ef(object): - ''' - Extensions Framework base class - ''' - - added_property_cache = {} +added_property_cache = {} def init_properties(obj, props, cache=True): - if not obj in ef.added_property_cache.keys(): - ef.added_property_cache[obj] = [] + """Initialise custom properties in the given object or type. + The props list is described in the declarative_property_group + class definition. If the cache flag is False, this function + will attempt to redefine properties even if they have already been + added. + + """ + + if not obj in added_property_cache.keys(): + added_property_cache[obj] = [] for prop in props: - if cache and prop['attr'] in ef.added_property_cache[obj]: - continue try: + if cache and prop['attr'] in added_property_cache[obj]: + continue + if prop['type'] == 'bool': t = bpy.props.BoolProperty - a = {k: v for k,v in prop.items() if k in ['name','description','default']} + a = {k: v for k,v in prop.items() if k in ['name', + 'description','default']} elif prop['type'] == 'collection': t = bpy.props.CollectionProperty - a = {k: v for k,v in prop.items() if k in ["ptype", "name", "description"]} + a = {k: v for k,v in prop.items() if k in ["ptype", "name", + "description"]} a['type'] = a['ptype'] del a['ptype'] elif prop['type'] == 'enum': t = bpy.props.EnumProperty - a = {k: v for k,v in prop.items() if k in ["items", "name", "description", "default"]} + a = {k: v for k,v in prop.items() if k in ["items", "name", + "description", "default"]} elif prop['type'] == 'float': t = bpy.props.FloatProperty - a = {k: v for k,v in prop.items() if k in ["name", "description", "min", "max", "soft_min", "soft_max", "default", "precision"]} + a = {k: v for k,v in prop.items() if k in ["name", + "description", "min", "max", "soft_min", "soft_max", + "default", "precision"]} elif prop['type'] == 'float_vector': t = bpy.props.FloatVectorProperty - a = {k: v for k,v in prop.items() if k in ["name", "description", "min", "max", "soft_min", "soft_max", "default", "precision", "size", "subtype"]} + a = {k: v for k,v in prop.items() if k in ["name", + "description", "min", "max", "soft_min", "soft_max", + "default", "precision", "size", "subtype"]} elif prop['type'] == 'int': t = bpy.props.IntProperty - a = {k: v for k,v in prop.items() if k in ["name", "description", "min", "max", "soft_min", "soft_max", "default"]} + a = {k: v for k,v in prop.items() if k in ["name", + "description", "min", "max", "soft_min", "soft_max", + "default"]} elif prop['type'] == 'pointer': t = bpy.props.PointerProperty - a = {k: v for k,v in prop.items() if k in ["ptype", "name", "description"]} + a = {k: v for k,v in prop.items() if k in ["ptype", "name", + "description"]} a['type'] = a['ptype'] del a['ptype'] elif prop['type'] == 'string': t = bpy.props.StringProperty - a = {k: v for k,v in prop.items() if k in ["name", "description", "maxlen", "default", "subtype"]} + a = {k: v for k,v in prop.items() if k in ["name", + "description", "maxlen", "default", "subtype"]} else: - #ef.log('Property type not recognised: %s' % prop['type']) continue setattr(obj, prop['attr'], t(**a)) - ef.added_property_cache[obj].append(prop['attr']) - #log('Created property %s.%s' % (obj, prop['attr'])) + added_property_cache[obj].append(prop['attr']) except KeyError: + # Silently skip invalid entries in props continue + class declarative_property_group(bpy.types.IDPropertyGroup): + """A declarative_property_group describes a set of logically + related properties, using a declarative style to list each + property type, name, values, and other relevant information. + The information provided for each property depends on the + property's type. + + The properties list attribute in this class describes the + properties present in this group. + + Some additional information about the properties in this group + can be specified, so that a UI can be generated to display them. + To that end, the controls list attribute and the visibility dict + attribute are present here, to be read and interpreted by a + property_group_renderer object. + See extensions_framework.ui.property_group_renderer. + + """ + + """This list controls the order of property layout when rendered + by a property_group_renderer. This can be a nested list, where each + list becomes a row in the panel layout. Nesting may be to any depth. - controls = [ - # this list controls the order of property - # layout when rendered by a property_group_renderer. - # This can be a nested list, where each list - # becomes a row in the panel layout. - # nesting may be to any depth - ] - - # Include some properties in display based on values of others - visibility = { - # See ef.validate for test syntax + """ + controls = [] + + """The visibility dict controls the display of properties based on + the value of other properties. See extensions_framework.validate + for test syntax. + + """ + visibility = {} + + """The properties list describes each property to be created. Each + item should be a dict of args to pass to a + bpy.props.<?>Property function, with the exception of 'type' + which is used and stripped by extensions_framework in order to + determine which Property creation function to call. + + Example item: + { + 'type': 'int', # bpy.props.IntProperty + 'attr': 'threads', # bpy.types.<type>.threads + 'name': 'Render Threads', # Rendered next to the UI + 'description': 'Number of threads to use', # Tooltip text in the UI + 'default': 1, + 'min': 1, + 'soft_min': 1, + 'max': 64, + 'soft_max': 64 } - # engine-specific properties to create in the scene. - # Each item should be a dict of args to pass to a - # bpy.types.Scene.<?>Property function, with the exception - # of 'type' which is used and stripped by ef - properties = [ - # example: - #{ - # 'type': 'int', - # 'attr': 'threads', - # 'name': 'Render Threads', - # 'description': 'Number of threads to use', - # 'default': 1, - # 'min': 1, - # 'soft_min': 1, - # 'max': 64, - # 'soft_max': 64 - #}, - ] + """ + properties = [] def draw_callback(self, context): - ''' - Sub-classes can override this to get a callback - when rendered by a property_group_renderer class - ''' + """Sub-classes can override this to get a callback when + rendering is completed by a property_group_renderer sub-class. + + """ pass @classmethod def get_exportable_properties(cls): + """Return a list of properties which have the 'save_in_preset' key + set to True, and hence should be saved into preset files. + + """ + out = [] for prop in cls.properties: if 'save_in_preset' in prop.keys() and prop['save_in_preset']: diff --git a/release/scripts/modules/extensions_framework/engine.py b/release/scripts/modules/extensions_framework/engine.py index 448eb715bef..0a6fecb034a 100644 --- a/release/scripts/modules/extensions_framework/engine.py +++ b/release/scripts/modules/extensions_framework/engine.py @@ -24,12 +24,14 @@ # # ***** END GPL LICENCE BLOCK ***** # -from .plugin import plugin +from extensions_framework.plugin import plugin class engine_base(plugin): - ''' - Render Engine plugin base class - ''' + """Render Engine plugin base class + + TODO: Remove, this class hasn't grown to be useful + + """ bl_label = 'Abstract Render Engine Base Class' diff --git a/release/scripts/modules/extensions_framework/outputs/xml_output.py b/release/scripts/modules/extensions_framework/outputs/xml_output.py index 4d1af9cc877..3b1102c1888 100644 --- a/release/scripts/modules/extensions_framework/outputs/xml_output.py +++ b/release/scripts/modules/extensions_framework/outputs/xml_output.py @@ -28,36 +28,49 @@ import xml.etree.cElementTree as ET import xml.dom.minidom as MD class xml_output(object): + """This class serves to describe an XML output, it uses + cElementTree and minidom to construct and format the XML + data. + """ + + """The format dict describes the XML structure that this class + should generate, and which properties should be used to fill + the XML data structure + + """ format = {} def __str__(self): return ET.tostring(self.root) def write_pretty(self, file): + """Write a formatted XML string to file""" xml_dom = MD.parseString(ET.tostring(self.root, encoding='utf-8')) xml_dom.writexml(file, addindent=' ', newl='\n', encoding='utf-8') def pretty(self): + """Return a formatted XML string""" xml_str = MD.parseString(ET.tostring(self.root)) return xml_str.toprettyxml() - # This should be overridden in classes that produce XML conditionally def get_format(self): + """This should be overridden in classes that produce XML + conditionally + + """ return self.format def compute(self, context): + """Compute the XML output from the input format""" self.context = context self.root = ET.Element(self.root_element) self.parse_dict(self.get_format(), self.root) - #ET.dump(root) return self.root - - def make_subelement(self, elem, name): - return ET.SubElement(elem, name) - + + """Formatting functions for various data types""" format_types = { 'bool': lambda c,x: str(x).lower(), 'collection': lambda c,x: x, @@ -67,8 +80,12 @@ class xml_output(object): 'pointer': lambda c,x: x, 'string': lambda c,x: x, } - + def parse_dict(self, d, elem): + """Parse the values in the format dict and collect the + formatted data into XML structure starting at self.root + + """ for key in d.keys(): # tuple provides multiple child elements if type(d[key]) is tuple: @@ -93,4 +110,7 @@ class xml_output(object): if 'compute' in p.keys(): x.text = str(p['compute'](self.context, self)) else: - x.text = str(self.format_types[p['type']](self.context, getattr(self, d[key]))) + x.text = str( + self.format_types[p['type']](self.context, + getattr(self, d[key])) + ) diff --git a/release/scripts/modules/extensions_framework/plugin.py b/release/scripts/modules/extensions_framework/plugin.py index a6a5b719e30..76f41930fdd 100644 --- a/release/scripts/modules/extensions_framework/plugin.py +++ b/release/scripts/modules/extensions_framework/plugin.py @@ -24,21 +24,45 @@ # # ***** END GPL LICENCE BLOCK ***** # -from . import init_properties -from . import log - import bpy +from extensions_framework import init_properties +from extensions_framework import log + class plugin(object): + """Base class for plugins which wish to make use of utilities + provided in extensions_framework. Using the property_groups + attribute and the install() and uninstall() methods, a large number + of custom scene properties can be easily defined, displayed and + managed. + + TODO: Rename, 'extension' would be more appropriate than 'plugin' + + """ - # List of IDPropertyGroup types to create in the scene - property_groups = [ - # ('bpy.type prototype to attach to. eg. Scene', <declarative_property_group type>) - ] + """The property_groups defines a list of declarative_property_group + types to create in specified types during the initialisation of the + plugin. + Item format: + ('bpy.type prototype to attach to', <declarative_property_group>) + + Example item: + ('Scene', myaddon_property_group) + In this example, a new property group will be attached to + bpy.types.Scene and all of the properties described in that group + will be added to it. + See extensions_framework.declarative_property_group. + + """ + property_groups = [] @classmethod def install(r_class): - # create custom property groups + """Initialise this plugin. So far, all this does is to create + custom property groups specified in the property_groups + attribute. + + """ for property_group_parent, property_group in r_class.property_groups: call_init = False if property_group_parent is not None: @@ -52,19 +76,17 @@ class plugin(object): 'description': property_group.__name__ }]) call_init = True - #print('Created IDPropertyGroup %s.%s' % (prototype, property_group.__name__)) else: call_init = True if call_init: init_properties(property_group, property_group.properties) - #print('Initialised IDPropertyGroup %s' % property_group.__name__) log('Extension "%s" initialised' % r_class.bl_label) @classmethod def uninstall(r_class): - # unregister property groups in reverse order + """Unregister property groups in reverse order""" reverse_property_groups = [p for p in r_class.property_groups] reverse_property_groups.reverse() for property_group_parent, property_group in reverse_property_groups: diff --git a/release/scripts/modules/extensions_framework/ui.py b/release/scripts/modules/extensions_framework/ui.py index 5d29ed7245a..bf68596b823 100644 --- a/release/scripts/modules/extensions_framework/ui.py +++ b/release/scripts/modules/extensions_framework/ui.py @@ -26,9 +26,10 @@ # import bpy -from .validate import Visibility +from extensions_framework.validate import Visibility class EF_OT_msg(bpy.types.Operator): + """An operator to show simple messages in the UI""" bl_idname = 'ef.msg' bl_label = 'Show UI Message' msg_type = bpy.props.StringProperty(default='INFO') @@ -38,50 +39,81 @@ class EF_OT_msg(bpy.types.Operator): return {'FINISHED'} def _get_item_from_context(context, path): + """Utility to get an object when the path to it is known: + _get_item_from_context(context, ['a','b','c']) returns + context.a.b.c + No error checking is performed other than checking that context + is not None. Exceptions caused by invalid path should be caught in + the calling code. + + """ + if context is not None: for p in path: context = getattr(context, p) return context class property_group_renderer(object): + """Mix-in class for sub-classes of bpy.types.Panel. This class + will provide the draw() method which implements drawing one or + more property groups derived from + extensions_framework.declarative_propery_group. + The display_property_groups list attribute describes which + declarative_property_groups should be drawn in the Panel, and + how to extract those groups from the context passed to draw(). + + """ - # Choose which custom property groups this panel should draw, and - # where to find that property group in the active context - display_property_groups = [ - # ( ('scene',), 'declarative_property_group name') - ] + """The display_property_groups list attribute specifies which + custom declarative_property_groups this panel should draw, and + where to find that property group in the active context. + Example item: + ( ('scene',), 'myaddon_property_group') + In this case, this renderer will look for properties in + context.scene.myaddon_property_group to draw in the Panel. + + """ + display_property_groups = [] def draw(self, context): - ''' - Sub-classes should override this if they need to display - other (object-related) property groups - ''' - for property_group_path, property_group_name in self.display_property_groups: + """Sub-classes should override this if they need to display + other (object-related) property groups. super().draw(context) + can be a useful call in those cases. + + """ + for property_group_path, property_group_name in \ + self.display_property_groups: ctx = _get_item_from_context(context, property_group_path) property_group = getattr(ctx, property_group_name) for p in property_group.controls: - self.draw_column(p, self.layout, ctx, context, property_group=property_group) + self.draw_column(p, self.layout, ctx, context, + property_group=property_group) property_group.draw_callback(context) @staticmethod def property_reload(): - ''' - override this in sub classes to force data refresh upon scene reload - ''' + """Override this in sub classes to force data refresh upon scene reload + + TODO: Remove, this is not used anywhere + """ pass - def check_visibility(self, lookup_property, context, property_group): + def check_visibility(self, lookup_property, property_group): + """Determine if the lookup_property should be drawn in the Panel""" vt = Visibility(property_group) if lookup_property in property_group.visibility.keys(): if hasattr(property_group, lookup_property): member = getattr(property_group, lookup_property) else: member = None - return vt.test_logic(member, property_group.visibility[lookup_property]) + return vt.test_logic(member, + property_group.visibility[lookup_property]) else: return True - def draw_column(self, control_list_item, layout, context, supercontext=None, property_group=None): + def draw_column(self, control_list_item, layout, context, + supercontext=None, property_group=None): + """Draw a column's worth of UI controls in this Panel""" if type(control_list_item) is list: do_split = False @@ -91,85 +123,117 @@ class property_group_renderer(object): found_percent = sp elif type(sp) is list: for ssp in control_list_item: - do_split = do_split and self.check_visibility(ssp, context, property_group) + do_split = do_split and self.check_visibility(ssp, + property_group) else: - do_split = do_split or self.check_visibility(sp, context, property_group) + do_split = do_split or self.check_visibility(sp, + property_group) if do_split: - # print('split %s'%p) if found_percent is not None: - fp = { 'percentage': found_percent } + fp = {'percentage': found_percent} splt = layout.split(**fp) else: splt = layout.row(True) - for sp in [s for s in control_list_item if type(s) in [str, list] ]: + for sp in [s for s in control_list_item if type(s) in \ + [str, list]]: col2 = splt.column() - self.draw_column(sp, col2, context, supercontext, property_group) - #else: - # print('dont split %s'%p) + self.draw_column(sp, col2, context, supercontext, + property_group) else: - if self.check_visibility(control_list_item, context, property_group): + if self.check_visibility(control_list_item, property_group): for current_property in property_group.properties: if current_property['attr'] == control_list_item: current_property_keys = current_property.keys() if 'type' in current_property_keys: - if current_property['type'] in ['int', 'float', 'float_vector', 'enum', 'string']: + if current_property['type'] in ['int', 'float', + 'float_vector', 'enum', 'string']: layout.prop( property_group, control_list_item, - text = current_property['name'], - expand = current_property['expand'] if 'expand' in current_property_keys else False, - slider = current_property['slider'] if 'slider' in current_property_keys else False, - toggle = current_property['toggle'] if 'toggle' in current_property_keys else False, - icon_only = current_property['icon_only'] if 'icon_only' in current_property_keys else False, - event = current_property['event'] if 'event' in current_property_keys else False, - full_event = current_property['full_event'] if 'full_event' in current_property_keys else False, - emboss = current_property['emboss'] if 'emboss' in current_property_keys else True, + text = current_property['name'], + expand = current_property['expand'] \ + if 'expand' in current_property_keys \ + else False, + slider = current_property['slider'] \ + if 'slider' in current_property_keys \ + else False, + toggle = current_property['toggle'] \ + if 'toggle' in current_property_keys \ + else False, + icon_only = current_property['icon_only'] \ + if 'icon_only' in current_property_keys \ + else False, + event = current_property['event'] \ + if 'event' in current_property_keys \ + else False, + full_event = current_property['full_event'] \ + if 'full_event' in current_property_keys \ + else False, + emboss = current_property['emboss'] \ + if 'emboss' in current_property_keys \ + else True, ) if current_property['type'] in ['bool']: layout.prop( property_group, control_list_item, - text = current_property['name'], - toggle = current_property['toggle'] if 'toggle' in current_property_keys else False, - icon_only = current_property['icon_only'] if 'icon_only' in current_property_keys else False, - event = current_property['event'] if 'event' in current_property_keys else False, - full_event = current_property['full_event'] if 'full_event' in current_property_keys else False, - emboss = current_property['emboss'] if 'emboss' in current_property_keys else True, + text = current_property['name'], + toggle = current_property['toggle'] \ + if 'toggle' in current_property_keys \ + else False, + icon_only = current_property['icon_only'] \ + if 'icon_only' in current_property_keys \ + else False, + event = current_property['event'] \ + if 'event' in current_property_keys \ + else False, + full_event = current_property['full_event'] \ + if 'full_event' in current_property_keys \ + else False, + emboss = current_property['emboss'] \ + if 'emboss' in current_property_keys \ + else True, ) elif current_property['type'] in ['operator']: layout.operator(current_property['operator'], - text = current_property['text'], - icon = current_property['icon'] + text = current_property['text'], + icon = current_property['icon'] ) elif current_property['type'] in ['text']: layout.label( - text = current_property['name'] + text = current_property['name'] ) elif current_property['type'] in ['template_list']: - # row.template_list(idblock, "texture_slots", idblock, "active_texture_index", rows=2) layout.template_list( current_property['src'](supercontext, context), current_property['src_attr'], current_property['trg'](supercontext, context), current_property['trg_attr'], - rows = 4 if not 'rows' in current_property_keys else current_property['rows'], - maxrows = 4 if not 'rows' in current_property_keys else current_property['rows'], - type = 'DEFAULT' if not 'list_type' in current_property_keys else current_property['list_type'] + rows = 4 \ + if not 'rows' in current_property_keys \ + else current_property['rows'], + maxrows = 4 \ + if not 'rows' in current_property_keys \ + else current_property['rows'], + type = 'DEFAULT' \ + if not 'list_type' in current_property_keys \ + else current_property['list_type'] ) elif current_property['type'] in ['prop_search']: - # split.prop_search(tex, "uv_layer", ob.data, "uv_textures", text="") layout.prop_search( - current_property['trg'](supercontext, context), + current_property['trg'](supercontext, + context), current_property['trg_attr'], - current_property['src'](supercontext, context), + current_property['src'](supercontext, + context), current_property['src_attr'], - text = current_property['name'], + text = current_property['name'], ) else: layout.prop(property_group, control_list_item) diff --git a/release/scripts/modules/extensions_framework/util.py b/release/scripts/modules/extensions_framework/util.py index 8bbeaf74ec3..403eeb8a759 100644 --- a/release/scripts/modules/extensions_framework/util.py +++ b/release/scripts/modules/extensions_framework/util.py @@ -25,23 +25,24 @@ # # ***** END GPL LICENCE BLOCK ***** # -import datetime, os, configparser, threading, tempfile +import configparser +import datetime +import os +import tempfile +import threading import bpy config_paths = bpy.utils.script_paths() -''' -This path is set at the start of export, so that -calls to path_relative_to_export() can make all -exported paths relative to this one -''' +"""This path is set at the start of export, so that calls to +path_relative_to_export() can make all exported paths relative to +this one. +""" export_path = ''; def path_relative_to_export(p): - ''' - Return a path that is relative to the export path - ''' + """Return a path that is relative to the export path""" global export_path p = filesystem_path(p) try: @@ -49,26 +50,25 @@ def path_relative_to_export(p): except ValueError: # path on different drive on windows relp = p - #print('Resolving rel path %s -> %s' % (p, relp)) - return relp.replace('\\', '/') def filesystem_path(p): - ''' - Resolve a relative Blender path to a real filesystem path - ''' + """Resolve a relative Blender path to a real filesystem path""" if p.startswith('//'): pout = bpy.path.abspath(p) else: pout = os.path.realpath(p) - #print('Resolving FS path %s -> %s' % (p,pout)) - return pout.replace('\\', '/') # TODO: - somehow specify TYPES to get/set from config def find_config_value(module, section, key, default): + """Attempt to find the configuration value specified by string key + in the specified section of module's configuration file. If it is + not found, return default. + + """ global config_paths fc = [] for p in config_paths: @@ -97,6 +97,10 @@ def find_config_value(module, section, key, default): return default def write_config_value(module, section, key, value): + """Attempt to write the configuration value specified by string key + in the specified section of module's configuration file. + + """ global config_paths fc = [] for p in config_paths: @@ -104,7 +108,8 @@ def write_config_value(module, section, key, value): fc.append( '/'.join([p, '%s.cfg' % module])) if len(fc) < 1: - raise Exception('Cannot find a writable path to store %s config file' % module) + raise Exception('Cannot find a writable path to store %s config file' % + module) cp = configparser.SafeConfigParser() @@ -130,32 +135,35 @@ def write_config_value(module, section, key, value): return True def scene_filename(): - ''' - Construct a safe scene filename - ''' + """Construct a safe scene filename, using 'untitled' instead of ''""" filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0] if filename == '': filename = 'untitled' return bpy.path.clean_name(filename) def temp_directory(): - ''' - Return the system temp directory - ''' + """Return the system temp directory""" return tempfile.gettempdir() def temp_file(ext='tmp'): - ''' - Get a temporary filename with the given extension - ''' + """Get a temporary filename with the given extension. This function + will actually attempt to create the file.""" tf, fn = tempfile.mkstemp(suffix='.%s'%ext) os.close(tf) return fn class TimerThread(threading.Thread): - ''' - Periodically call self.kick() - ''' + """Periodically call self.kick(). The period of time in seconds + between calling is given by self.KICK_PERIOD, and the first call + may be delayed by setting self.STARTUP_DELAY, also in seconds. + self.kick() will continue to be called at regular intervals until + self.stop() is called. Since this is a thread, calling self.join() + may be wise after calling self.stop() if self.kick() is performing + a task necessary for the continuation of the program. + The object that creates this TimerThread may pass into it data + needed during self.kick() as a dict LocalStorage in __init__(). + + """ STARTUP_DELAY = 0 KICK_PERIOD = 8 @@ -169,24 +177,27 @@ class TimerThread(threading.Thread): self.LocalStorage = LocalStorage def set_kick_period(self, period): + """Adjust the KICK_PERIOD between __init__() and start()""" self.KICK_PERIOD = period + self.STARTUP_DELAY def stop(self): + """Stop this timer. This method does not join()""" self.active = False if self.timer is not None: self.timer.cancel() def run(self): - ''' - Timed Thread loop - ''' - + """Timed Thread loop""" while self.active: self.timer = threading.Timer(self.KICK_PERIOD, self.kick_caller) self.timer.start() if self.timer.isAlive(): self.timer.join() def kick_caller(self): + """Intermediary between the kick-wait-loop and kick to allow + adjustment of the first KICK_PERIOD by STARTUP_DELAY + + """ if self.STARTUP_DELAY > 0: self.KICK_PERIOD -= self.STARTUP_DELAY self.STARTUP_DELAY = 0 @@ -194,15 +205,11 @@ class TimerThread(threading.Thread): self.kick() def kick(self): - ''' - sub-classes do their work here - ''' + """Sub-classes do their work here""" pass def format_elapsed_time(t): - ''' - Format a duration in seconds as an HH:MM:SS format time - ''' + """Format a duration in seconds as an HH:MM:SS format time""" td = datetime.timedelta(seconds=t) min = td.days*1440 + td.seconds/60.0 diff --git a/release/scripts/modules/extensions_framework/validate.py b/release/scripts/modules/extensions_framework/validate.py index b2960fcf01f..d9cee8fd807 100644 --- a/release/scripts/modules/extensions_framework/validate.py +++ b/release/scripts/modules/extensions_framework/validate.py @@ -24,7 +24,7 @@ # # ***** END GPL LICENCE BLOCK ***** # -''' +""" Pure logic and validation class. By using a Subject object, and a dict of described logic tests, it @@ -45,10 +45,11 @@ class Subject(object): Tests are described thus: -Use the special list types Logic_AND and Logic_OR to describe combinations -of values and other members. Use Logic_Operator for numerical comparison. +Use the special list types Logic_AND and Logic_OR to describe +combinations of values and other members. Use Logic_Operator for +numerical comparison. -# With regards to Subject, each of these evaluate to True: +With regards to Subject, each of these evaluate to True: TESTA = { 'a': 0, 'c': Logic_OR([ 'foo', 'bar' ]), @@ -58,7 +59,7 @@ TESTA = { 'g': Logic_OR([ 'baz', Logic_AND([{'b': 1}, {'f': 8}]) ]) } -# With regards to Subject, each of these evaluate to False: +With regards to Subject, each of these evaluate to False: TESTB = { 'a': 'foo', 'c': Logic_OR([ 'bar', 'baz' ]), @@ -68,17 +69,17 @@ TESTB = { 'g': Logic_OR([ 'baz', Logic_AND([{'b':0}, {'f': 8}]) ]) } -# With regards to Subject, this test is invalid +With regards to Subject, this test is invalid TESTC = { 'n': 0 } -# Tests are executed thus: +Tests are executed thus: S = Subject() L = Logician(S) L.execute(TESTA) -''' +""" class Logic_AND(list): pass @@ -88,31 +89,28 @@ class Logic_Operator(dict): pass class Logician(object): - ''' - Given a subject and a dict that describes tests to perform on its members, - this class will evaluate True or False results for each member/test pair. - - See the examples below for test syntax. - ''' + """Given a subject and a dict that describes tests to perform on + its members, this class will evaluate True or False results for + each member/test pair. See the examples below for test syntax. + + """ subject = None def __init__(self, subject): self.subject = subject def get_member(self, member_name): - ''' - Get a member value from the subject object. - Raise exception is subject is None or member not found. - ''' + """Get a member value from the subject object. Raise exception + if subject is None or member not found. + + """ if self.subject is None: raise Exception('Cannot run tests on a subject which is None') return getattr(self.subject, member_name) def test_logic(self, member, logic, operator='eq'): - ''' - Find the type of test to run on member, and perform that test - ''' + """Find the type of test to run on member, and perform that test""" if type(logic) is dict: return self.test_dict(member, logic) @@ -123,17 +121,21 @@ class Logician(object): elif type(logic) is Logic_Operator: return self.test_operator(member, logic) else: - # compare the value, I think using Logic_Operator() here allows completeness in test_operator(), - # but I can't put my finger on why for the minute - return self.test_operator(member, Logic_Operator({operator: logic})) + # compare the value, I think using Logic_Operator() here + # allows completeness in test_operator(), but I can't put + # my finger on why for the minute + return self.test_operator(member, + Logic_Operator({operator: logic})) def test_operator(self, member, value): - ''' - execute the operators contained within value and expect that ALL operators are True - ''' + """Execute the operators contained within value and expect that + ALL operators are True - # something in this method is incomplete, what if operand is a dict, Logic_AND, Logic_OR or another Logic_Operator ? - # do those constructs even make any sense ? + """ + + # something in this method is incomplete, what if operand is + # a dict, Logic_AND, Logic_OR or another Logic_Operator ? + # Do those constructs even make any sense ? result = True for operator, operand in value.items(): @@ -161,9 +163,10 @@ class Logician(object): return result def test_or(self, member, logic): - ''' - member is a value, logic is a set of values, ANY of which can be True - ''' + """Member is a value, logic is a set of values, ANY of which + can be True + + """ result = False for test in logic: result |= self.test_logic(member, test) @@ -171,9 +174,10 @@ class Logician(object): return result def test_and(self, member, logic): - ''' - member is a value, logic is a list of values, ALL of which must be True - ''' + """Member is a value, logic is a list of values, ALL of which + must be True + + """ result = True for test in logic: result &= self.test_logic(member, test) @@ -181,9 +185,10 @@ class Logician(object): return result def test_dict(self, member, logic): - ''' - member is a value, logic is a dict of other members to compare to. All other member tests must be True - ''' + """Member is a value, logic is a dict of other members to + compare to. All other member tests must be True + + """ result = True for other_member, test in logic.items(): result &= self.test_logic(self.get_member(other_member), test) @@ -191,11 +196,11 @@ class Logician(object): return result def execute(self, test): - ''' - subject is an object, - test is a dict of {member: test} pairs to perform on subject's members. - each key in test is a member of subject. - ''' + """Subject is an object, test is a dict of {member: test} pairs + to perform on subject's members. Wach key in test is a member + of subject. + + """ for member_name, logic in test.items(): result = self.test_logic(self.get_member(member_name), logic) |