From b5ddc52536749fb32df3eff4eae7c39b45f160d7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 13:39:14 +0100 Subject: [PATCH] Make evennia lib importable without created gamedir --- CHANGELOG.md | 1 + evennia/__init__.py | 13 ------------- evennia/commands/default/comms.py | 3 ++- evennia/server/portal/portal.py | 5 +++-- evennia/settings_default.py | 12 ++++++++++++ evennia/utils/utils.py | 17 +++++++++++++++-- evennia/web/website/forms.py | 9 ++++++--- evennia/web/website/tests.py | 3 ++- evennia/web/website/views.py | 31 +++++++++++++++++++++---------- 9 files changed, 62 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 547fdb357a..f44a67fa12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - Make MuxCommand `lhs/rhslist` always be lists, also if empty (used to be the empty string) - Fix typo in UnixCommand contrib, where `help` was given as `--hel`. - Latin (la) i18n translation (jamalainm) +- Made the `evennia` dir possible to use without gamedir for purpose of doc generation. ### Evennia 0.9.5 (2019-2020) diff --git a/evennia/__init__.py b/evennia/__init__.py index 828bec77fe..3b7c37f83f 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -379,19 +379,6 @@ def _init(): del SystemCmds del _EvContainer - # typeclases - from .utils.utils import class_from_module - - BASE_ACCOUNT_TYPECLASS = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) - BASE_OBJECT_TYPECLASS = class_from_module(settings.BASE_OBJECT_TYPECLASS) - BASE_CHARACTER_TYPECLASS = class_from_module(settings.BASE_CHARACTER_TYPECLASS) - BASE_ROOM_TYPECLASS = class_from_module(settings.BASE_ROOM_TYPECLASS) - BASE_EXIT_TYPECLASS = class_from_module(settings.BASE_EXIT_TYPECLASS) - BASE_CHANNEL_TYPECLASS = class_from_module(settings.BASE_CHANNEL_TYPECLASS) - BASE_SCRIPT_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS) - BASE_GUEST_TYPECLASS = class_from_module(settings.BASE_GUEST_TYPECLASS) - del class_from_module - # delayed starts - important so as to not back-access evennia before it has # finished initializing GLOBAL_SCRIPTS.start() diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index d984854389..f7128c1c24 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -19,7 +19,8 @@ from evennia.utils import create, logger, utils, evtable from evennia.utils.utils import make_iter, class_from_module COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) -CHANNEL_DEFAULT_TYPECLASS = class_from_module(settings.BASE_CHANNEL_TYPECLASS) +CHANNEL_DEFAULT_TYPECLASS = class_from_module( + settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS) # limit symbol import for API diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 77c56d22b6..54cd748c39 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -31,7 +31,6 @@ from evennia.utils.utils import get_evennia_version, mod_import, make_iter, clas from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS from evennia.utils import logger from evennia.server.webserver import EvenniaReverseProxyResource -from django.db import connection # we don't need a connection to the database so close it right away @@ -434,4 +433,6 @@ if WEBSERVER_ENABLED: for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES: # external plugin services to start - plugin_module.start_plugin_services(PORTAL) + if plugin_module: + plugin_module.start_plugin_services(PORTAL) + diff --git a/evennia/settings_default.py b/evennia/settings_default.py index b642d5acfb..9693e70828 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -511,6 +511,18 @@ START_LOCATION = "#2" # out of sync between the processes. Keep on unless you face such # issues. TYPECLASS_AGGRESSIVE_CACHE = True +# These are fallbacks for BASE typeclasses failing to load. Usually needed only +# during doc building. The system expects these to *always* load correctly, so +# only modify if you are making fundamental changes to how objects/accounts +# work and know what you are doing +FALLBACK_ACCOUNT_TYPECLASS = "evennia.accounts.accounts.DefaultAccount" +FALLBACK_OBJECT_TYPECLASS = "evennia.objects.objects.DefaultObject" +FALLBACK_CHARACTER_TYPECLASS = "evennia.objects.objects.DefaultCharacter" +FALLBACK_ROOM_TYPECLASS = "evennia.objects.objects.DefaultRoom" +FALLBACK_EXIT_TYPECLASS = "evennia.objects.objects.DefaultExit" +FALLBACK_CHANNEL_TYPECLASS = "evennia.comms.comms.DefaultChannel" +FALLBACK_SCRIPT_TYPECLASS = "evennia.scripts.scripts.DefaultScript" + ###################################################################### # Options and validators diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 2c67593c29..6ba51c2987 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1319,6 +1319,9 @@ def variable_from_module(module, variable=None, default=None): mod = mod_import(module) + if not mod: + return default + if variable: result = [] for var in make_iter(variable): @@ -1410,7 +1413,7 @@ def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None): return default -def class_from_module(path, defaultpaths=None): +def class_from_module(path, defaultpaths=None, fallback=None): """ Return a class from a module, given the class' full python path. This is primarily used to convert db_typeclass_path:s to classes. @@ -1419,6 +1422,10 @@ def class_from_module(path, defaultpaths=None): path (str): Full Python dot-path to module. defaultpaths (iterable, optional): If a direct import from `path` fails, try subsequent imports by prepending those paths to `path`. + fallback (str): If all other attempts fail, use this path as a fallback. + This is intended as a last-resport. In the example of Evennia + loading, this would be a path to a default parent class in the + evennia repo itself. Returns: class (Class): An uninstatiated class recovered from path. @@ -1472,7 +1479,13 @@ def class_from_module(path, defaultpaths=None): err += "\nPaths searched:\n %s" % "\n ".join(paths) else: err += "." - raise ImportError(err) + logger.log_err(err) + if fallback: + logger.log_warn(f"Falling back to {fallback}.") + return class_from_module(fallback) + else: + # even fallback fails + raise ImportError(err) return cls diff --git a/evennia/web/website/forms.py b/evennia/web/website/forms.py index 8a1585459f..505415f7cd 100644 --- a/evennia/web/website/forms.py +++ b/evennia/web/website/forms.py @@ -52,7 +52,8 @@ class AccountForm(UserCreationForm): """ # The model/typeclass this form creates - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) + model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, + fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) # The fields to display on the form, in the given order fields = ("username", "email") @@ -87,7 +88,8 @@ class ObjectForm(EvenniaForm, ModelForm): """ # The model/typeclass this form creates - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) # The fields to display on the form, in the given order fields = ("db_key",) @@ -140,7 +142,8 @@ class CharacterForm(ObjectForm): """ # Get the correct object model - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS) + model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, + fallback=settings.FALLBACK_CHARACTER_TYPECLASS) # Allow entry of the 'key' field fields = ("db_key",) diff --git a/evennia/web/website/tests.py b/evennia/web/website/tests.py index 0c6ae8bf48..a4633f51f4 100644 --- a/evennia/web/website/tests.py +++ b/evennia/web/website/tests.py @@ -125,7 +125,8 @@ class ChannelDetailTest(EvenniaWebTest): def setUp(self): super(ChannelDetailTest, self).setUp() - klass = class_from_module(self.channel_typeclass) + klass = class_from_module(self.channel_typeclass, + fallback=settings.FALLBACK_CHANNEL_TYPECLASS) # Create a channel klass.create("demo") diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index 5e22698ad9..fe65bc5c8b 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -32,6 +32,7 @@ from django.utils.text import slugify _BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS +# typeclass fallbacks def _gamestats(): # Some misc. configurable stuff. @@ -48,11 +49,14 @@ def _gamestats(): nobjs = ObjectDB.objects.count() nobjs = nobjs or 1 # fix zero-div error with empty database - Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS) + Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS, + fallback=settings.FALLBACK_CHARACTER_TYPECLASS) nchars = Character.objects.all_family().count() - Room = class_from_module(settings.BASE_ROOM_TYPECLASS) + Room = class_from_module(settings.BASE_ROOM_TYPECLASS, + fallback=settings.FALLBACK_ROOM_TYPECLASS) nrooms = Room.objects.all_family().count() - Exit = class_from_module(settings.BASE_EXIT_TYPECLASS) + Exit = class_from_module(settings.BASE_EXIT_TYPECLASS, + fallback=settings.FALLBACK_EXIT_TYPECLASS) nexits = Exit.objects.all_family().count() nothers = nobjs - nchars - nrooms - nexits @@ -269,7 +273,8 @@ class ObjectDetailView(EvenniaDetailView): # # So when you extend it, this line should look simple, like: # model = Object - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) # What HTML template you wish to use to display this page. template_name = "website/object_detail.html" @@ -372,7 +377,8 @@ class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView): """ - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): @@ -387,7 +393,8 @@ class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): """ # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) template_name = "website/object_confirm_delete.html" # -- Evennia constructs -- @@ -430,7 +437,8 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView): """ # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) # -- Evennia constructs -- access_type = "edit" @@ -513,7 +521,8 @@ class AccountMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) + model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, + fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) form_class = website_forms.AccountForm @@ -578,7 +587,8 @@ class CharacterMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS) + model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, + fallback=settings.FALLBACK_CHARACTER_TYPECLASS) form_class = website_forms.CharacterForm success_url = reverse_lazy("character-manage") @@ -817,7 +827,8 @@ class ChannelMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_CHANNEL_TYPECLASS) + model = class_from_module(settings.BASE_CHANNEL_TYPECLASS, + fallback=settings.FALLBACK_CHANNEL_TYPECLASS) # -- Evennia constructs -- page_title = "Channels"