From d96cf3b809cee9e0a56412e4d2c610ad9b3f3608 Mon Sep 17 00:00:00 2001 From: Andrew Bastien Date: Thu, 11 Apr 2019 21:06:15 -0400 Subject: [PATCH] Cleanups. Containers created and BaseOption done better. --- evennia/accounts/accounts.py | 8 +- evennia/accounts/styles.py | 36 ----- evennia/commands/default/account.py | 4 +- evennia/settings_default.py | 6 +- evennia/utils/containers.py | 47 ++++++ evennia/utils/opclasses.py | 223 ++++++++++++---------------- evennia/utils/option.py | 40 ++--- evennia/utils/valid.py | 25 ---- evennia/utils/validfuncs.py | 4 +- 9 files changed, 159 insertions(+), 234 deletions(-) delete mode 100644 evennia/accounts/styles.py create mode 100644 evennia/utils/containers.py delete mode 100644 evennia/utils/valid.py diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 35a6e76c53..f7ec54aeed 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -198,6 +198,10 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): def sessions(self): return AccountSessionHandler(self) + @lazy_property + def options(self): + return OptionHandler(self, options_dict=settings.OPTIONS_ACCOUNT_DEFAULT, save_category='option') + # Do not make this a lazy property; the web UI will not refresh it! @property def characters(self): @@ -1384,10 +1388,6 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68) return look_string - @lazy_property - def options(self): - return OptionHandler(self, options_dict=settings.ACCOUNT_OPTIONS, save_category='option') - class DefaultGuest(DefaultAccount): """ diff --git a/evennia/accounts/styles.py b/evennia/accounts/styles.py deleted file mode 100644 index a91814fefa..0000000000 --- a/evennia/accounts/styles.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Styles (playing off CSS) are a way to change the colors and symbols used for standardized -displays used in Evennia. Accounts all have a StyleHandler accessible via .style which -retrieves per-Account settings, falling back to the global settings contained in settings.py. - -""" -from django.conf import settings - - -class StyleHandler(object): - category = 'style' - - def __init__(self, acc): - self.acc = acc - - def set(self, option, value): - pass - - def get(self, option): - """ - Get the stored Style information from this Account's Attributes if possible. - If not, fallback to the Global. - - Args: - option (str): The key of the Style to retrieve. - - Returns: - String or None - """ - stored = self.acc.attributes.get(option, category=self.category) - if stored: - return stored - default = settings.DEFAULT_STYLES.get(option, None) - if default: - return default[2] - return None diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index 151961ab7a..bf9767d893 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -886,12 +886,12 @@ class CmdStyle(COMMAND_DEFAULT_CLASS): styles_table = self.style_table('Option', 'Description', 'Type', 'Value', width=78) for op_key in self.account.options.options_dict.keys(): op_found = self.account.options.get(op_key, return_obj=True) - styles_table.add_row(op_key, op_found.description, op_found.expect_type, op_found.display()) + styles_table.add_row(op_key, op_found.description, op_found.__class__.__name__, op_found.display()) self.msg(str(styles_table)) def set(self): try: - result = self.account.options.set(self.lhs, self.rhs, account=self.account) + result = self.account.options.set(self.lhs, self.rhs) except ValueError as e: self.msg(str(e)) return diff --git a/evennia/settings_default.py b/evennia/settings_default.py index d7c2a55eb3..fd4a535c48 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -500,7 +500,7 @@ TYPECLASS_AGGRESSIVE_CACHE = True # Option tuples are in this format: # ("Description", 'Option Class', 'Default Value') -ACCOUNT_OPTIONS = { +OPTIONS_ACCOUNT_DEFAULT = { 'border_color': ('Headers, footers, table borders, etc.', 'Color', 'M'), 'header_star_color': ('* inside Header lines.', 'Color', 'm'), 'header_text_color': ('Text inside Header lines.', 'Color', 'w'), @@ -565,11 +565,11 @@ PROTOTYPEFUNC_MODULES = ["evennia.utils.prototypefuncs", # Module holding validator functions. functions in later modules will # override those in earlier ones. -VALIDFUNC_MODULES = ['evennia.utils.validfuncs', ] +VALIDATOR_MODULES = ['evennia.utils.validfuncs', ] # Modules holding Option classes. Those in later modules will # override ones in earlier modules. -OPTIONCLASS_MODULES = ['evennia.utils.opclasses', ] +OPTION_MODULES = ['evennia.utils.opclasses', ] ###################################################################### # Default Account setup and access diff --git a/evennia/utils/containers.py b/evennia/utils/containers.py new file mode 100644 index 0000000000..9d1d0e0deb --- /dev/null +++ b/evennia/utils/containers.py @@ -0,0 +1,47 @@ +from django.conf import settings +from evennia.utils.utils import callables_from_module + + +class ValidContainer(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_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. +VALID_CONTAINER = ValidContainer() + + +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_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 Options. +OPTION_CONTAINER = OptionContainer() diff --git a/evennia/utils/opclasses.py b/evennia/utils/opclasses.py index 041675df14..0304feeab8 100644 --- a/evennia/utils/opclasses.py +++ b/evennia/utils/opclasses.py @@ -1,10 +1,11 @@ import datetime as _dt +from evennia import logger as _log from evennia.utils.ansi import ANSIString as _ANSI from evennia.utils.validfuncs import _TZ_DICT -from evennia.utils.valid import VALID_HANDLER as _VAL +from evennia.utils.containers import VALID_CONTAINER as _VAL -class _BaseOption(object): +class BaseOption(object): """ Abstract Class to deal with encapsulating individual Options. An Option has a name/key, a description to display in relevant commands and menus, and a default value. It saves to the owner's Attributes using @@ -17,9 +18,7 @@ class _BaseOption(object): valid: Shortcut to the loaded VALID_HANDLER. valid_type (str): The key of the Validator this uses. """ - expect_type = '' - valid = _VAL - valid_type = '' + validator_key = '' def __str__(self): return self.key @@ -47,7 +46,16 @@ class _BaseOption(object): # And it's not loaded until it's called upon to spit out its contents. self.loaded = False - def load(self): + def display(self, **kwargs): + """ + Renders the Option's value as something pretty to look at. + + Returns: + How the stored value should be projected to users. a raw timedelta is pretty ugly, y'know? + """ + return self.value + + def _load(self): """ Takes the provided save data, validates it, and gets this Option ready to use. @@ -56,17 +64,23 @@ class _BaseOption(object): """ if self.save_data is not None: try: - self.value_storage = self.valid_save(self.save_data) + self.value_storage = self.deserialize(self.save_data) self.loaded = True return True except Exception as e: - print(e) # need some kind of error message here! + _log.log_trace(e) return False - def customized(self): - return self.value_storage != self.default_value + def _save(self): + """ + Exports the current value to an Attribute. - def valid_save(self, save_data): + Returns: + None + """ + self.handler.obj.attributes.add(self.key, category=self.handler.save_category, value=self.serialize()) + + def deserialize(self, save_data): """ Perform sanity-checking on the save data. This isn't the same as Validators, as Validators deal with user input. save data might be a timedelta or a list or some other object. isinstance() is probably @@ -81,16 +95,18 @@ class _BaseOption(object): """ return save_data - def clear(self): + def serialize(self): """ - Resets this Option to default settings. + Serializes the save data for Attribute storage if it's something complicated. Returns: - self. Why? + Whatever best handles the Attribute. """ - self.value_storage = None - self.loaded = False - return self + return self.value_storage + + @property + def changed(self): + return self.value_storage != self.default_value @property def default(self): @@ -99,13 +115,30 @@ class _BaseOption(object): @property def value(self): if not self.loaded and self.save_data is not None: - self.load() + self._load() if self.loaded: return self.value_storage else: return self.default - def validate(self, value, account): + @value.setter + def value(self, value): + """ + Takes user input, presumed to be a string, and changes the value if it is a valid input. + + Args: + value: + account: + + Returns: + None + """ + final_value = self.validate(value) + self.value_storage = final_value + self.loaded = True + self._save() + + def validate(self, value): """ Validate user input, which is presumed to be a string. @@ -118,202 +151,128 @@ class _BaseOption(object): Returns: The results of a Validator call. Might be any kind of python object. """ - return self.do_validate(value, account) - - def do_validate(self, value, account): - """ - Second layer of abstraction on validation due to design choices. - - Args: - value: - account: - - Returns: - - """ - return self.valid[self.valid_type](value, thing_name=self.key, account=account) - - def set(self, value, account): - """ - Takes user input, presumed to be a string, and changes the value if it is a valid input. - - Args: - value: - account: - - Returns: - - """ - final_value = self.validate(value, account) - self.value_storage = final_value - self.loaded = True - self.save() - return self.display() - - def display(self): - """ - Renders the Option's value as something pretty to look at. - - Returns: - How the stored value should be projected to users. a raw timedelta is pretty ugly, y'know? - """ - return self.value - - def export(self): - """ - Serializes the save data for Attribute storage if it's something complicated. - - Returns: - Whatever best handles the Attribute. - """ - return self.value_storage - - def save(self): - """ - Exports the current value to an Attribute. - - Returns: - None - """ - self.handler.obj.attributes.add(self.key, category=self.handler.save_category, value=self.export()) + return _VAL[self.validator_key](value, thing_name=self.key) -class Text(_BaseOption): - expect_type = 'Text' - valid_type = 'text' +class Text(BaseOption): + validator_key = 'text' - def do_validate(self, value, account): - if not str(value): - raise ValueError("Must enter some text!") - return str(value) - - def valid_save(self, save_data): + def deserialize(self, save_data): got_data = str(save_data) if not got_data: raise ValueError(f"{self.key} expected Text data, got '{save_data}'") return got_data -class Email(_BaseOption): - expect_type = 'Email' - valid_type = 'email' +class Email(BaseOption): + validator_key = 'email' - def valid_save(self, save_data): + def deserialize(self, save_data): got_data = str(save_data) if not got_data: raise ValueError(f"{self.key} expected String data, got '{save_data}'") return got_data -class Boolean(_BaseOption): - expect_type = 'Boolean' - valid_type = 'boolean' +class Boolean(BaseOption): + validator_key = 'boolean' - def display(self): + def display(self, **kwargs): if self.value: return '1 - On/True' return '0 - Off/False' - def export(self): + def serialize(self): return self.value - def valid_save(self, save_data): + def deserialize(self, save_data): if not isinstance(save_data, bool): raise ValueError(f"{self.key} expected Boolean, got '{save_data}'") return save_data -class Color(_BaseOption): - expect_type = 'Color' - valid_type = 'color' +class Color(BaseOption): + validator_key = 'color' - def display(self): + def display(self, **kwargs): return f'{self.value} - |{self.value}this|n' - def valid_save(self, save_data): + def deserialize(self, save_data): if not save_data or len(_ANSI(f'|{save_data}|n')) > 0: raise ValueError(f"{self.key} expected Color Code, got '{save_data}'") return save_data -class Timezone(_BaseOption): - expect_type = 'Timezone' - valid_type = 'timezone' +class Timezone(BaseOption): + validator_key = 'timezone' @property def default(self): return _TZ_DICT[self.default_value] - def valid_save(self, save_data): + def deserialize(self, save_data): if save_data not in _TZ_DICT: raise ValueError(f"{self.key} expected Timezone Data, got '{save_data}'") return _TZ_DICT[save_data] - def export(self): + def serialize(self): return str(self.value_storage) -class UnsignedInteger(_BaseOption): - expect_type = 'Whole Number 0+' - valid_type = 'unsigned_integer' +class UnsignedInteger(BaseOption): + validator_key = 'unsigned_integer' - def valid_save(self, save_data): + def deserialize(self, save_data): if isinstance(save_data, int) and save_data >= 0: return save_data raise ValueError(f"{self.key} expected Whole Number 0+, got '{save_data}'") -class SignedInteger(_BaseOption): - expect_type = 'Whole Number' - valid_type = 'signed_integer' +class SignedInteger(BaseOption): + validator_key = 'signed_integer' - def valid_save(self, save_data): + def deserialize(self, save_data): if isinstance(save_data, int): return save_data raise ValueError(f"{self.key} expected Whole Number, got '{save_data}'") -class PositiveInteger(_BaseOption): - expect_type = 'Whole Number 1+' - valid_type = 'positive_integer' +class PositiveInteger(BaseOption): + validator_key = 'positive_integer' - def valid_save(self, save_data): + def deserialize(self, save_data): if isinstance(save_data, int) and save_data > 0: return save_data raise ValueError(f"{self.key} expected Whole Number 1+, got '{save_data}'") -class Duration(_BaseOption): - expect_type = 'Duration' - valid_type = 'duration' +class Duration(BaseOption): + validator_key = 'duration' - def valid_save(self, save_data): + def deserialize(self, save_data): if isinstance(save_data, int): return _dt.timedelta(0, save_data, 0, 0, 0, 0, 0) raise ValueError(f"{self.key} expected Timedelta in seconds, got '{save_data}'") - def export(self): + def serialize(self): return self.value_storage.seconds -class Datetime(_BaseOption): - expect_type = 'Datetime' - valid_type = 'datetime' +class Datetime(BaseOption): + validator_key = 'datetime' - def valid_save(self, save_data): + def deserialize(self, save_data): if isinstance(save_data, int): return _dt.datetime.utcfromtimestamp(save_data) raise ValueError(f"{self.key} expected UTC Datetime in EPOCH format, got '{save_data}'") - def export(self): + def serialize(self): return int(self.value_storage.strftime('%s')) class Future(Datetime): - expect_type = 'Future Datetime' - valid_type = 'future' + validator_key = 'future' class Lock(Text): - expect_type = 'Lock String' - valid_type = 'lock' + validator_key = 'lock' diff --git a/evennia/utils/option.py b/evennia/utils/option.py index 2d54c0f51b..099e09be9d 100644 --- a/evennia/utils/option.py +++ b/evennia/utils/option.py @@ -1,27 +1,5 @@ -from django.conf import settings -from evennia.utils.utils import string_partial_matching, callables_from_module - - -class OptionManager(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.OPTIONCLASS_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 Options. -OPTION_MANAGER = OptionManager() +from evennia.utils.utils import string_partial_matching +from evennia.utils.containers import OPTION_CONTAINER class OptionHandler(object): @@ -58,7 +36,7 @@ class OptionHandler(object): # 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} + return_list=True, return_obj=True) if s} def __getitem__(self, item): return self.get(item).value @@ -78,19 +56,17 @@ class OptionHandler(object): option_def = self.options_dict[key] save_data = self.save_data.get(key, None) self.obj.msg(save_data) - loaded_option = OPTION_MANAGER[option_def[1]](self, key, option_def[0], option_def[2], save_data) + loaded_option = OPTION_CONTAINER[option_def[1]](self, key, option_def[0], option_def[2], save_data) self.options[key] = loaded_option return loaded_option - def set(self, option, value, account): + def set(self, option, value): """ 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. - account (AccountDB): The Account performing the setting. Necessary due to other - option lookups like timezone. Returns: New value @@ -104,4 +80,8 @@ class OptionHandler(object): raise ValueError(f"That matched: {', '.join(found)}. Please be more specific.") found = found[0] op = self.get(found, return_obj=True) - return op.set(value, account) + op.value = value + return op.display() + + + diff --git a/evennia/utils/valid.py b/evennia/utils/valid.py deleted file mode 100644 index 9ac80ab452..0000000000 --- a/evennia/utils/valid.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.conf import settings -from evennia.utils.utils import callables_from_module - - -class ValidHandler(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.VALIDFUNC_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. -VALID_HANDLER = ValidHandler() diff --git a/evennia/utils/validfuncs.py b/evennia/utils/validfuncs.py index 08d1d6f23e..77ad4cf288 100644 --- a/evennia/utils/validfuncs.py +++ b/evennia/utils/validfuncs.py @@ -47,8 +47,8 @@ def datetime(entry, thing_name='Datetime', account=None, from_tz=None, **kwargs) if not entry: raise ValueError(f"No {thing_name} entered!") if not from_tz: - from_tz = _pytz['UTC'] - utc = _pytz['UTC'] + from_tz = _pytz.UTC + utc = _pytz.UTC now = _dt.datetime.utcnow().replace(tzinfo=utc) cur_year = now.strftime('%Y') split_time = entry.split(' ')