Beispiel #1
0
    def __new__(mcs, name, bases, attrs):
        flattened_bases = mcs._get_bases(bases)
        super_new = super(TopLevelDocumentMetaclass, mcs).__new__

        # Set default _meta data if base class, otherwise get user defined meta
        if attrs.get('my_metaclass') == TopLevelDocumentMetaclass:
            # defaults
            attrs['_meta'] = {
                'abstract': True,
                'max_documents': None,
                'max_size': None,
                'ordering': [],  # default ordering applied at runtime
                'indexes': [],  # indexes to be ensured at runtime
                'id_field': None,
                'index_background': False,
                'index_drop_dups': False,
                'index_opts': None,
                'delete_rules': None,

                # allow_inheritance can be True, False, and None. True means
                # "allow inheritance", False means "don't allow inheritance",
                # None means "do whatever your parent does, or don't allow
                # inheritance if you're a top-level class".
                'allow_inheritance': None,
            }
            attrs['_is_base_cls'] = True
            attrs['_meta'].update(attrs.get('meta', {}))
        else:
            attrs['_meta'] = attrs.get('meta', {})
            # Explicitly set abstract to false unless set
            attrs['_meta']['abstract'] = attrs['_meta'].get('abstract', False)
            attrs['_is_base_cls'] = False

        # Set flag marking as document class - as opposed to an object mixin
        attrs['_is_document'] = True

        # Ensure queryset_class is inherited
        if 'objects' in attrs:
            manager = attrs['objects']
            if hasattr(manager, 'queryset_class'):
                attrs['_meta']['queryset_class'] = manager.queryset_class

        # Clean up top level meta
        if 'meta' in attrs:
            del attrs['meta']

        # Find the parent document class
        parent_doc_cls = [
            b for b in flattened_bases
            if b.__class__ == TopLevelDocumentMetaclass
        ]
        parent_doc_cls = None if not parent_doc_cls else parent_doc_cls[0]

        # Prevent classes setting collection different to their parents
        # If parent wasn't an abstract class
        if (parent_doc_cls and 'collection' in attrs.get('_meta', {})
                and not parent_doc_cls._meta.get('abstract', True)):
            msg = 'Trying to set a collection on a subclass (%s)' % name
            warnings.warn(msg, SyntaxWarning)
            del attrs['_meta']['collection']

        # Ensure abstract documents have abstract bases
        if attrs.get('_is_base_cls') or attrs['_meta'].get('abstract'):
            if (parent_doc_cls
                    and not parent_doc_cls._meta.get('abstract', False)):
                msg = 'Abstract document cannot have non-abstract base'
                raise ValueError(msg)
            return super_new(mcs, name, bases, attrs)

        # Merge base class metas.
        # Uses a special MetaDict that handles various merging rules
        meta = MetaDict()
        for base in flattened_bases[::-1]:
            # Add any mixin metadata from plain objects
            if hasattr(base, 'meta'):
                meta.merge(base.meta)
            elif hasattr(base, '_meta'):
                meta.merge(base._meta)

            # Set collection in the meta if its callable
            if (getattr(base, '_is_document', False)
                    and not base._meta.get('abstract')):
                collection = meta.get('collection', None)
                if callable(collection):
                    meta['collection'] = collection(base)

        meta.merge(attrs.get('_meta', {}))  # Top level meta

        # Only simple classes (i.e. direct subclasses of Document) may set
        # allow_inheritance to False. If the base Document allows inheritance,
        # none of its subclasses can override allow_inheritance to False.
        simple_class = all([
            b._meta.get('abstract') for b in flattened_bases
            if hasattr(b, '_meta')
        ])
        if (not simple_class and meta['allow_inheritance'] is False
                and not meta['abstract']):
            raise ValueError('Only direct subclasses of Document may set '
                             '"allow_inheritance" to False')

        # Set default collection name
        if 'collection' not in meta:
            meta['collection'] = ''.join('_%s' % c if c.isupper() else c
                                         for c in name).strip('_').lower()
        attrs['_meta'] = meta

        # Call super and get the new class
        new_class = super_new(mcs, name, bases, attrs)

        meta = new_class._meta

        # Set index specifications
        meta['index_specs'] = new_class._build_index_specs(meta['indexes'])

        # If collection is a callable - call it and set the value
        collection = meta.get('collection')
        if callable(collection):
            new_class._meta['collection'] = collection(new_class)

        # Provide a default queryset unless exists or one has been set
        if 'objects' not in dir(new_class):
            new_class.objects = QuerySetManager()

        # Validate the fields and set primary key if needed
        for field_name, field in iteritems(new_class._fields):
            if field.primary_key:
                # Ensure only one primary key is set
                current_pk = new_class._meta.get('id_field')
                if current_pk and current_pk != field_name:
                    raise ValueError('Cannot override primary key field')

                # Set primary key
                if not current_pk:
                    new_class._meta['id_field'] = field_name
                    new_class.id = field

        # Set primary key if not defined by the document
        new_class._auto_id_field = getattr(parent_doc_cls, '_auto_id_field',
                                           False)
        if not new_class._meta.get('id_field'):
            # After 0.10, find not existing names, instead of overwriting
            id_name, id_db_name = mcs.get_auto_id_names(new_class)
            new_class._auto_id_field = True
            new_class._meta['id_field'] = id_name
            new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
            new_class._fields[id_name].name = id_name
            new_class.id = new_class._fields[id_name]
            new_class._db_field_map[id_name] = id_db_name
            new_class._reverse_db_field_map[id_db_name] = id_name
            # Prepend id field to _fields_ordered
            new_class._fields_ordered = (id_name, ) + new_class._fields_ordered

        # Merge in exceptions with parent hierarchy
        exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
        module = attrs.get('__module__')
        for exc in exceptions_to_merge:
            name = exc.__name__
            parents = tuple(
                getattr(base, name)
                for base in flattened_bases if hasattr(base, name)) or (exc, )
            # Create new exception and set to new_class
            exception = type(name, parents, {'__module__': module})
            setattr(new_class, name, exception)

        return new_class
