Cleanups. Containers created and BaseOption done better.

This commit is contained in:
Andrew Bastien 2019-04-11 21:06:15 -04:00
parent 5bc9a42bb5
commit d96cf3b809
9 changed files with 159 additions and 234 deletions

View file

@ -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):
"""

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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'

View file

@ -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()

View file

@ -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()

View file

@ -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(' ')