Source code for pyplanet.contrib.setting.setting

import json
from asyncio import iscoroutinefunction

from pyplanet.apps.core.pyplanet.models.setting import Setting as SettingModel
from pyplanet.contrib.setting.exceptions import TypeUnknownException, SerializationException, SettingException

[docs]class Setting: """ The setting class is for defining a setting for the end-user. This setting can be changed with /settings and //settings. With this class you can define or manage your setting that is going to be public for all other apps and end-user. You can get notified of changes with the ``change_target`` in the init of this class. Point this to a method (async or sync) with the following params: ``old_value`` and ``new_value``. Example: .. code-block:: python my_setting = Setting( 'dedimania_code', 'Dedimania Server Code', Setting.CAT_KEYS, type=str, description='The secret dedimania code. Get one at $l', default=None ) my_other_setting = Setting( 'sample_boolean', 'Booleans for the win!', Setting.CAT_BEHAVIOUR, type=bool, description='Example', ) """ CAT_GENERAL = 'General' CAT_KEYS = 'Keys' CAT_DESIGN = 'Design' CAT_POSITION = 'Position' CAT_BEHAVIOUR = 'Behaviour' CAT_FEATURES = 'Features' CAT_OTHER = 'Other' ALL_CATEGORIES = [CAT_GENERAL, CAT_DESIGN, CAT_POSITION, CAT_BEHAVIOUR, CAT_FEATURES, CAT_KEYS, CAT_OTHER]
[docs] def __init__( self, key: str, name: str, category: str, type=str, description: str = None, choices=None, default=None, change_target=None ): """ Create setting with properties. :param key: Key of setting, this is mainly only used for the backend and for referencing the setting. You should keep this unique in your app! :param name: Name of the setting that will be displayed as a small label to the player. :param category: Category from Categories.*. Must be provided! :param type: Type of value to expect, use python types here. str by default. :param description: Description to provide help and instructions to the player. :param choices: List or tuple with choices, only when wanting to restrict values to selected options. :param default: Default value if not provided from database. This will be returned. Defaults to None. :param change_target: Target method to call when the setting value has been changed. """ if category not in self.ALL_CATEGORIES: raise SettingException('Invalid category. Must be an category in the Categories static class.') # Prepare property for app specific setting. Will be injected by the register command. self.app_label = None self.key = key = name self.description = description self.category = category self.default = default self.type = type self.choices = choices self.change_target = change_target # Prepare the model instance here. This will be filled once it's fetched for the first time (or inited). self._instance = None self._value = (False, None)
[docs] async def initiate_setting(self): """ Initiate database record for setting. """ return await SettingModel.get_or_create_from_info( key=self.key, app=self.app_label, category=self.category,, description=self.description, value=None )
[docs] def unserialize_value(self, value): """ Unserialize the datastorage value to the python value, based on the type of the setting. :param value: Value from database. :return: Python value. :raise pyplanet.contrib.setting.exceptions.SerializationException: SerializationException """ if value is None: return self.default try: if self.type == str: return str(value) elif self.type == int: return int(value) elif self.type == float: return float(value) elif self.type == bool: return bool(value) elif self.type == list or self.type == set or self.type == dict: return json.loads(value) else: raise TypeUnknownException('The type \'{}\' is unknown!'.format(self.type)) except TypeUnknownException: raise except Exception as e: raise SerializationException('Error with unserialization of the setting \'{}\''.format(str(self))) from e
[docs] def serialize_value(self, value): """ Serialize the python value to the data store value, based on the type of the setting. :param value: Python Value. :return: Database Value """ # Always set to Null, so we get the default value back. if value is None: return value # Empty value, set the default. if value == '': return self.default if self.choices and value not in self.choices: raise SerializationException('Value given is not in the predefined choices!') try: if self.type == int: value = int(value) elif self.type == float: value = float(value) elif self.type == bool: if value == '1' or value == 1 or value == '0' or value == 0: value = bool(int(value)) except: pass if type(value) != self.type: raise SerializationException( 'Your given value is not of the type you specified! \'{}\' != \'{}\''.format(type(value), self.type) ) if self.type == list or self.type == set or self.type == dict: return json.dumps(value) if self.type == bool: return value return str(value)
@property def type_name(self): """ Get the name of the specified type in string format, suited for displaying to end-user. :return: User friendly name of type. """ if self.type == str: return 'string' elif self.type == int: return 'integer' elif self.type == float: return 'float' elif self.type == bool: return 'boolean' elif self.type == list or self.type == set: return 'list' elif self.type == dict: return 'dict' else: return 'unknown'
[docs] async def get_value(self, refresh=False): """ Get the value or the default value for the setting model. :param refresh: Force a refresh of the value. :return: Value in the desired type and unserialized from database/storage. :raise: NotFound / SerializationException """ if not self._value[0] or refresh is True: model = await self.get_model() self._value = (True, self.unserialize_value(model.value)) return self._value[1]
[docs] async def set_value(self, value): """ Set the value, this will serialize and save the setting to the data storage. :param value: Python value input. :raise: NotFound / SerializationException """ old_value = self._value[0] if self._value and len(self._value) > 0 else None model = await self.get_model() model.value = self.serialize_value(value) self._value = (True, model.value) await # Call the change target. if self.change_target and callable(self.change_target): if iscoroutinefunction(self.change_target): await self.change_target(old_value, model.value) else: self.change_target(old_value, model.value)
[docs] async def clear(self): """ Clear the value in the data storage. This will set the value to None, and will return the default value on request of data. :raise: NotFound / SerializationException """ return await self.set_value(None)
[docs] async def get_model(self): """ Get the model for the setting. This will return the model instance or raise an exception when not found. :return: Model instance :raise: NotFound """ return await SettingModel.get(key=self.key, app=self.app_label)
[docs] def __str__(self): return