nimrud / conex / src / conex / types /

from content import BaseContent
from plone.supermodel import load_string
from plone.supermodel import xml_schema
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 import Folder
from zope.dottedname.resolve import resolve

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."""

    known_classes = {}
    __default_bases = (BaseContent,)

    def register_type(self,
                      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]
                implementation = implementation_dotted_name
                class_name = implementation.__name__
            self.known_classes[unicode(class_name)] = implementation

    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
            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
                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)
                    schema_file = open(file_path)
                    new_name =
            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 = load_string(inschema).schema
            #ensure the schema class has a name
            schema.__name__ = dotted_name.split(".")[-1]
            #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)
        #TODO: Should this actually happen in register?
        self.known_classes[unicode(new_name)] = NewClass
        return NewClass