Source code for pyplanet.apps.config

import inspect
import importlib
import logging
import os

from pyplanet.core.exceptions import ImproperlyConfigured, InvalidAppModule


[docs]class _AppContext: """ The app context holds instances of core/contrib components that must be managed on a per app base. Such as the UI registration and distribution. """ def __init__(self, app): """ Initiate the App Context. Used by several core and contribs to have it's own manager instance per app. You should always use the managers of your local app at first! :param app: App Config instance. :type app: pyplanet.apps.config.AppConfig """ self.ui = app.instance.ui_manager.create_app_manager(app) """ UI Component. See :doc:`UI Classes </api/core_ui>`. """ self.setting = app.instance.setting_manager.create_app_manager(app) """ Setting Contrib Component. See :doc:`Setting Classes </api/contrib_setting>`. """ self.signals = app.instance.signals.create_app_manager(app) """ Signal manager. See :doc:`Signal Manager </api/core_events>`. """ async def on_destroy(self): await self.ui.on_destroy() await self.signals.on_destroy()
class AppState: UNLOADED = 0 LOADED = 1
[docs]class AppConfig: """ This class is the base class for the Applications metadata class. The class holds information and hooks that will be executed after initiation for example. .. code-block:: python class MyApp(AppConfig): async def on_start(self): print('we are staring!!') """ name = None label = None human_name = None path = None app_dependencies = None """ You can provide a list of dependencies to other apps (each entry needs to be a string of the app label!) """ mode_dependencies = None """ You can provide a list of gamemodes that are required to activate the app. Gamemodes needs to be declared as script names. You can override this behaviour by defining the following method in your config class .. code-block :: python def is_mode_supported(self, mode): return mode.lower().startswith('TimeAttack') """ game_dependencies = None """ You can provide a list of game dependencies that needs to meet when the app is started. For example you can provide: .. code-block :: python game_dependencies = ['trackmania'] You can override this behaviour by defining the following method in your config class .. code-block :: python def is_game_supported(self, game): return game != 'questmania' """ def __init__(self, app_name, app_module, instance): """ Init app config. :param app_name: App Name (from module path). :param app_module: App Module. :param instance: Instance of controller :type app_name: str :type app_module: str :type instance: pyplanet.core.instance.Instance """ # The full python module path. The postfix `*.app` is always the same! # Example: pyplanet.contrib.apps.games.trackmania.app self.name = app_name # The apps root module. # Example: pyplanet.contrib.apps.games.trackmania self.module = app_module # The apps registry will be injected into the app config. self.apps = None # Make sure we give the core attribute the default value of false. This indicates if it's an internally # module. self.core = getattr(self, 'core', False) # The label can be given by the module, or automatically determinated on the last component. if not hasattr(self, 'label') or getattr(self, 'label', None) is None: self.label = app_name.rpartition('.')[2] # If the module is a core contrib module, we give the label a prefix (contrib.app). if self.core is True: self.label = 'core.{}'.format(self.label) # Human-readable name for the application eg. `MyApp`. if not hasattr(self, 'human_name') or getattr(self, 'human_name', None) is None: self.human_name = self.label.title() # Filesystem path to the application directory eg. if not hasattr(self, 'path') or getattr(self, 'path') is None: self.path = self._path_from_module(app_module) # The instance and related app context managers. self.instance = instance self.context = _AppContext(self) # State of app. self.state = AppState.UNLOADED def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.label) ################################################################################################### # Lifecycle Methods ###################################################################################################
[docs] async def on_init(self): """ The on_init will be called before all apps are started (just before the on_ready). This will allow the app to register things like commands, permissions and other things that are important and don't require other apps to be ready. """
[docs] async def on_start(self): """ The on_start call is being called after all apps has been started successfully. You should register any stuff that is related to any other apps and signals like your `self` context for signals if they are classmethods. """ # Deprecated: Fix the deprecated method if hasattr(self, 'on_ready'): logging.warning('on_ready is deprecated, use on_start instead! app: {}'.format(self.label)) await self.on_ready()
[docs] async def on_stop(self): """ The on_stop will be called before stopping the app. """ pass
[docs] async def on_destroy(self): """ On destroy is being called when unloading the app from the memory. """ await self.context.on_destroy()
###################################################################################################
[docs] def is_mode_supported(self, mode): if self.mode_dependencies: for mode_requirement in self.mode_dependencies: if mode_requirement.lower() in mode.lower(): return True return False return True
[docs] def is_game_supported(self, game): if self.game_dependencies: return game in self.game_dependencies return True
def _path_from_module(self, module): """Attempt to determine app's filesystem path from its module.""" paths = list(getattr(module, '__path__', [])) if len(paths) != 1: filename = getattr(module, '__file__', None) if filename is not None: paths = [os.path.dirname(filename)] else: # Can be bugged for unknown reasons. paths = list(set(paths)) if len(paths) > 1: raise ImproperlyConfigured( 'The app module {} has multiple filesystem locations {}; ' 'you must configure this app with an AppConfig subclass ' 'with a \'path\' class attribute.'.format(module, paths)) elif not paths: raise ImproperlyConfigured( 'The app module {} has no filesystem location, ' 'you must configure this app with an AppConfig subclass ' 'with a \'path\' class attribute.'.format(module)) return paths[0]
[docs] @staticmethod def import_app(entry, instance): # Import the module, we need to strip down the path into namespace, file and class. module_path, app_glue, cls_name = entry.rpartition('.') # The app name (full module path to module, not the app!) app_name = module_path.rpartition('.')[0] if not module_path: raise ImproperlyConfigured('Module for your app {} can\'t be found!'.format(entry)) # The new style definitions works a bit different. We just import the module, search for the first class that is # a subclass of AppConfig. if cls_name.islower(): module_path += '.' + cls_name app_name = module_path cls_name = None # Try to load the app module, containing the class. try: module = importlib.import_module(module_path) # If we have a new-style module, check for the first AppConfig extending class in our module. # See #109. if cls_name is None: for name, obj in inspect.getmembers(module): if inspect.isclass(obj) and issubclass(obj, AppConfig) and obj.__name__ != 'AppConfig': cls_name = obj.__name__ break module = getattr(module, cls_name) except TypeError as e: raise ImproperlyConfigured( 'Can\'t load the app {}. Can\'t find the app config! ' 'Check your apps.py and if your app is installed correctly'.format(entry) ) from e except ImportError: raise ImproperlyConfigured( 'Can\'t load the app {}. Can\'t find the app config!'.format(entry) ) except AttributeError as e: raise ImproperlyConfigured( 'Can\'t load the app {}. Can\'t load the app class!'.format(entry) ) from e # Last check if subclass of appconfig. if not issubclass(module, AppConfig): raise InvalidAppModule('Your required app {} couldn\'t be loaded!'.format(entry)) # Ensure app_name points to a valid module. try: app_module = importlib.import_module(app_name) except ImportError: raise ImproperlyConfigured( 'Can\'t import {}. Check that \'{}.{}.name\' is correct.'.format( app_name, module_path, cls_name ) ) return module(app_name, app_module, instance)