Beispiel #2
0
    def __new__(mcs, name, bases, attrs):
        flattened_bases = mcs._get_bases(bases)
        super_new = super(TopLevelDocumentMetaclass, mcs).__new__

        # Set default _meta data if base class, otherwise get user defined meta
        if attrs.get("my_metaclass") == TopLevelDocumentMetaclass:
            # defaults
            attrs["_meta"] = {
                "abstract": True,
                "max_documents": None,
                "max_size": None,
                "ordering": [],  # default ordering applied at runtime
                "indexes": [],  # indexes to be ensured at runtime
                "id_field": None,
                "index_background": False,
                "index_opts": None,
                "delete_rules": None,
                # allow_inheritance can be True, False, and None. True means
                # "allow inheritance", False means "don't allow inheritance",
                # None means "do whatever your parent does, or don't allow
                # inheritance if you're a top-level class".
                "allow_inheritance": None,
            }
            attrs["_is_base_cls"] = True
            attrs["_meta"].update(attrs.get("meta", {}))
        else:
            attrs["_meta"] = attrs.get("meta", {})
            # Explicitly set abstract to false unless set
            attrs["_meta"]["abstract"] = attrs["_meta"].get("abstract", False)
            attrs["_is_base_cls"] = False

        # Set flag marking as document class - as opposed to an object mixin
        attrs["_is_document"] = True

        # Ensure queryset_class is inherited
        if "objects" in attrs:
            manager = attrs["objects"]
            if hasattr(manager, "queryset_class"):
                attrs["_meta"]["queryset_class"] = manager.queryset_class

        # Clean up top level meta
        if "meta" in attrs:
            del attrs["meta"]

        # Find the parent document class
        parent_doc_cls = [
            b for b in flattened_bases if b.__class__ == TopLevelDocumentMetaclass
        ]
        parent_doc_cls = None if not parent_doc_cls else parent_doc_cls[0]

        # Prevent classes setting collection different to their parents
        # If parent wasn't an abstract class
        if (
            parent_doc_cls
            and "collection" in attrs.get("_meta", {})
            and not parent_doc_cls._meta.get("abstract", True)
        ):
            msg = "Trying to set a collection on a subclass (%s)" % name
            warnings.warn(msg, SyntaxWarning)
            del attrs["_meta"]["collection"]

        # Ensure abstract documents have abstract bases
        if attrs.get("_is_base_cls") or attrs["_meta"].get("abstract"):
            if parent_doc_cls and not parent_doc_cls._meta.get("abstract", False):
                msg = "Abstract document cannot have non-abstract base"
                raise ValueError(msg)
            return super_new(mcs, name, bases, attrs)

        # Merge base class metas.
        # Uses a special MetaDict that handles various merging rules
        meta = MetaDict()
        for base in flattened_bases[::-1]:
            # Add any mixin metadata from plain objects
            if hasattr(base, "meta"):
                meta.merge(base.meta)
            elif hasattr(base, "_meta"):
                meta.merge(base._meta)

            # Set collection in the meta if its callable
            if getattr(base, "_is_document", False) and not base._meta.get("abstract"):
                collection = meta.get("collection", None)
                if callable(collection):
                    meta["collection"] = collection(base)

        meta.merge(attrs.get("_meta", {}))  # Top level meta

        # Only simple classes (i.e. direct subclasses of Document) may set
        # allow_inheritance to False. If the base Document allows inheritance,
        # none of its subclasses can override allow_inheritance to False.
        simple_class = all(
            [b._meta.get("abstract") for b in flattened_bases if hasattr(b, "_meta")]
        )
        if (
            not simple_class
            and meta["allow_inheritance"] is False
            and not meta["abstract"]
        ):
            raise ValueError(
                "Only direct subclasses of Document may set "
                '"allow_inheritance" to False'
            )

        # Set default collection name
        if "collection" not in meta:
            meta["collection"] = (
                "".join("_%s" % c if c.isupper() else c for c in name)
                .strip("_")
                .lower()
            )
        attrs["_meta"] = meta

        # Call super and get the new class
        new_class = super_new(mcs, name, bases, attrs)

        meta = new_class._meta

        # Set index specifications
        meta["index_specs"] = new_class._build_index_specs(meta["indexes"])

        # If collection is a callable - call it and set the value
        collection = meta.get("collection")
        if callable(collection):
            new_class._meta["collection"] = collection(new_class)

        # Provide a default queryset unless exists or one has been set
        if "objects" not in dir(new_class):
            new_class.objects = QuerySetManager()

        # Validate the fields and set primary key if needed
        for field_name, field in iteritems(new_class._fields):
            if field.primary_key:
                # Ensure only one primary key is set
                current_pk = new_class._meta.get("id_field")
                if current_pk and current_pk != field_name:
                    raise ValueError("Cannot override primary key field")

                # Set primary key
                if not current_pk:
                    new_class._meta["id_field"] = field_name
                    new_class.id = field

        # If the document doesn't explicitly define a primary key field, create
        # one. Make it an ObjectIdField and give it a non-clashing name ("id"
        # by default, but can be different if that one's taken).
        if not new_class._meta.get("id_field"):
            id_name, id_db_name = mcs.get_auto_id_names(new_class)
            new_class._meta["id_field"] = id_name
            new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
            new_class._fields[id_name].name = id_name
            new_class.id = new_class._fields[id_name]
            new_class._db_field_map[id_name] = id_db_name
            new_class._reverse_db_field_map[id_db_name] = id_name

            # Prepend the ID field to _fields_ordered (so that it's *always*
            # the first field).
            new_class._fields_ordered = (id_name,) + new_class._fields_ordered

        # Merge in exceptions with parent hierarchy.
        exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
        module = attrs.get("__module__")
        for exc in exceptions_to_merge:
            name = exc.__name__
            parents = tuple(
                getattr(base, name) for base in flattened_bases if hasattr(base, name)
            ) or (exc,)

            # Create a new exception and set it as an attribute on the new
            # class.
            exception = type(name, parents, {"__module__": module})
            setattr(new_class, name, exception)

        return new_class