mirror of
https://github.com/evennia/evennia.git
synced 2026-04-03 14:37:17 +02:00
Rework options/optionhandler to use custom save/load functions
This commit is contained in:
parent
f2d9391827
commit
10b3657ffb
3 changed files with 128 additions and 93 deletions
|
|
@ -200,7 +200,12 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
|||
|
||||
@lazy_property
|
||||
def options(self):
|
||||
return OptionHandler(self, options_dict=settings.OPTIONS_ACCOUNT_DEFAULT, save_category='option')
|
||||
return OptionHandler(self,
|
||||
options_dict=settings.OPTIONS_ACCOUNT_DEFAULT,
|
||||
savefunc=self.attributes.add,
|
||||
loadfunc=self.attributes.get,
|
||||
save_kwargs={"category": 'option'},
|
||||
load_kwargs={"category": 'option'})
|
||||
|
||||
# Do not make this a lazy property; the web UI will not refresh it!
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import datetime as _dt
|
||||
from evennia import logger as _log
|
||||
from evennia.utils.ansi import ANSIString as _ANSI
|
||||
import datetime
|
||||
from evennia import logger
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia.utils.validatorfuncs import _TZ_DICT
|
||||
from evennia.utils.containers import VALIDATOR_FUNCS
|
||||
from evennia.utils.utils import crop
|
||||
|
|
@ -29,7 +29,7 @@ class BaseOption(object):
|
|||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __init__(self, handler, key, description, default, save_data=None):
|
||||
def __init__(self, handler, key, description, default):
|
||||
"""
|
||||
|
||||
Args:
|
||||
|
|
@ -38,14 +38,12 @@ class BaseOption(object):
|
|||
Must be unique per OptionHandler.
|
||||
description (str): What this Option's text will show in commands and menus.
|
||||
default: A default value for this Option.
|
||||
save_data: Whatever was saved to Attributes. This differs by Option.
|
||||
|
||||
"""
|
||||
self.handler = handler
|
||||
self.key = key
|
||||
self.default_value = default
|
||||
self.description = description
|
||||
self.save_data = save_data
|
||||
|
||||
# Value Storage contains None until the Option is loaded.
|
||||
self.value_storage = None
|
||||
|
|
@ -63,7 +61,7 @@ class BaseOption(object):
|
|||
|
||||
@property
|
||||
def value(self):
|
||||
if not self.loaded and self.save_data is not None:
|
||||
if not self.loaded:
|
||||
self.load()
|
||||
if self.loaded:
|
||||
return self.value_storage
|
||||
|
|
@ -98,28 +96,36 @@ class BaseOption(object):
|
|||
Boolean: Whether loading was successful.
|
||||
|
||||
"""
|
||||
if self.save_data is not None:
|
||||
try:
|
||||
self.value_storage = self.deserialize(self.save_data)
|
||||
self.loaded = True
|
||||
return True
|
||||
except Exception as e:
|
||||
_log.log_trace(e)
|
||||
return False
|
||||
loadfunc = self.handler.loadfunc
|
||||
load_kwargs = self.handler.load_kwargs
|
||||
|
||||
print("load", self.key, loadfunc, load_kwargs)
|
||||
try:
|
||||
self.value_storage = self.deserialize(
|
||||
loadfunc(self.key, default=self.default_value, **load_kwargs))
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
return False
|
||||
self.loaded = True
|
||||
return True
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
Stores the current value (to an Attribute by default).
|
||||
Stores the current value using .handler.save_handler(self.key, value, **kwargs)
|
||||
where kwargs are a combination of those passed into this function and the
|
||||
ones specified by the OptionHandler.
|
||||
|
||||
Kwargs:
|
||||
any (any): Not used by default. These are passed in from self.set
|
||||
and allows the option to let the caller customize saving
|
||||
if desrired.
|
||||
and allows the option to let the caller customize saving by
|
||||
overriding or extend the default save kwargs
|
||||
|
||||
"""
|
||||
self.handler.obj.attributes.add(self.key,
|
||||
category=self.handler.save_category,
|
||||
value=self.serialize())
|
||||
value = self.serialize()
|
||||
save_kwargs = {**self.handler.save_kwargs, **kwargs}
|
||||
savefunc = self.handler.savefunc
|
||||
print("save:", self.key, value, savefunc, save_kwargs)
|
||||
savefunc(self.key, value=value, **save_kwargs)
|
||||
|
||||
def deserialize(self, save_data):
|
||||
"""
|
||||
|
|
@ -160,9 +166,10 @@ class BaseOption(object):
|
|||
entries are processed.
|
||||
|
||||
Returns:
|
||||
The results of a Validator call. Might be any kind of python object.
|
||||
any (any): The results of the validation.
|
||||
|
||||
"""
|
||||
return VALIDATOR_FUNCS[self.validator_key](value, thing_name=self.key, **kwargs)
|
||||
return VALIDATOR_FUNCS.get(self.validator_key)(value, thing_name=self.key, **kwargs)
|
||||
|
||||
def display(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -227,7 +234,7 @@ class Color(BaseOption):
|
|||
return f'{self.value} - |{self.value}this|n'
|
||||
|
||||
def deserialize(self, save_data):
|
||||
if not save_data or len(_ANSI(f'|{save_data}|n')) > 0:
|
||||
if not save_data or len(strip_ansi(f'|{save_data}|n')) > 0:
|
||||
raise ValueError(f"{self.key} expected Color Code, got '{save_data}'")
|
||||
return save_data
|
||||
|
||||
|
|
@ -280,7 +287,7 @@ class Duration(BaseOption):
|
|||
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int):
|
||||
return _dt.timedelta(0, save_data, 0, 0, 0, 0, 0)
|
||||
return datetime.timedelta(0, save_data, 0, 0, 0, 0, 0)
|
||||
raise ValueError(f"{self.key} expected Timedelta in seconds, got '{save_data}'")
|
||||
|
||||
def serialize(self):
|
||||
|
|
@ -292,7 +299,7 @@ class Datetime(BaseOption):
|
|||
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int):
|
||||
return _dt.datetime.utcfromtimestamp(save_data)
|
||||
return datetime.datetime.utcfromtimestamp(save_data)
|
||||
raise ValueError(f"{self.key} expected UTC Datetime in EPOCH format, got '{save_data}'")
|
||||
|
||||
def serialize(self):
|
||||
|
|
|
|||
|
|
@ -2,46 +2,90 @@ from evennia.utils.utils import string_partial_matching
|
|||
from evennia.utils.containers import OPTION_CLASSES
|
||||
|
||||
|
||||
class InMemorySaveHandler(object):
|
||||
"""
|
||||
Fallback SaveHandler, implementing a minimum of the required save mechanism
|
||||
and storing data in memory.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.storage = {}
|
||||
|
||||
def add(self, key, value=None, **kwargs):
|
||||
self.storage[key] = value
|
||||
|
||||
def get(self, key, default=None, **kwargs):
|
||||
return self.storage.get(key, default)
|
||||
|
||||
|
||||
class OptionHandler(object):
|
||||
"""
|
||||
This is a generic Option handler meant for Typed Objects - anything that
|
||||
This is a generic Option handler. It is commonly used
|
||||
implements AttributeHandler. Retrieve options eithers as properties on
|
||||
this handler or by using the .get method.
|
||||
|
||||
This is used for Account.options but it could be used by Scripts or Objects
|
||||
just as easily. All it needs to be provided is an options_dict.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, obj, options_dict=None, save_category=None):
|
||||
def __init__(self, obj, options_dict=None, savefunc=None, loadfunc=None,
|
||||
save_kwargs=None, load_kwargs=None):
|
||||
"""
|
||||
Initialize an OptionHandler.
|
||||
|
||||
Args:
|
||||
obj (TypedObject): The Typed Object this sits on. Obj MUST
|
||||
implement the Evennia AttributeHandler or this will barf.
|
||||
obj (object): The object this handler sits on. This is usually a TypedObject.
|
||||
options_dict (dict): A dictionary of option keys, where the values
|
||||
are options. The format of those tuples is: ('key', "Description to
|
||||
show", 'option_type', <default value>)
|
||||
save_category (str): The Options data will be stored to this
|
||||
Attribute category on obj.
|
||||
savefunc (callable): A callable for all options to call when saving itself.
|
||||
It will be called as `savefunc(key, value, **save_kwargs)`. A common one
|
||||
to pass would be AttributeHandler.add.
|
||||
loadfunc (callable): A callable for all options to call when loading data into
|
||||
itself. It will be called as `loadfunc(key, default=default, **load_kwargs)`.
|
||||
A common one to pass would be AttributeHandler.get.
|
||||
save_kwargs (any): Optional extra kwargs to pass into `savefunc` above.
|
||||
load_kwargs (any): Optional extra kwargs to pass into `loadfunc` above.
|
||||
Notes:
|
||||
Both loadfunc and savefunc must be specified. If only one is given, the other
|
||||
will be ignored and in-memory storage will be used.
|
||||
|
||||
"""
|
||||
if not options_dict:
|
||||
options_dict = {}
|
||||
self.options_dict = options_dict
|
||||
self.save_category = save_category
|
||||
self.obj = obj
|
||||
self.options_dict = {} if options_dict is None else options_dict
|
||||
|
||||
# This dictionary stores the in-memory Options by their key. Values are the Option objects.
|
||||
if not savefunc and loadfunc:
|
||||
self._in_memory_handler = InMemorySaveHandler()
|
||||
savefunc = InMemorySaveHandler.add
|
||||
loadfunc = InMemorySaveHandler.get
|
||||
self.savefunc = savefunc
|
||||
self.loadfunc = loadfunc
|
||||
self.save_kwargs = {} if save_kwargs is None else save_kwargs
|
||||
self.load_kwargs = {} if load_kwargs is None else load_kwargs
|
||||
|
||||
# This dictionary stores the in-memory Options objects by their key for
|
||||
# quick lookup.
|
||||
self.options = {}
|
||||
|
||||
# We use lazy-loading of each Option when it's called for, but it's
|
||||
# good to have the save data on hand.
|
||||
self.save_data = {s.key: s.value for s in obj.attributes.get(
|
||||
category=save_category, return_list=True, return_obj=True) if s}
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self.get(key).value
|
||||
return self.get(key)
|
||||
|
||||
def _load_option(self, key):
|
||||
"""
|
||||
Loads option on-demand if it has not been loaded yet.
|
||||
|
||||
Args:
|
||||
key (str): The option being loaded.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
desc, clsname, default_val = self.options_dict[key]
|
||||
loaded_option = OPTION_CLASSES.get(clsname)(self, key, desc, default_val)
|
||||
# store the value for future easy access
|
||||
self.options[key] = loaded_option
|
||||
return loaded_option
|
||||
|
||||
def get(self, key, return_obj=False):
|
||||
"""
|
||||
|
|
@ -59,13 +103,34 @@ class OptionHandler(object):
|
|||
"""
|
||||
if key not in self.options_dict:
|
||||
raise KeyError("Option not found!")
|
||||
if key in self.options:
|
||||
op_found = self.options[key]
|
||||
else:
|
||||
op_found = self._load_option(key)
|
||||
if return_obj:
|
||||
return op_found
|
||||
return op_found.value
|
||||
op_found = self.options.get(key) or self._load_option(key)
|
||||
return op_found if return_obj else op_found.value
|
||||
|
||||
def set(self, key, value, **kwargs):
|
||||
"""
|
||||
Change an individual option.
|
||||
|
||||
Args:
|
||||
key (str): The key of an option that can be changed. Allows partial matching.
|
||||
value (str): The value that should be checked, coerced, and stored.:
|
||||
kwargs (any, optional): These are passed into the Option's validation function,
|
||||
save function and display function and allows to customize either.
|
||||
|
||||
Returns:
|
||||
value (any): Value stored in option, after validation.
|
||||
|
||||
"""
|
||||
if not key:
|
||||
raise ValueError("Option field blank!")
|
||||
match = string_partial_matching(list(self.options_dict.keys()), key, ret_index=False)
|
||||
if not match:
|
||||
raise ValueError("Option not found!")
|
||||
if len(match) > 1:
|
||||
raise ValueError(f"Multiple matches: {', '.join(match)}. Please be more specific.")
|
||||
match = match[0]
|
||||
op = self.get(match, return_obj=True)
|
||||
op.set(value, **kwargs)
|
||||
return op.value
|
||||
|
||||
def all(self, return_objs=False):
|
||||
"""
|
||||
|
|
@ -80,45 +145,3 @@ class OptionHandler(object):
|
|||
|
||||
"""
|
||||
return [self.get(key, return_obj=return_objs) for key in self.options_dict]
|
||||
|
||||
def _load_option(self, key):
|
||||
"""
|
||||
Loads option on-demand if it has not been loaded yet.
|
||||
|
||||
Args:
|
||||
key (str): The option being loaded.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
desc, clsname, default_val = self.options_dict[key]
|
||||
save_data = self.save_data.get(key, None)
|
||||
self.obj.msg(save_data)
|
||||
loaded_option = OPTION_CLASSES.get(clsname)(self, key, desc, default_val, save_data)
|
||||
self.options[key] = loaded_option
|
||||
return loaded_option
|
||||
|
||||
def set(self, option, value, **kwargs):
|
||||
"""
|
||||
Change an individual option.
|
||||
|
||||
Args:
|
||||
option (str): The key of an option that can be changed. Allows partial matching.
|
||||
value (str): The value that should be checked, coerced, and stored.
|
||||
kwargs (any, optional): These are passed into the Option's validation function,
|
||||
save function and display function and allows to customize either.
|
||||
|
||||
Returns:
|
||||
New value
|
||||
"""
|
||||
if not option:
|
||||
raise ValueError("Option field blank!")
|
||||
found = string_partial_matching(list(self.options_dict.keys()), option, ret_index=False)
|
||||
if not found:
|
||||
raise ValueError("Option not found!")
|
||||
if len(found) > 1:
|
||||
raise ValueError(f"That matched: {', '.join(found)}. Please be more specific.")
|
||||
found = found[0]
|
||||
op = self.get(found, return_obj=True)
|
||||
op.set(value, **kwargs)
|
||||
return op.display(**kwargs)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue