Refactor containers for inheritance and delayed loading

This commit is contained in:
Griatch 2019-04-14 15:37:34 +02:00
parent d1baab7c0b
commit 6ddc98a947
8 changed files with 234 additions and 173 deletions

View file

@ -1,6 +1,14 @@
"""
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.VALIDATOR_FUNCS
evennia.OPTION_CLASSES
"""
@ -9,7 +17,82 @@ from evennia.utils.utils import class_from_module, callables_from_module
from evennia.utils import logger
class GlobalScriptContainer(object):
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 = []
def __init__(self):
"""
Read data from module.
"""
self.loaded_data = None
def _load_data(self):
"""
Delayed import to avoid eventual circular imports from inside
the storage modules.
"""
if self.loaded_data is None:
for module in self.storage_modules:
self.loaded_data.update(callables_from_module(module))
def __getattr__(self, key):
self._load_data()
return self.loaded_data.get(key)
def get(self, key):
"""
Retrive data by key (in case of not knowing it beforehand).
Args:
key (str): The name of the script.
Returns:
any (any): The data loaded on this container.
"""
return self.__getattr__(key)
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())
class ValidatorContainer(Container):
"""
Loads and stores the final list of VALIDATOR FUNCTIONS.
Can access these as properties or dictionary-contents.
"""
storage_modules = settings.VALIDATOR_FUNC_MODULES
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
class GlobalScriptContainer(Container):
"""
Simple Handler object loaded by the Evennia API to contain and manage a
game's Global Scripts. Scripts to start are defined by
@ -19,45 +102,56 @@ class GlobalScriptContainer(object):
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.
"""
def __init__(self):
"""
Initialize the container by preparing scripts. Lazy-load only when the
script is requested.
Note: We must delay loading of typeclasses since this module may get
initialized before Scripts are actually initialized.
"""
self.script_data = {key: {} if data is None else data
self.loaded_data = {key: {} if data is None else data
for key, data in settings.GLOBAL_SCRIPTS.items()}
self.script_storage = {}
self.typeclass_storage = {}
for key, data in self.script_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"GlobalContainer could not start global script {key}: {err}")
def __getitem__(self, key):
if key not in self.typeclass_storage:
# this script is unknown to the container
return None
# (re)create script on-demand
return self.script_storage.get(key) or self._load_script(key)
self.typeclass_storage = None
def __getattr__(self, key):
return self[key]
if key not in self.loaded_data:
return None
return self.script_storage.get(key) or self._load_script(key)
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}")
def _load_script(self, key):
self._load_data()
typeclass = self.typeclass_storage[key]
found = typeclass.objects.filter(db_key=key).first()
interval = self.script_data[key].get('interval', None)
start_delay = self.script_data[key].get('start_delay', None)
repeats = self.script_data[key].get('repeats', 0)
desc = self.script_data[key].get('desc', '')
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:
new_script, errors = typeclass.create(key=key, persistent=True,
@ -81,20 +175,6 @@ class GlobalScriptContainer(object):
self.script_storage[key] = found
return found
def get(self, key):
"""
Retrive script by key (in case of not knowing it beforehand).
Args:
key (str): The name of the script.
Returns:
script (Script): The named global script.
"""
# note that this will recreate the script if it doesn't exist/was lost
return self[key]
def all(self):
"""
Get all scripts.
@ -103,53 +183,11 @@ class GlobalScriptContainer(object):
scripts (list): All global script objects stored on the container.
"""
return list(self.script_storage.values())
return [self.__getattr__(key) for key in self.loaded_data]
# Create singleton of the GlobalHandler for the API.
# Create all singletons
GLOBAL_SCRIPTS = GlobalScriptContainer()
class ValidatorContainer(object):
"""
Loads and stores the final list of VALIDATOR FUNCTIONS.
Can access these as properties or dictionary-contents.
"""
def __init__(self):
self.valid_storage = {}
for module in settings.VALIDATOR_FUNC_MODULES:
self.valid_storage.update(callables_from_module(module))
def __getitem__(self, item):
return self.valid_storage.get(item, None)
def __getattr__(self, item):
return self[item]
# Ensure that we have a Singleton of ValidHandler that is always loaded... and only needs to be loaded once.
VALIDATOR_FUNCS = ValidatorContainer()
class OptionContainer(object):
"""
Loads and stores the final list of OPTION CLASSES.
Can access these as properties or dictionary-contents.
"""
def __init__(self):
self.option_storage = {}
for module in settings.OPTION_CLASS_MODULES:
self.option_storage.update(callables_from_module(module))
def __getitem__(self, item):
return self.option_storage.get(item, None)
def __getattr__(self, item):
return self[item]
# Ensure that we have a Singleton that keeps all loaded Option classes
OPTION_CLASSES = OptionContainer()