Source code for evennia.utils.containers

"""
Containers

Containers are storage classes usually initialized from a setting. They
represent Singletons and acts as a convenient place to find resources (
available as properties on the singleton)

evennia.GLOBAL_SCRIPTS
evennia.OPTION_CLASSES

"""


from django.conf import settings
from evennia.utils.utils import class_from_module, callables_from_module
from evennia.utils import logger


SCRIPTDB = None


[docs]class Container(object): """ Base container class. A container is simply a storage object whose properties can be acquired as a property on it. This is generally considered a read-only affair. The container is initialized by a list of modules containing callables. """ storage_modules = []
[docs] def __init__(self): """ Read data from module. """ self.loaded_data = None
[docs] def load_data(self): """ Delayed import to avoid eventual circular imports from inside the storage modules. """ if self.loaded_data is None: self.loaded_data = {} for module in self.storage_modules: self.loaded_data.update(callables_from_module(module))
def __getattr__(self, key): return self.get(key)
[docs] def get(self, key, default=None): """ Retrive data by key (in case of not knowing it beforehand). Args: key (str): The name of the script. default (any, optional): Value to return if key is not found. Returns: any (any): The data loaded on this container. """ self.load_data() return self.loaded_data.get(key, default)
[docs] def all(self): """ Get all stored data Returns: scripts (list): All global script objects stored on the container. """ self.load_data() return list(self.loaded_data.values())
[docs]class OptionContainer(Container): """ Loads and stores the final list of OPTION CLASSES. Can access these as properties or dictionary-contents. """ storage_modules = settings.OPTION_CLASS_MODULES
[docs]class GlobalScriptContainer(Container): """ Simple Handler object loaded by the Evennia API to contain and manage a game's Global Scripts. This will list global Scripts created on their own but will also auto-(re)create scripts defined in `settings.GLOBAL_SCRIPTS`. Example: import evennia evennia.GLOBAL_SCRIPTS.scriptname Note: This does not use much of the BaseContainer since it's not loading callables from settings but a custom dict of tuples. """
[docs] def __init__(self): """ Note: We must delay loading of typeclasses since this module may get initialized before Scripts are actually initialized. """ self.typeclass_storage = None self.loaded_data = { key: {} if data is None else data for key, data in settings.GLOBAL_SCRIPTS.items() }
def _get_scripts(self, key=None, default=None): global SCRIPTDB if not SCRIPTDB: from evennia.scripts.models import ScriptDB as SCRIPTDB if key: try: return SCRIPTDB.objects.get(db_key__exact=key, db_obj__isnull=True) except SCRIPTDB.DoesNotExist: return default else: return SCRIPTDB.objects.filter(db_obj__isnull=True) def _load_script(self, key): self.load_data() typeclass = self.typeclass_storage[key] found = typeclass.objects.filter(db_key=key).first() interval = self.loaded_data[key].get("interval", None) start_delay = self.loaded_data[key].get("start_delay", None) repeats = self.loaded_data[key].get("repeats", 0) desc = self.loaded_data[key].get("desc", "") if not found: logger.log_info(f"GLOBAL_SCRIPTS: (Re)creating {key} ({typeclass}).") new_script, errors = typeclass.create( key=key, persistent=True, interval=interval, start_delay=start_delay, repeats=repeats, desc=desc, ) if errors: logger.log_err("\n".join(errors)) return None new_script.start() return new_script if ( (found.interval != interval) or (found.start_delay != start_delay) or (found.repeats != repeats) ): found.restart(interval=interval, start_delay=start_delay, repeats=repeats) if found.desc != desc: found.desc = desc return found
[docs] def start(self): """ Called last in evennia.__init__ to initialize the container late (after script typeclasses have finished loading). We include all global scripts in the handler and make sure to auto-load time-based scripts. """ # populate self.typeclass_storage self.load_data() # start registered scripts for key in self.loaded_data: self._load_script(key)
[docs] def load_data(self): """ This delayed import avoids trying to load Scripts before they are initialized. """ if self.typeclass_storage is None: self.typeclass_storage = {} for key, data in self.loaded_data.items(): try: typeclass = data.get("typeclass", settings.BASE_SCRIPT_TYPECLASS) self.typeclass_storage[key] = class_from_module(typeclass) except ImportError as err: logger.log_err( f"GlobalScriptContainer could not start global script {key}: {err}" )
[docs] def get(self, key, default=None): """ Retrive data by key (in case of not knowing it beforehand). Any scripts that are in settings.GLOBAL_SCRIPTS that are not found will be recreated on-demand. Args: key (str): The name of the script. default (any, optional): Value to return if key is not found at all on this container (i.e it cannot be loaded at all). Returns: any (any): The data loaded on this container. """ res = self._get_scripts(key) if not res: if key in self.loaded_data: # recreate if we have the info return self._load_script(key) or default return default return res
[docs] def all(self): """ Get all global scripts. Note that this will not auto-start scripts defined in settings. Returns: scripts (list): All global script objects stored on the container. """ self.typeclass_storage = None self.load_data() for key in self.loaded_data: self._load_script(key) return self._get_scripts(None)
# Create all singletons GLOBAL_SCRIPTS = GlobalScriptContainer() OPTION_CLASSES = OptionContainer()