Source

nimrud / conex / src / conex / types / factory.py

from content import BaseContent
from plone.supermodel import loadString
from plone.supermodel import xmlSchema
from zope.schema import getFieldNamesInOrder
from zope.schema.fieldproperty import FieldProperty
from interfaces import IConcaveTypesFactory
from interfaces import IConcaveContent
from zope.interface import implements
from zope.interface import classImplements
from zope.app.folder import Folder
from zope.dottedname.resolve import resolve
#from zope.app.security.metaconfigure import require
from zope.security.protectclass import protectName
from zope.security.protectclass import protectSetAttribute

import os

class ConcaveTypesFactory(object):
    """A pretty bog-simple implementation of the IConcaveTypesFactory.
    By default, currently implements types with a base-class of Folder, 
    creating all schema elements as FieldProperties, and asserting that
    the new class implements the schema interface."""

    implements(IConcaveTypesFactory)
    known_classes = {}
    __default_bases = (BaseContent,)

    def register_type(self,
                      schema_dotted_name, 
                      implementation_dotted_name = None):
        """Registers a new type with the factory for later creation"""
        #First, well need a schema for the type
        schema = self._get_schema(schema_dotted_name)
        #If there's an implementation provided, just add it to known_classes
        #otherwise, we'll need to make the type
        if implementation_dotted_name:
            if isinstance(implementation_dotted_name, str):
                implementation = self._resolve_dotted_name(implementation_dotted_name)
                class_name = implementation_dotted_name.split(".")[-1]
            else:
                implementation = implementation_dotted_name
                class_name = implementation.__name__
            self.known_classes[unicode(class_name)] = implementation
        else:
            self._make_type(schema)

    def create_object(self, type_name, *args, **kwargs):
        """Takes a type name (which are in a flat namespace) and returns an
        object of that type. Args and kwargs are passed to the __init__ of th
        new object"""
        return self.known_classes[type_name](*args, **kwargs)

    def _resolve_dotted_name(self, dotted_name):
        """Takes a dotted_name as a string and will return either a python
        module/class/name or a string of an XML file. Raises ImportError 
        if the dotted name simply does not exist
        """
        #Maybe it's a python object
        try:
            new_name = resolve(dotted_name)
        except ImportError:
            #One of two things happened: this is a totally invalid name or
            #this name points to an XML file.
            #If you come up with something really invalid here (like a name
            #with spaces), we get a dumb exception, so we've got to catch
            #those and turn them into ImportErrors
            try:
                parent_package_name = ".".join(dotted_name.split(".")[:-1])
                parent_package = resolve(parent_package_name)
                parent_path = parent_package.__path__[0]
                filename = ".".join([dotted_name.split(".")[-1], "xml"])
                file_path = os.path.join(parent_path, filename)
                try:
                    schema_file = open(file_path)
                    new_name = schema_file.read()
                finally:
                    schema_file.close()
            except ValueError, e:
                raise ImportError, e
        return new_name

    def _get_schema(self, dotted_name):
        inschema = self._resolve_dotted_name(dotted_name)
        if isinstance(inschema, str):
            #This means we got back an xml file
            schema = loadString(inschema).schema
            #ensure the schema class has a name
            #TODO: This is clearly an insane hack and needs to be replaced
            #fixes http://paste.plone.org/27812
            for fieldname in schema.names():
                schema[fieldname].__name__ = str(fieldname)
            schema.__name__ = dotted_name.split(".")[-1]
        else:
            #we got back a python schema
            schema = inschema
        #add the schema to the known classes, as it's now known by the factory
        #TODO: Should this actually happen in register?
        self.known_classes[unicode(schema.__name__)] = schema
        return schema
    
    def _make_type(self, in_schema, bases=None):
        """Takes a schema (which is a zope interface) and returns a python type
        that implements that schema."""
        if bases is None:
            bases = self.__default_bases
        #Create a dict of attrs for our object
        properties_dict = {}
        for field in getFieldNamesInOrder(in_schema):
            properties_dict[field] = FieldProperty(in_schema[field])
        properties_dict["schemas"] = [in_schema,]
        #If we're making the new class, we'll name it by taking the
        #schema's name, chopping off the I
        new_name = str(in_schema.__name__[1:])
        #The magic moment where we create a new class
        NewClass = type(new_name, bases, properties_dict)
        #The new class needs to be added to the factory module's globals
        #so that it can be persisted via pickling (i.e., by the ZODB)
        globals()[new_name]  = NewClass
        #claim that it implements the class we just caused it to implement
        #note that this doesn't affect the class itself, but instead only
        #objects it creates
        classImplements(NewClass, in_schema)
        classImplements(NewClass, IConcaveContent)

        #XXX: HIDEOUS HACK
        ### Protects the class with zope.pubic to see how things go
        PUBLIC_PERMISSION = "zope.Public"
        for name in in_schema.names(all=True):
            protectName(NewClass, name, PUBLIC_PERMISSION)
            protectSetAttribute(NewClass, name, PUBLIC_PERMISSION)
        #TODO: Should this actually happen in register?
        self.known_classes[unicode(new_name)] = NewClass
        
        return NewClass