₭ ₭abaret - Digital Studio Framework

Source code for kabaret.naming.path

'''
    Copyright (c) Supamonks Studio and individual contributors.
    All rights reserved.

    This file is part of kabaret, a python Digital Creation Framework.

    Kabaret is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer.
        
    Redistributions in binary form must reproduce the above copyright 
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
    
    Kabaret is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    
    You should have received a copy of the GNU Lesser General Public License
    along with kabaret.  If not, see <http://www.gnu.org/licenses/>

--
    The kabaret.naming.path module.
    
    Defines the PathItem, FileOrFolder and WildPath classes.
    
'''



import os
import stat

from . fields import Field, FieldValueError, FieldConfigError


[docs]class PathItem(object): class PathValueError(ValueError): pass class PathValidationError(PathValueError): pass class PathConfigError(PathValueError): def __init__(self, path_item, config, messages): super(PathItem.PathConfigError, self).__init__( 'Unable to get %r under %r (%s). Error(s): \n '%( config, path_item.path(), path_item.__class__.__name__ )+' \n'.join(messages) ) class PathChildError(PathValueError): def __init__(self, path_item, value, errors): super(PathItem.PathChildError, self).__init__( 'Unable to get %r under %r (%s). Error(s): \n '%( value, path_item.path(), path_item.__class__.__name__ )+' \n'.join([ err.message for err in errors ]) ) SEP = '/' NAME = Field CHILD_CLASSES = () @classmethod def from_name(cls, name): pi = cls() pi._name_field.set_value(name) return pi def __init__(self): super(PathItem, self).__init__() self._parent = None self._name_field = self.NAME(None) def _set_parent(self, parent): self._parent = parent def _get_depth(self): if self._parent is None: return 0 return self._parent._get_depth() + 1 def pformat(self, indent=None): if indent is None: indent = self._get_depth() ret = '' if self._parent is not None: ret = self._parent.pformat(indent-1) slash = indent and '/' or '' return ( ret +(' '*indent) +slash +'%s (%s):\n%s\n'%( self._name_field.value(), self.__class__.__name__, self._name_field.pformat(indent+2) ) ) def __div__(self, str_path): try: name, remaining_path = str_path.split(self.SEP, 1) except ValueError: return self._get_child_from_name(str_path, validated=True) else: return self._get_child_from_name(name, validated=False) / remaining_path def _get_child_from_name(self, name, validated=True, allow_wild=True): child = None errs = [] for child_class in self.CHILD_CLASSES: try: child = child_class.from_name(name) except FieldValueError, err: # print '###############', err # import traceback # traceback.print_exc() errs.append(err) child = None continue child._set_parent(self) # must set parent before validating if validated: try: child.validated() except PathItem.PathValueError, err: # print '###############', err # import traceback # traceback.print_exc() errs.append(err) child = None continue break if child is None: err = PathItem.PathChildError(self, name, errs) if not allow_wild: raise err # Build a WildItem and set why child = WildItem.from_name(name) child.set_why(err) child._set_parent(self) return child def to(self, **new_config): return self.to_config(new_config) def to_config(self, new_config): config = self.config() for key, new_value in new_config.items(): if new_value is None: del config[key] else: config[key] = new_value return self(**config) def __call__(self, **config): debug = config.pop('debug', False) item = None next = self.root() consumed_keys = set() # The root is not re-created so we must ensure # this config points to it and mark its keys as # consumed: root_config = next.config() for k, v in root_config.items(): if config.get(k) not in (None, v): raise self.PathConfigError( next, config, [ 'Config keys %r does not match root config: %r != %r. Cannot create this config.'%( k, config[k], v ) ] ) consumed_keys.add(k) key_set = set() while next is not None: item = next consumed_keys.update(key_set) if debug: print '#######', item.path() key_set, next = item._get_child_from_config(config, debug=debug) remaining_keys = set(config.keys()).difference(consumed_keys) if debug: print '###-- consumed keys:', consumed_keys print '###-- config keys were:', config.keys() print '###-- remaing keys:', remaining_keys if remaining_keys: raise self.PathConfigError( item, config, [ 'Config keys %r were not consumed from %r to build %r from %r (%s)'%( list(remaining_keys), config.keys(), item.path(), self.path(), self.__class__.__name__ ) ] ) return item.validated() def _get_child_from_config(self, config, debug=False): child = None consumed_keys = set() child_depth = self._get_depth()+1 for child_class in list(self.CHILD_CLASSES)+[WildItem]: if debug: print '#####----- child from config: trying %r'%(child_class,) try: child = child_class() consumed_keys = child._name_field.set_config(config, allowed_index=child_depth) except FieldConfigError: if debug: import traceback traceback.print_exc() child = None consumed_keys = set() continue child._set_parent(self) if debug: print '##### Got Child!', child, child.path(), 'used keys:', consumed_keys print '##### try validation' try: child.validate(config) except self.PathValidationError: if debug: import traceback traceback.print_exc() child = None consumed_keys = set() continue if debug: print '##### Validation done for', child, child.path() break return consumed_keys, child def validated(self): ''' Checks that this PathItem is a valid leaf. Returns self or raises a PathItem.PathValidationError ''' self.validate(self.config()) return self def validate(self, leaf_config): ''' Validate the config of this PathItem when it is an ancestor of a PathItem having 'leaf_config' as config. The default implementation checks that no key in this path item's config clashes with leaf_config. Subclasses can override here to add special validation. It should always call the base class' implementation and must raises a PathItem.PathValidationError when something does not validate. ''' if self._parent: self._parent.validate(leaf_config) config = self._name_field.config() for key, local_value in config.items(): try: leaf_value = leaf_config[key] except KeyError: continue #print '???', self, key, local_value, leaf_value if leaf_value != local_value: raise PathItem.PathValidationError( 'Invalid name %r for %r (%s): %s should be %r and not %r'%( self._name_field.value(), self.__class__.__name__, self.path(), key, leaf_value, local_value ) ) def parent(self): return self._parent @property def name_field(self): return self._name_field def value(self): return self._name_field.value() def root(self): if self._parent is None: return self return self._parent.root() def path(self): l = self._parent is not None and [self._parent.path()] or [] l.append(str(self._name_field.value())) return self.SEP.join(l) def is_wild(self): return isinstance(self, WildItem) def raise_wild(self): if self.is_wild(): raise self.why() def config(self, relative_to=None): config = {} if self._parent is not None and relative_to != self: config = self._parent.config(relative_to) config.update(self._name_field.config()) return config @classmethod def get_keys_for(cls, path_item_type_name, up_keys=[]): ''' Returns a (somewhat ordered) list of keys needed to go from this item type to the first child item type named 'path_item_type_name' ''' here_keys = up_keys+cls.NAME.get_config_keys() if cls.__name__ == path_item_type_name: # remove duplicates, preserving order: seen = set() seen_add = seen.add return [ x for x in here_keys if x not in seen and not seen_add(x)] for child_class in cls.CHILD_CLASSES: if child_class == cls: continue keys = child_class.get_keys_for(path_item_type_name, up_keys=here_keys) if keys is not None: return keys return None def mtime(self): try: return os.stat(self.path())[stat.ST_MTIME] except: return None def exists(self): return os.path.exists(self.path()) def isfile(self): return os.path.isfile(self.path()) def isdir(self): return os.path.isdir(self.path())
class AnyName(Field): KEY = 'name'
[docs]class FileOrFolder(PathItem): SEP = '/' NAME = AnyName CHILD_CLASSES = () def _set_parent(self, parent): super(FileOrFolder, self)._set_parent(parent) self._name_field.key_override = self._name_field.make_indexed_key( self._name_field.key, self._get_depth() )
FileOrFolder.CHILD_CLASSES = (FileOrFolder,) class WildField(Field): KEY = 'wild'
[docs]class WildItem(PathItem): SEP = '/' NAME = WildField CHILD_CLASSES = () def __init__(self): super(WildItem, self).__init__() self._why = None def set_why(self, exception): self._why = exception def why(self): if self._why is not None: return self._why if self._parent is not None: return self._parent.why() return Exception('Sorry, Wild path with no known reason.') def _set_parent(self, parent): super(WildItem, self)._set_parent(parent) self._name_field.key_override = self._name_field.make_indexed_key( self._name_field.key, self._get_depth() )
WildItem.CHILD_CLASSES = (WildItem,)