From bfe533441e438117f051b8c1e86d7b200c1cbb6b Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 20 Jan 2020 23:20:05 +0100 Subject: [PATCH] Make options accept default kwarg, add new raise_exception. Fix datetime validatorfunc accoring to spec. Resolves #1967. --- CHANGELOG.md | 2 ++ evennia/accounts/models.py | 2 +- evennia/utils/optionhandler.py | 14 +++++++---- evennia/utils/tests/test_validatorfuncs.py | 6 +++++ evennia/utils/validatorfuncs.py | 28 +++++++++++++++------- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d13cb583d3..36bc4121bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ without arguments starts a full interactive Python console. - Improve performance of `find` and `objects` commands on large data sets (strikaco) - New `CHANNEL_HANDLER_CLASS` setting allows for replacing the ChannelHandler entirely. - Made `py` interactive mode support regular quit() and more verbose. +- Made `Account.options.get` accept `default=None` kwarg to mimic other uses of get. Set + the new `raise_exception` boolean if ranting to raise KeyError on a missing key. ## Evennia 0.9 (2018-2019) diff --git a/evennia/accounts/models.py b/evennia/accounts/models.py index 06d46d9777..53ba05ea78 100644 --- a/evennia/accounts/models.py +++ b/evennia/accounts/models.py @@ -105,9 +105,9 @@ class AccountDB(TypedObject, AbstractUser): objects = AccountDBManager() # defaults - __settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS __defaultclasspath__ = "evennia.accounts.accounts.DefaultAccount" __applabel__ = "accounts" + __settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS class Meta(object): verbose_name = "Account" diff --git a/evennia/utils/optionhandler.py b/evennia/utils/optionhandler.py index fa59c9fc8d..507785d1e4 100644 --- a/evennia/utils/optionhandler.py +++ b/evennia/utils/optionhandler.py @@ -24,8 +24,7 @@ class InMemorySaveHandler(object): class OptionHandler(object): """ - This is a generic Option handler. It is commonly used - implements AttributeHandler. Retrieve options eithers as properties on + This is a generic Option handler. Retrieve options either 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 @@ -54,7 +53,7 @@ class OptionHandler(object): 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)`. + 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. @@ -116,14 +115,16 @@ class OptionHandler(object): self.options[key] = loaded_option return loaded_option - def get(self, key, return_obj=False): + def get(self, key, default=None, return_obj=False, raise_error=False): """ Retrieves an Option stored in the handler. Will load it if it doesn't exist. Args: key (str): The option key to retrieve. + default (any): What to return if the option is defined. return_obj (bool, optional): If True, returns the actual option object instead of its value. + raise_error (bool, optional): Raise Exception if key is not found in options. Returns: option_value (any or Option): An option value the Option itself. Raises: @@ -131,7 +132,10 @@ class OptionHandler(object): """ if key not in self.options_dict: - raise KeyError("Option not found!") + if raise_error: + raise KeyError("Option not found!") + return default + # get the options or load/recache it op_found = self.options.get(key) or self._load_option(key) return op_found if return_obj else op_found.value diff --git a/evennia/utils/tests/test_validatorfuncs.py b/evennia/utils/tests/test_validatorfuncs.py index 54823bc4ee..e4235974a4 100644 --- a/evennia/utils/tests/test_validatorfuncs.py +++ b/evennia/utils/tests/test_validatorfuncs.py @@ -32,6 +32,11 @@ class TestValidatorFuncs(TestCase): self.assertTrue( isinstance(validatorfuncs.datetime(dt, from_tz=pytz.UTC), datetime.datetime) ) + account = mock.MagicMock() + account.options.get = mock.MagicMock(return_value="America/Chicago") + expected = datetime.datetime(1492, 10, 12, 6, 51, tzinfo=pytz.UTC) + self.assertEqual(expected, validatorfuncs.datetime("Oct 12 1:00 1492", account=account)) + account.options.get.assert_called_with("timezone", "UTC") def test_datetime_raises_ValueError(self): for dt in ["", "January 1, 2019", "1/1/2019", "Jan 1 2019"]: @@ -121,6 +126,7 @@ class TestValidatorFuncs(TestCase): validatorfuncs.boolean(b) def test_timezone_ok(self): + for tz in ["America/Chicago", "GMT", "UTC"]: self.assertEqual(tz, validatorfuncs.timezone(tz).zone) diff --git a/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py index 623680b176..bca25874cb 100644 --- a/evennia/utils/validatorfuncs.py +++ b/evennia/utils/validatorfuncs.py @@ -40,24 +40,36 @@ def color(entry, option_key="Color", **kwargs): def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs): """ - Process a datetime string in standard forms while accounting for the inputter's timezone. + Process a datetime string in standard forms while accounting for the inputer's timezone. Always + returns a result in UTC. Args: entry (str): A date string from a user. option_key (str): Name to display this datetime as. - account (AccountDB): The Account performing this lookup. Unless from_tz is provided, - account's timezone will be used (if found) for local time and convert the results - to UTC. - from_tz (pytz): An instance of pytz from the user. If not provided, defaults to whatever - the Account uses. If neither one is provided, defaults to UTC. - + account (AccountDB): The Account performing this lookup. Unless `from_tz` is provided, + the account's timezone option will be used. + from_tz (pytz.timezone): An instance of a pytz timezone object from the + user. If not provided, tries to use the timezone option of the `account'. + If neither one is provided, defaults to UTC. Returns: - datetime in utc. + datetime in UTC. + Raises: + ValueError: If encountering a malformed timezone, date string or other format error. + """ if not entry: raise ValueError(f"No {option_key} entered!") if not from_tz: from_tz = _pytz.UTC + if account: + acct_tz = account.options.get("timezone", "UTC") + try: + from_tz = _pytz.timezone(acct_tz) + except Exception as err: + raise ValueError(f"Timezone string '{acct_tz}' is not a valid timezone ({err})") + else: + from_tz = _pytz.UTC + utc = _pytz.UTC now = _dt.datetime.utcnow().replace(tzinfo=utc) cur_year = now.strftime("%Y")