Exemple #1
0
class TypedObject(SharedMemoryModel):
    """
    Abstract Django model.

    This is the basis for a typed object. It also contains all the
    mechanics for managing connected attributes.

    The TypedObject has the following properties:
      key - main name
      name - alias for key
      typeclass_path - the path to the decorating typeclass
      typeclass - auto-linked typeclass
      date_created - time stamp of object creation
      permissions - perm strings
      dbref - #id of object
      db - persistent attribute storage
      ndb - non-persistent attribute storage

    """

    #
    # TypedObject Database Model setup
    #
    #
    # These databse fields are all accessed and set using their corresponding
    # properties, named same as the field, but without the db_* prefix
    # (no separate save() call is needed)

    # Main identifier of the object, for searching. Is accessed with self.key
    # or self.name
    db_key = models.CharField('key', max_length=255, db_index=True)
    # This is the python path to the type class this object is tied to. The
    # typeclass is what defines what kind of Object this is)
    db_typeclass_path = models.CharField(
        'typeclass',
        max_length=255,
        null=True,
        help_text=
        "this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass."
    )
    # Creation date. This is not changed once the object is created.
    db_date_created = models.DateTimeField('creation date',
                                           editable=False,
                                           auto_now_add=True)
    # Lock storage
    db_lock_storage = models.TextField(
        'locks',
        blank=True,
        help_text=
        "locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted."
    )
    # many2many relationships
    db_attributes = models.ManyToManyField(
        Attribute,
        null=True,
        help_text=
        'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).'
    )
    db_tags = models.ManyToManyField(
        Tag,
        null=True,
        help_text=
        'tags on this object. Tags are simple string markers to identify, group and alias objects.'
    )

    # Database manager
    objects = managers.TypedObjectManager()

    # quick on-object typeclass cache for speed
    _cached_typeclass = None

    # typeclass mechanism

    def __init__(self, *args, **kwargs):
        """
        The `__init__` method of typeclasses is the core operational
        code of the typeclass system, where it dynamically re-applies
        a class based on the db_typeclass_path database field rather
        than use the one in the model.

        Args:
            Passed through to parent.

        Kwargs:
            Passed through to parent.

        Notes:
            The loading mechanism will attempt the following steps:

            1. Attempt to load typeclass given on command line
            2. Attempt to load typeclass stored in db_typeclass_path
            3. Attempt to load `__settingsclasspath__`, which is by the
               default classes defined to be the respective user-set
               base typeclass settings, like `BASE_OBJECT_TYPECLASS`.
            4. Attempt to load `__defaultclasspath__`, which is the
               base classes in the library, like DefaultObject etc.
            5. If everything else fails, use the database model.

            Normal operation is to load successfully at either step 1
            or 2 depending on how the class was called. Tracebacks
            will be logged for every step the loader must take beyond
            2.

        """
        typeclass_path = kwargs.pop("typeclass", None)
        super(TypedObject, self).__init__(*args, **kwargs)
        if typeclass_path:
            try:
                self.__class__ = class_from_module(
                    typeclass_path, defaultpaths=settings.TYPECLASS_PATHS)
            except Exception:
                log_trace()
                try:
                    self.__class__ = class_from_module(
                        self.__settingsclasspath__)
                except Exception:
                    log_trace()
                    try:
                        self.__class__ = class_from_module(
                            self.__defaultclasspath__)
                    except Exception:
                        log_trace()
                        self.__class__ = self._meta.proxy_for_model or self.__class__
            finally:
                self.db_typclass_path = typeclass_path
        elif self.db_typeclass_path:
            try:
                self.__class__ = class_from_module(self.db_typeclass_path)
            except Exception:
                log_trace()
                try:
                    self.__class__ = class_from_module(
                        self.__defaultclasspath__)
                except Exception:
                    log_trace()
                    self.__dbclass__ = self._meta.proxy_for_model or self.__class__
        else:
            self.db_typeclass_path = "%s.%s" % (self.__module__,
                                                self.__class__.__name__)
        # important to put this at the end since _meta is based on the set __class__
        self.__dbclass__ = self._meta.proxy_for_model or self.__class__

    # initialize all handlers in a lazy fashion
    @lazy_property
    def attributes(self):
        return AttributeHandler(self)

    @lazy_property
    def locks(self):
        return LockHandler(self)

    @lazy_property
    def tags(self):
        return TagHandler(self)

    @lazy_property
    def aliases(self):
        return AliasHandler(self)

    @lazy_property
    def permissions(self):
        return PermissionHandler(self)

    @lazy_property
    def nattributes(self):
        return NAttributeHandler(self)

    class Meta:
        """
        Django setup info.
        """
        abstract = True
        verbose_name = "Evennia Database Object"
        ordering = ['-db_date_created', 'id', 'db_typeclass_path', 'db_key']

    # wrapper
    # Wrapper properties to easily set database fields. These are
    # @property decorators that allows to access these fields using
    # normal python operations (without having to remember to save()
    # etc). So e.g. a property 'attr' has a get/set/del decorator
    # defined that allows the user to do self.attr = value,
    # value = self.attr and del self.attr respectively (where self
    # is the object in question).

    # name property (alias to self.key)
    def __name_get(self):
        return self.key

    def __name_set(self, value):
        self.key = value

    def __name_del(self):
        raise Exception("Cannot delete name")

    name = property(__name_get, __name_set, __name_del)

    #
    #
    # TypedObject main class methods and properties
    #
    #

    def __eq__(self, other):
        return other and hasattr(other, 'dbid') and self.dbid == other.dbid

    def __str__(self):
        return smart_str("%s" % self.db_key)

    def __unicode__(self):
        return u"%s" % self.db_key

    #@property
    def __dbid_get(self):
        """
        Caches and returns the unique id of the object.
        Use this instead of self.id, which is not cached.
        """
        return self.id

    def __dbid_set(self, value):
        raise Exception("dbid cannot be set!")

    def __dbid_del(self):
        raise Exception("dbid cannot be deleted!")

    dbid = property(__dbid_get, __dbid_set, __dbid_del)

    #@property
    def __dbref_get(self):
        """
        Returns the object's dbref on the form #NN.
        """
        return "#%s" % self.id

    def __dbref_set(self):
        raise Exception("dbref cannot be set!")

    def __dbref_del(self):
        raise Exception("dbref cannot be deleted!")

    dbref = property(__dbref_get, __dbref_set, __dbref_del)

    #
    # Object manipulation methods
    #

    def is_typeclass(self, typeclass, exact=True):
        """
        Returns true if this object has this type OR has a typeclass
        which is an subclass of the given typeclass. This operates on
        the actually loaded typeclass (this is important since a
        failing typeclass may instead have its default currently
        loaded) typeclass - can be a class object or the python path
        to such an object to match against.

        typeclass - a class or the full python path to the class
        exact - returns true only
                if the object's type is exactly this typeclass, ignoring
                parents.
        """
        if isinstance(typeclass, basestring):
            typeclass = [typeclass] + [
                "%s.%s" % (prefix, typeclass)
                for prefix in settings.TYPECLASS_PATHS
            ]
        else:
            typeclass = [typeclass.path]

        selfpath = self.path
        if exact:
            # check only exact match
            return selfpath in typeclass
        else:
            # check parent chain
            return any(
                hasattr(cls, "path") and cls.path in typeclass
                for cls in self.__class__.mro())

    def swap_typeclass(self,
                       new_typeclass,
                       clean_attributes=False,
                       run_start_hooks=True,
                       no_default=True):
        """
        This performs an in-situ swap of the typeclass. This means
        that in-game, this object will suddenly be something else.
        Player will not be affected. To 'move' a player to a different
        object entirely (while retaining this object's type), use
        self.player.swap_object().

        Note that this might be an error prone operation if the
        old/new typeclass was heavily customized - your code
        might expect one and not the other, so be careful to
        bug test your code if using this feature! Often its easiest
        to create a new object and just swap the player over to
        that one instead.

        Arguments:
        new_typeclass (path/classobj) - type to switch to
        clean_attributes (bool/list) - will delete all attributes
                           stored on this object (but not any
                           of the database fields such as name or
                           location). You can't get attributes back,
                           but this is often the safest bet to make
                           sure nothing in the new typeclass clashes
                           with the old one. If you supply a list,
                           only those named attributes will be cleared.
        run_start_hooks - trigger the start hooks of the object, as if
                          it was created for the first time.
        no_default - if this is active, the swapper will not allow for
                     swapping to a default typeclass in case the given
                     one fails for some reason. Instead the old one
                     will be preserved.
        Returns:
          boolean True/False depending on if the swap worked or not.

        """

        if not callable(new_typeclass):
            # this is an actual class object - build the path
            new_typeclass = class_from_module(
                new_typeclass, defaultpaths=settings.TYPECLASS_PATHS)

        # if we get to this point, the class is ok.

        if inherits_from(self, "evennia.scripts.models.ScriptDB"):
            if self.interval > 0:
                raise RuntimeError("Cannot use swap_typeclass on time-dependent " \
                                   "Script '%s'.\nStop and start a new Script of the " \
                                   "right type instead." % self.key)

        self.typeclass_path = new_typeclass.path
        self.__class__ = new_typeclass

        if clean_attributes:
            # Clean out old attributes
            if is_iter(clean_attributes):
                for attr in clean_attributes:
                    self.attributes.remove(attr)
                for nattr in clean_attributes:
                    if hasattr(self.ndb, nattr):
                        self.nattributes.remove(nattr)
            else:
                #print "deleting attrs ..."
                self.attributes.clear()
                self.nattributes.clear()

        if run_start_hooks:
            # fake this call to mimic the first save
            self.at_first_save()

    #
    # Lock / permission methods
    #

    def access(self,
               accessing_obj,
               access_type='read',
               default=False,
               **kwargs):
        """
        Determines if another object has permission to access.
        accessing_obj - object trying to access this one
        access_type - type of access sought
        default - what to return if no lock of access_type was found
        **kwargs - this is ignored, but is there to make the api consistent with the
                   object-typeclass method access, which use it to feed to its hook methods.
        """
        return self.locks.check(accessing_obj,
                                access_type=access_type,
                                default=default)

    def check_permstring(self, permstring):
        """
        This explicitly checks if we hold particular permission without
        involving any locks. It does -not- trigger the at_access hook.
        """
        if hasattr(self, "player"):
            if self.player and self.player.is_superuser:
                return True
        else:
            if self.is_superuser:
                return True

        if not permstring:
            return False
        perm = permstring.lower()
        perms = [p.lower() for p in self.permissions.all()]
        if perm in perms:
            # simplest case - we have a direct match
            return True
        if perm in _PERMISSION_HIERARCHY:
            # check if we have a higher hierarchy position
            ppos = _PERMISSION_HIERARCHY.index(perm)
            return any(True for hpos, hperm in enumerate(_PERMISSION_HIERARCHY)
                       if hperm in perms and hpos > ppos)
        return False

    #
    # Deletion methods
    #

    def _deleted(self, *args, **kwargs):
        "Scrambling method for already deleted objects"
        raise ObjectDoesNotExist("This object was already deleted!")

    def delete(self):
        "Cleaning up handlers on the typeclass level"
        global TICKER_HANDLER
        if not TICKER_HANDLER:
            from evennia.scripts.tickerhandler import TICKER_HANDLER
        TICKER_HANDLER.remove(
            self)  # removes objects' all ticker subscriptions
        self.permissions.clear()
        self.attributes.clear()
        self.aliases.clear()
        if hasattr(self, "nicks"):
            self.nicks.clear()

        # scrambling properties
        self.delete = self._deleted
        super(TypedObject, self).delete()

    #
    # Memory management
    #

    def flush_from_cache(self):
        """
        Flush this object instance from cache, forcing an object reload.
        Note that this will kill all temporary attributes on this object
         since it will be recreated as a new Typeclass instance.
        """
        self.__class__.flush_cached_instance(self)

    #
    # Attribute storage
    #

    #@property db
    def __db_get(self):
        """
        Attribute handler wrapper. Allows for the syntax
           obj.db.attrname = value
             and
           value = obj.db.attrname
             and
           del obj.db.attrname
             and
           all_attr = obj.db.all() (unless there is an attribute
                      named 'all', in which case that will be returned instead).
        """
        try:
            return self._db_holder
        except AttributeError:
            self._db_holder = DbHolder(self, 'attributes')
            return self._db_holder

    #@db.setter
    def __db_set(self, value):
        "Stop accidentally replacing the db object"
        string = "Cannot assign directly to db object! "
        string += "Use db.attr=value instead."
        raise Exception(string)

    #@db.deleter
    def __db_del(self):
        "Stop accidental deletion."
        raise Exception("Cannot delete the db object!")

    db = property(__db_get, __db_set, __db_del)

    #
    # Non-persistent (ndb) storage
    #

    #@property ndb
    def __ndb_get(self):
        """
        A non-attr_obj store (ndb: NonDataBase). Everything stored
        to this is guaranteed to be cleared when a server is shutdown.
        Syntax is same as for the _get_db_holder() method and
        property, e.g. obj.ndb.attr = value etc.
        """
        try:
            return self._ndb_holder
        except AttributeError:
            self._ndb_holder = DbHolder(self,
                                        "nattrhandler",
                                        manager_name='nattributes')
            return self._ndb_holder

    #@db.setter
    def __ndb_set(self, value):
        "Stop accidentally replacing the ndb object"
        string = "Cannot assign directly to ndb object! "
        string += "Use ndb.attr=value instead."
        raise Exception(string)

    #@db.deleter
    def __ndb_del(self):
        "Stop accidental deletion."
        raise Exception("Cannot delete the ndb object!")

    ndb = property(__ndb_get, __ndb_set, __ndb_del)
Exemple #2
0
class TypedObject(SharedMemoryModel):
    """
    Abstract Django model.

    This is the basis for a typed object. It also contains all the
    mechanics for managing connected attributes.

    The TypedObject has the following properties:
      key - main name
      name - alias for key
      typeclass_path - the path to the decorating typeclass
      typeclass - auto-linked typeclass
      date_created - time stamp of object creation
      permissions - perm strings
      dbref - #id of object
      db - persistent attribute storage
      ndb - non-persistent attribute storage

    """

    #
    # TypedObject Database Model setup
    #
    #
    # These databse fields are all accessed and set using their corresponding
    # properties, named same as the field, but without the db_* prefix
    # (no separate save() call is needed)

    # Main identifier of the object, for searching. Is accessed with self.key
    # or self.name
    db_key = models.CharField('key', max_length=255, db_index=True)
    # This is the python path to the type class this object is tied to. The
    # typeclass is what defines what kind of Object this is)
    db_typeclass_path = models.CharField(
        'typeclass',
        max_length=255,
        null=True,
        help_text=
        "this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass."
    )
    # Creation date. This is not changed once the object is created.
    db_date_created = models.DateTimeField('creation date',
                                           editable=False,
                                           auto_now_add=True)
    # Lock storage
    db_lock_storage = models.TextField(
        'locks',
        blank=True,
        help_text=
        "locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted."
    )
    # many2many relationships
    db_attributes = models.ManyToManyField(
        Attribute,
        null=True,
        help_text=
        'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).'
    )
    db_tags = models.ManyToManyField(
        Tag,
        null=True,
        help_text=
        'tags on this object. Tags are simple string markers to identify, group and alias objects.'
    )

    # Database manager
    objects = managers.TypedObjectManager()

    # quick on-object typeclass cache for speed
    _cached_typeclass = None

    # typeclass mechanism

    def __init__(self, *args, **kwargs):
        """
        The `__init__` method of typeclasses is the core operational
        code of the typeclass system, where it dynamically re-applies
        a class based on the db_typeclass_path database field rather
        than use the one in the model.

        Args:
            Passed through to parent.

        Kwargs:
            Passed through to parent.

        Notes:
            The loading mechanism will attempt the following steps:

            1. Attempt to load typeclass given on command line
            2. Attempt to load typeclass stored in db_typeclass_path
            3. Attempt to load `__settingsclasspath__`, which is by the
               default classes defined to be the respective user-set
               base typeclass settings, like `BASE_OBJECT_TYPECLASS`.
            4. Attempt to load `__defaultclasspath__`, which is the
               base classes in the library, like DefaultObject etc.
            5. If everything else fails, use the database model.

            Normal operation is to load successfully at either step 1
            or 2 depending on how the class was called. Tracebacks
            will be logged for every step the loader must take beyond
            2.

        """
        typeclass_path = kwargs.pop("typeclass", None)
        super(TypedObject, self).__init__(*args, **kwargs)
        if typeclass_path:
            try:
                self.__class__ = class_from_module(
                    typeclass_path, defaultpaths=settings.TYPECLASS_PATHS)
            except Exception:
                log_trace()
                try:
                    self.__class__ = class_from_module(
                        self.__settingsclasspath__)
                except Exception:
                    log_trace()
                    try:
                        self.__class__ = class_from_module(
                            self.__defaultclasspath__)
                    except Exception:
                        log_trace()
                        self.__class__ = self._meta.proxy_for_model or self.__class__
            finally:
                self.db_typeclass_path = typeclass_path
        elif self.db_typeclass_path:
            try:
                self.__class__ = class_from_module(self.db_typeclass_path)
            except Exception:
                log_trace()
                try:
                    self.__class__ = class_from_module(
                        self.__defaultclasspath__)
                except Exception:
                    log_trace()
                    self.__dbclass__ = self._meta.proxy_for_model or self.__class__
        else:
            self.db_typeclass_path = "%s.%s" % (self.__module__,
                                                self.__class__.__name__)
        # important to put this at the end since _meta is based on the set __class__
        try:
            self.__dbclass__ = self._meta.proxy_for_model or self.__class__
        except AttributeError:
            err_class = repr(self.__class__)
            self.__class__ = class_from_module(
                "evennia.objects.objects.DefaultObject")
            self.__dbclass__ = class_from_module(
                "evennia.objects.models.ObjectDB")
            self.db_typeclass_path = "evennia.objects.objects.DefaultObject"
            log_trace(
                "Critical: Class %s of %s is not a valid typeclass!\nTemporarily falling back to %s."
                % (err_class, self, self.__class__))

    # initialize all handlers in a lazy fashion
    @lazy_property
    def attributes(self):
        return AttributeHandler(self)

    @lazy_property
    def locks(self):
        return LockHandler(self)

    @lazy_property
    def tags(self):
        return TagHandler(self)

    @lazy_property
    def aliases(self):
        return AliasHandler(self)

    @lazy_property
    def permissions(self):
        return PermissionHandler(self)

    @lazy_property
    def nattributes(self):
        return NAttributeHandler(self)

    class Meta(object):
        """
        Django setup info.
        """
        abstract = True
        verbose_name = "Evennia Database Object"
        ordering = ['-db_date_created', 'id', 'db_typeclass_path', 'db_key']

    # wrapper
    # Wrapper properties to easily set database fields. These are
    # @property decorators that allows to access these fields using
    # normal python operations (without having to remember to save()
    # etc). So e.g. a property 'attr' has a get/set/del decorator
    # defined that allows the user to do self.attr = value,
    # value = self.attr and del self.attr respectively (where self
    # is the object in question).

    # name property (alias to self.key)
    def __name_get(self):
        return self.key

    def __name_set(self, value):
        self.key = value

    def __name_del(self):
        raise Exception("Cannot delete name")

    name = property(__name_get, __name_set, __name_del)

    # key property (overrides's the idmapper's db_key for the at_rename hook)
    @property
    def key(self):
        return self.db_key

    @key.setter
    def key(self, value):
        oldname = str(self.db_key)
        self.db_key = value
        self.save(update_fields=["db_key"])
        self.at_rename(oldname, value)

    #
    #
    # TypedObject main class methods and properties
    #
    #

    def __eq__(self, other):
        return other and hasattr(other, 'dbid') and self.dbid == other.dbid

    def __str__(self):
        return smart_str("%s" % self.db_key)

    def __unicode__(self):
        return u"%s" % self.db_key

    #@property
    def __dbid_get(self):
        """
        Caches and returns the unique id of the object.
        Use this instead of self.id, which is not cached.
        """
        return self.id

    def __dbid_set(self, value):
        raise Exception("dbid cannot be set!")

    def __dbid_del(self):
        raise Exception("dbid cannot be deleted!")

    dbid = property(__dbid_get, __dbid_set, __dbid_del)

    #@property
    def __dbref_get(self):
        """
        Returns the object's dbref on the form #NN.
        """
        return "#%s" % self.id

    def __dbref_set(self):
        raise Exception("dbref cannot be set!")

    def __dbref_del(self):
        raise Exception("dbref cannot be deleted!")

    dbref = property(__dbref_get, __dbref_set, __dbref_del)

    def at_idmapper_flush(self):
        """
        This is called when the idmapper cache is flushed and
        allows customized actions when this happens.

        Returns:
            do_flush (bool): If True, flush this object as normal. If
                False, don't flush and expect this object to handle
                the flushing on its own.

        Notes:
            The default implementation relies on being able to clear
            Django's Foreignkey cache on objects not affected by the
            flush (notably objects with an NAttribute stored). We rely
            on this cache being stored on the format "_<fieldname>_cache".
            If Django were to change this name internally, we need to
            update here (unlikely, but marking just in case).

        """
        if self.nattributes.all():
            # we can't flush this object if we have non-persistent
            # attributes stored - those would get lost! Nevertheless
            # we try to flush as many references as we can.
            self.attributes.reset_cache()
            self.tags.reset_cache()
            # flush caches for all related fields
            for field in self._meta.fields:
                name = "_%s_cache" % field.name
                if field.is_relation and name in self.__dict__:
                    # a foreignkey - remove its cache
                    del self.__dict__[name]
            return False
        # a normal flush
        return True

    #
    # Object manipulation methods
    #

    def is_typeclass(self, typeclass, exact=True):
        """
        Returns true if this object has this type OR has a typeclass
        which is an subclass of the given typeclass. This operates on
        the actually loaded typeclass (this is important since a
        failing typeclass may instead have its default currently
        loaded) typeclass - can be a class object or the python path
        to such an object to match against.

        Args:
            typeclass (str or class): A class or the full python path
                to the class to check.
            exact (bool, optional): Returns true only if the object's
                type is exactly this typeclass, ignoring parents.

        Returns:
            is_typeclass (bool): If this typeclass matches the given
                typeclass.

        """
        if isinstance(typeclass, basestring):
            typeclass = [typeclass] + [
                "%s.%s" % (prefix, typeclass)
                for prefix in settings.TYPECLASS_PATHS
            ]
        else:
            typeclass = [typeclass.path]

        selfpath = self.path
        if exact:
            # check only exact match
            return selfpath in typeclass
        else:
            # check parent chain
            return any(
                hasattr(cls, "path") and cls.path in typeclass
                for cls in self.__class__.mro())

    def swap_typeclass(self,
                       new_typeclass,
                       clean_attributes=False,
                       run_start_hooks="all",
                       no_default=True,
                       clean_cmdsets=False):
        """
        This performs an in-situ swap of the typeclass. This means
        that in-game, this object will suddenly be something else.
        Player will not be affected. To 'move' a player to a different
        object entirely (while retaining this object's type), use
        self.player.swap_object().

        Note that this might be an error prone operation if the
        old/new typeclass was heavily customized - your code
        might expect one and not the other, so be careful to
        bug test your code if using this feature! Often its easiest
        to create a new object and just swap the player over to
        that one instead.

        Args:
            new_typeclass (str or classobj): Type to switch to.
            clean_attributes (bool or list, optional): Will delete all
                attributes stored on this object (but not any of the
                database fields such as name or location). You can't get
                attributes back, but this is often the safest bet to make
                sure nothing in the new typeclass clashes with the old
                one. If you supply a list, only those named attributes
                will be cleared.
            run_start_hooks (str or None, optional): This is either None,
                to not run any hooks, "all" to run all hooks defined by
                at_first_start, or a string giving the name of the hook
                to run (for example 'at_object_creation'). This will
                always be called without arguments.
            no_default (bool, optiona): If set, the swapper will not
                allow for swapping to a default typeclass in case the
                given one fails for some reason. Instead the old one will
                be preserved.
            clean_cmdsets (bool, optional): Delete all cmdsets on the object.

        """

        if not callable(new_typeclass):
            # this is an actual class object - build the path
            new_typeclass = class_from_module(
                new_typeclass, defaultpaths=settings.TYPECLASS_PATHS)

        # if we get to this point, the class is ok.

        if inherits_from(self, "evennia.scripts.models.ScriptDB"):
            if self.interval > 0:
                raise RuntimeError("Cannot use swap_typeclass on time-dependent " \
                                   "Script '%s'.\nStop and start a new Script of the " \
                                   "right type instead." % self.key)

        self.typeclass_path = new_typeclass.path
        self.__class__ = new_typeclass

        if clean_attributes:
            # Clean out old attributes
            if is_iter(clean_attributes):
                for attr in clean_attributes:
                    self.attributes.remove(attr)
                for nattr in clean_attributes:
                    if hasattr(self.ndb, nattr):
                        self.nattributes.remove(nattr)
            else:
                self.attributes.clear()
                self.nattributes.clear()
        if clean_cmdsets:
            # purge all cmdsets
            self.cmdset.clear()
            self.cmdset.remove_default()

        if run_start_hooks == 'all':
            # fake this call to mimic the first save
            self.at_first_save()
        elif run_start_hooks:
            # a custom hook-name to call.
            getattr(self, run_start_hooks)()

    #
    # Lock / permission methods
    #

    def access(self,
               accessing_obj,
               access_type='read',
               default=False,
               no_superuser_bypass=False,
               **kwargs):
        """
        Determines if another object has permission to access this one.

        Args:
            accessing_obj (str): Object trying to access this one.
            access_type (str, optional): Type of access sought.
            default (bool, optional): What to return if no lock of
                access_type was found
            no_superuser_bypass (bool, optional): Turn off the
                superuser lock bypass (be careful with this one).

        Kwargs:
            kwargs (any): Ignored, but is there to make the api
                consistent with the object-typeclass method access, which
                use it to feed to its hook methods.

        """
        return self.locks.check(accessing_obj,
                                access_type=access_type,
                                default=default,
                                no_superuser_bypass=no_superuser_bypass)

    def check_permstring(self, permstring):
        """
        This explicitly checks if we hold particular permission
        without involving any locks.

        Args:
            permstring (str): The permission string to check against.

        Returns:
            result (bool): If the permstring is passed or not.

        """
        if hasattr(self, "player"):
            if self.player and self.player.is_superuser and not self.player.attributes.get(
                    "_quell"):
                return True
        else:
            if self.is_superuser and not self.attributes.get("_quell"):
                return True

        if not permstring:
            return False
        perm = permstring.lower()
        perms = [p.lower() for p in self.permissions.all()]
        if perm in perms:
            # simplest case - we have a direct match
            return True
        if perm in _PERMISSION_HIERARCHY:
            # check if we have a higher hierarchy position
            ppos = _PERMISSION_HIERARCHY.index(perm)
            return any(True for hpos, hperm in enumerate(_PERMISSION_HIERARCHY)
                       if hperm in perms and hpos > ppos)
        return False

    #
    # Deletion methods
    #

    def _deleted(self, *args, **kwargs):
        """
        Scrambling method for already deleted objects
        """
        raise ObjectDoesNotExist("This object was already deleted!")

    def delete(self):
        """
        Cleaning up handlers on the typeclass level

        """
        global TICKER_HANDLER
        self.permissions.clear()
        self.attributes.clear()
        self.aliases.clear()
        if hasattr(self, "nicks"):
            self.nicks.clear()

        # scrambling properties
        self.delete = self._deleted
        super(TypedObject, self).delete()

    #
    # Memory management
    #

    #def flush_from_cache(self):
    #    """
    #    Flush this object instance from cache, forcing an object reload.
    #    Note that this will kill all temporary attributes on this object
    #     since it will be recreated as a new Typeclass instance.
    #    """
    #    self.__class__.flush_cached_instance(self)

    #
    # Attribute storage
    #

    #@property db
    def __db_get(self):
        """
        Attribute handler wrapper. Allows for the syntax
           obj.db.attrname = value
             and
           value = obj.db.attrname
             and
           del obj.db.attrname
             and
           all_attr = obj.db.all() (unless there is an attribute
                      named 'all', in which case that will be returned instead).
        """
        try:
            return self._db_holder
        except AttributeError:
            self._db_holder = DbHolder(self, 'attributes')
            return self._db_holder

    #@db.setter
    def __db_set(self, value):
        "Stop accidentally replacing the db object"
        string = "Cannot assign directly to db object! "
        string += "Use db.attr=value instead."
        raise Exception(string)

    #@db.deleter
    def __db_del(self):
        "Stop accidental deletion."
        raise Exception("Cannot delete the db object!")

    db = property(__db_get, __db_set, __db_del)

    #
    # Non-persistent (ndb) storage
    #

    #@property ndb
    def __ndb_get(self):
        """
        A non-attr_obj store (ndb: NonDataBase). Everything stored
        to this is guaranteed to be cleared when a server is shutdown.
        Syntax is same as for the _get_db_holder() method and
        property, e.g. obj.ndb.attr = value etc.
        """
        try:
            return self._ndb_holder
        except AttributeError:
            self._ndb_holder = DbHolder(self,
                                        "nattrhandler",
                                        manager_name='nattributes')
            return self._ndb_holder

    #@db.setter
    def __ndb_set(self, value):
        "Stop accidentally replacing the ndb object"
        string = "Cannot assign directly to ndb object! "
        string += "Use ndb.attr=value instead."
        raise Exception(string)

    #@db.deleter
    def __ndb_del(self):
        "Stop accidental deletion."
        raise Exception("Cannot delete the ndb object!")

    ndb = property(__ndb_get, __ndb_set, __ndb_del)

    def get_display_name(self, looker, **kwargs):
        """
        Displays the name of the object in a viewer-aware manner.

        Args:
            looker (TypedObject): The object or player that is looking
                at/getting inforamtion for this object.

        Returns:
            name (str): A string containing the name of the object,
                including the DBREF if this user is privileged to control
                said object.

        Notes:
            This function could be extended to change how object names
            appear to users in character, but be wary. This function
            does not change an object's keys or aliases when
            searching, and is expected to produce something useful for
            builders.

        """
        if self.access(looker, access_type='controls'):
            return "{}(#{})".format(self.name, self.id)
        return self.name

    def get_extra_info(self, looker, **kwargs):
        """
        Used when an object is in a list of ambiguous objects as an
        additional information tag.

        For instance, if you had potions which could have varying
        levels of liquid left in them, you might want to display how
        many drinks are left in each when selecting which to drop, but
        not in your normal inventory listing.

        Args:
            looker (TypedObject): The object or player that is looking
                at/getting information for this object.

        Returns:
            info (str): A string with disambiguating information,
                conventionally with a leading space.

        """

        if self.location == looker:
            return " (carried)"
        return ""

    def at_rename(self, oldname, newname):
        """
        This Hook is called by @name on a successful rename.

        Args:
            oldname (str): The instance's original name.
            newname (str): The new name for the instance.

        """
        pass