Made typeclass loading a little more forgiving, adding a multitude of try-levels to fall back to in case of errors. Default is now to fallback to first settings-set default, then to the library default and only as a last resort fall back to the model. Every fallback step is logged. This should make the missing-hook error of #698 go away and report missing typeclasses in a better way.

This commit is contained in:
Griatch 2015-03-11 00:41:06 +01:00
parent c9ed8b5ec5
commit d752106906
5 changed files with 71 additions and 6 deletions

View file

@ -3,6 +3,7 @@ Default Typeclass for Comms.
See objects.objects for more information on Typeclassing.
"""
from django.conf import settings
from evennia.typeclasses.models import TypeclassBase
from evennia.comms.models import Msg, TempMsg, ChannelDB
from evennia.comms.managers import ChannelManager
@ -15,7 +16,10 @@ class DefaultChannel(ChannelDB):
This is the base class for all Comms. Inherit from this to create different
types of communication channels.
"""
# typeclass setup
__metaclass__ = TypeclassBase
__settingclasspath__ = settings.BASE_CHANNEL_TYPECLASS
__defaultclasspath__ = "evennia.comms.comms.DefaultChannel"
objects = ChannelManager()
def at_first_save(self):

View file

@ -135,6 +135,8 @@ class DefaultObject(ObjectDB):
"""
# typeclass setup
__metaclass__ = TypeclassBase
__settingsclasspath__ = settings.BASE_OBJECT_TYPECLASS
__defaultclasspath__ = "evennia.objects.objects.DefaultObject"
objects = ObjectManager()
# on-object properties

View file

@ -109,6 +109,9 @@ class DefaultPlayer(PlayerDB):
"""
__metaclass__ = TypeclassBase
__settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS
__defaultclasspath__ = "evennia.players.players.DefaultPlayer"
objects = PlayerManager()
# properties

View file

@ -8,6 +8,7 @@ ability to run timers.
from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.task import LoopingCall
from django.utils.translation import ugettext as _
from django.conf import settings
from evennia.typeclasses.models import TypeclassBase
from evennia.scripts.models import ScriptDB
from evennia.scripts.manager import ScriptManager
@ -140,6 +141,8 @@ class ScriptBase(ScriptDB):
"""
__metaclass__ = TypeclassBase
__settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS
__defaultclasspath__ = "evennia.scripts.scripts.DefaultScript"
objects = ScriptManager()

View file

@ -89,8 +89,23 @@ class TypeclassBase(SharedMemoryModelBase):
"""
# storage of stats
attrs["typename"] = name#cls.__name__
attrs["typename"] = name
attrs["path"] = "%s.%s" % (attrs["__module__"], name)
#defaultpath = attrs["__defaultclasspath__"]
#attrs["__defaultclass__"] = class_from_module(attrs["__defaultclasspath__"])
#try:
# defaultpath = attrs["__defaultclasspath__"]
# attrs["__defaultclass__"] = class_from_module(attrs["__defaultclasspath__"])
#except Exception:
# log_trace("Typeclass error for %s: Default typeclass '%s' could not load. "
# "Falling back to library base." % (name, defaultpath))
# try:
# # two levels down from TypedObject will always be the default base class.
# attrs["__defaultclass__"] = cls.__mro__[cls.__mro__.index(TypedObject)-2]
# except Exception:
# log_trace("Critical error for %s: Neither typeclass, "
# "default fallback nor base class could load." % name)
# attrs["__defaultclass__"] = cls
# typeclass proxy setup
if not "Meta" in attrs:
@ -172,9 +187,33 @@ class TypedObject(SharedMemoryModel):
def __init__(self, *args, **kwargs):
"""
This is the main function of the typeclass system -
to dynamically re-apply a class based on the
db_typeclass_path rather than use the one in the model.
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
1. Attempt to load typeclass stored in db_typeclass_path
1. Attempt to load `__settingsclasspath__`, which is by the
default classes defined to be the respective user-set
base typeclass settings, like `BASE_OBJECT_TYPECLASS`.
1. Attempt to load `__defaultclasspath__`, which is the
base classes in the library, like DefaultObject etc.
1. 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)
@ -182,15 +221,29 @@ class TypedObject(SharedMemoryModel):
if typeclass_path:
try:
self.__class__ = class_from_module(typeclass_path)
except ImportError:
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 ImportError:
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__