def verify_plugin_settings(self): puts("Verifying settings requested by plugins...") missing_settings = False missing_setting_error_messages = [] with indent(2): for name, meta in self.required_settings_from_plugins.items(): if not hasattr(settings, name): error_message = ( "%(setting_name)s is missing. It's required by the" "%(plugin_name)s plugin's '%(function_name)s' method." ) % meta puts(colored.red("✗ %(setting_name)s" % meta)) missing_setting_error_messages.append(error_message) missing_settings = True else: show_valid("%(setting_name)s" % meta) if missing_settings: puts("") warn( "Will is missing settings required by some plugins. " "He's starting up anyway, but you will run into errors" " if you try to use those plugins!" ) self.add_startup_error("\n".join(missing_setting_error_messages)) else: puts("")
def __init__(self, **kwargs): if "template_dirs" in kwargs: warn("template_dirs is now depreciated") if "plugin_dirs" in kwargs: warn("plugin_dirs is now depreciated") log_level = getattr(settings, 'LOGLEVEL', logging.ERROR) logging.basicConfig( level=log_level, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', ) # Bootstrap exit code. self.exiting = False # Find all the PLUGINS modules try: plugins = settings.PLUGINS self.plugins_dirs = {} except: # We're missing settings. They handle that. sys.exit(1) # Set template dirs. full_path_template_dirs = [] for t in settings.TEMPLATE_DIRS: full_path_template_dirs.append(os.path.abspath(t)) # Add will's templates_root if TEMPLATES_ROOT not in full_path_template_dirs: full_path_template_dirs += [TEMPLATES_ROOT, ] # Add this project's templates_root if PROJECT_TEMPLATE_ROOT not in full_path_template_dirs: full_path_template_dirs += [PROJECT_TEMPLATE_ROOT, ] # Convert those to dirs for plugin in plugins: path_name = None for mod in plugin.split('.'): if path_name is not None: path_name = [path_name] file_name, path_name, description = imp.find_module(mod, path_name) # Add, uniquely. self.plugins_dirs[os.path.abspath(path_name)] = plugin if os.path.exists(os.path.join(os.path.abspath(path_name), "templates")): full_path_template_dirs.append( os.path.join(os.path.abspath(path_name), "templates") ) # Key by module name self.plugins_dirs = dict(zip(self.plugins_dirs.values(), self.plugins_dirs.keys())) # Storing here because storage hasn't been bootstrapped yet. os.environ["WILL_TEMPLATE_DIRS_PICKLED"] =\ ";;".join(full_path_template_dirs)
def import_settings(quiet=True): """This method takes care of importing settings from the environment, and config.py file. Order of operations: 1. Imports all WILL_ settings from the environment, and strips off the WILL_ 2. Imports settings from config.py 3. Sets defaults for any missing, required settings. This method takes a quiet kwarg, that when False, prints helpful output. Called that way during bootstrapping. """ settings = {} # Import from environment, handle environment-specific parsing. for k, v in os.environ.items(): if k[:5] == "WILL_": k = k[5:] settings[k] = v if "HIPCHAT_ROOMS" in settings and type( settings["HIPCHAT_ROOMS"]) is type("tes"): settings["HIPCHAT_ROOMS"] = settings["HIPCHAT_ROOMS"].split(";") if "ROOMS" in settings: settings["ROOMS"] = settings["ROOMS"].split(";") if "PLUGINS" in settings: settings["PLUGINS"] = settings["PLUGINS"].split(";") if 'PLUGIN_BLACKLIST' in settings: settings["PLUGIN_BLACKLIST"] = (settings["PLUGIN_BLACKLIST"].split(";") if settings["PLUGIN_BLACKLIST"] else []) # If HIPCHAT_SERVER is set, we need to change the USERNAME slightly # for XMPP to work. if "HIPCHAT_SERVER" in settings: settings["USERNAME"] = "******".\ format(user=settings["USERNAME"].split("@")[0], host=settings["HIPCHAT_SERVER"]) else: settings["HIPCHAT_SERVER"] = "api.hipchat.com" # Import from config if not quiet: puts("Importing config.py... ") with indent(2): try: had_warning = False try: import config except ImportError: # Missing config.py. Check for config.py.dist if os.path.isfile("config.py.dist"): confirm = input( "Hi, looks like you're just starting up!\nI didn't find a config.py, but I do see config.py.dist here. Want me to use that? (y/n) " ).lower() if confirm in ["y", "yes"]: print("Great! One moment.\n\n") os.rename("config.py.dist", "config.py") import config else: print( "Ok. I can't start without one though. Quitting now!" ) sys.exit(1) else: error( "I'm missing my config.py file. Usually one comes with the installation - maybe it got lost?" ) sys.exit(1) for k, v in config.__dict__.items(): # Ignore private variables if "__" not in k: if k in os.environ and v != os.environ[k] and not quiet: warn( "%s is set in the environment as '%s', but overridden in" " config.py as '%s'." % (k, os.environ[k], v)) had_warning = True if k not in settings: settings[k] = v if not had_warning and not quiet: show_valid("Valid.") except: # TODO: Check to see if there's a config.py.dist if not quiet: warn("no config.py found. This might be ok, but more likely, " "you haven't copied config.py.dist over to config.py") if not quiet: puts("Verifying settings... ") with indent(2): # Deprecation and backwards-compatibility for Will 1.x-> 2.x DEPRECATED_BUT_MAPPED_SETTINGS = { "USERNAME": "******", "PASSWORD": "******", "V1_TOKEN": "HIPCHAT_V1_TOKEN", "V2_TOKEN": "HIPCHAT_V2_TOKEN", "TOKEN": "HIPCHAT_V1_TOKEN", "ROOMS": "HIPCHAT_ROOMS", "NAME": "HIPCHAT_NAME", "HANDLE": "HIPCHAT_HANDLE", "DEFAULT_ROOM": "HIPCHAT_DEFAULT_ROOM", "SLACK_DEFAULT_ROOM": "SLACK_DEFAULT_CHANNEL", } deprecation_warn_shown = False for k, v in DEPRECATED_BUT_MAPPED_SETTINGS.items(): if not v in settings and k in settings: if not deprecation_warn_shown and not quiet: error( "Deprecated settings. The following settings will stop working in Will 2.2:" ) deprecation_warn_shown = True if not quiet: warn("Please update %s to %s. " % (k, v)) settings[v] = settings[k] del settings[k] # Migrate from 1.x if "CHAT_BACKENDS" in settings and "IO_BACKENDS" not in settings: IO_BACKENDS = [] for c in settings["CHAT_BACKENDS"]: IO_BACKENDS.append("will.backends.io_adapters.%s" % c) settings["IO_BACKENDS"] = IO_BACKENDS if not quiet: warn("Deprecated settings. Please update your config.py from:" "\n CHAT_BACKENDS = %s\n to\n IO_BACKENDS = %s" % (settings["CHAT_BACKENDS"], IO_BACKENDS)) if "CHAT_BACKENDS" not in settings and "IO_BACKENDS" not in settings: if not quiet: warn( """Deprecated settings. No backend found, so we're defaulting to hipchat and shell only. Please add this to your config.py: IO_BACKENDS = " "will.backends.io_adapters.hipchat", "will.backends.io_adapters.shell", # "will.backends.io_adapters.slack", # "will.backends.io_adapters.rocketchat", ] """) settings["IO_BACKENDS"] = [ "will.backends.io_adapters.hipchat", "will.backends.io_adapters.shell", ] if "ANALYZE_BACKENDS" not in settings: if not quiet: note( "No ANALYZE_BACKENDS specified. Defaulting to history only." ) settings["ANALYZE_BACKENDS"] = [ "will.backends.analysis.nothing", "will.backends.analysis.history", ] if "GENERATION_BACKENDS" not in settings: if not quiet: note( "No GENERATION_BACKENDS specified. Defaulting to fuzzy_all_matches and strict_regex." ) settings["GENERATION_BACKENDS"] = [ "will.backends.generation.fuzzy_all_matches", "will.backends.generation.strict_regex", ] if "EXECUTION_BACKENDS" not in settings: if not quiet: note( "No EXECUTION_BACKENDS specified. Defaulting to best_score." ) settings["EXECUTION_BACKENDS"] = [ "will.backends.execution.best_score", ] # Set for hipchat for b in settings["IO_BACKENDS"]: if "hipchat" in b: if "ALLOW_INSECURE_HIPCHAT_SERVER" in settings \ and ( settings["ALLOW_INSECURE_HIPCHAT_SERVER"] is True or settings["ALLOW_INSECURE_HIPCHAT_SERVER"].lower() == "true" ): warn( "You are choosing to run will with SSL disabled. " "This is INSECURE and should NEVER be deployed outside a development environment." ) settings["ALLOW_INSECURE_HIPCHAT_SERVER"] = True settings["REQUESTS_OPTIONS"] = { "verify": False, } else: settings["ALLOW_INSECURE_HIPCHAT_SERVER"] = False if "HIPCHAT_ROOMS" not in settings: if not quiet: warn( "no HIPCHAT_ROOMS list found in the environment or config. " "This is ok - Will will just join all available HIPCHAT_rooms." ) settings["HIPCHAT_ROOMS"] = None if ("HIPCHAT_DEFAULT_ROOM" not in settings and "HIPCHAT_ROOMS" in settings and settings["HIPCHAT_ROOMS"] and len(settings["HIPCHAT_ROOMS"]) > 0): if not quiet: warn( "no HIPCHAT_DEFAULT_ROOM found in the environment or config. " "Defaulting to '%s', the first one." % settings["HIPCHAT_ROOMS"][0]) settings["HIPCHAT_DEFAULT_ROOM"] = settings[ "HIPCHAT_ROOMS"][0] if "HIPCHAT_HANDLE" in settings and "HIPCHAT_HANDLE_NOTED" not in settings: if not quiet: note( """HIPCHAT_HANDLE is no longer required (or used), as Will knows how to get\n his current handle from the HipChat servers.""" ) settings["HIPCHAT_HANDLE_NOTED"] = True if "HIPCHAT_NAME" in settings and "HIPCHAT_NAME_NOTED" not in settings: if not quiet: note( """HIPCHAT_NAME is no longer required (or used), as Will knows how to get\n his current name from the HipChat servers.""") settings["HIPCHAT_NAME_NOTED"] = True # Rocket.chat for b in settings["IO_BACKENDS"]: if "rocketchat" in b: if "ROCKETCHAT_USERNAME" in settings and "ROCKETCHAT_EMAIL" not in settings: settings["ROCKETCHAT_EMAIL"] = settings[ "ROCKETCHAT_USERNAME"] if "ROCKETCHAT_URL" in settings: if settings["ROCKETCHAT_URL"].endswith("/"): settings["ROCKETCHAT_URL"] = settings[ "ROCKETCHAT_URL"][:-1] if ("DEFAULT_BACKEND" not in settings and "IO_BACKENDS" in settings and settings["IO_BACKENDS"] and len(settings["IO_BACKENDS"]) > 0): if not quiet: note( "no DEFAULT_BACKEND found in the environment or config.\n " " Defaulting to '%s', the first one." % settings["IO_BACKENDS"][0]) settings["DEFAULT_BACKEND"] = settings["IO_BACKENDS"][0] for b in settings["IO_BACKENDS"]: if "slack" in b and "SLACK_DEFAULT_CHANNEL" not in settings and not quiet: warn( "No SLACK_DEFAULT_CHANNEL set - any messages sent without an explicit channel will go " "to a non-deterministic channel that will has access to " "- this is almost certainly not what you want.") if "HTTPSERVER_PORT" not in settings: # For heroku if "PORT" in os.environ: settings["HTTPSERVER_PORT"] = os.environ["PORT"] else: if not quiet: warn( "no HTTPSERVER_PORT found in the environment or config. Defaulting to ':80'." ) settings["HTTPSERVER_PORT"] = "80" if "STORAGE_BACKEND" not in settings: if not quiet: warn("No STORAGE_BACKEND specified. Defaulting to redis.") settings["STORAGE_BACKEND"] = "redis" if "PUBSUB_BACKEND" not in settings: if not quiet: warn("No PUBSUB_BACKEND specified. Defaulting to redis.") settings["PUBSUB_BACKEND"] = "redis" if settings["STORAGE_BACKEND"] == "redis" or settings[ "PUBSUB_BACKEND"] == "redis": if "REDIS_URL" not in settings: # For heroku if "REDIS_URL" in os.environ: settings["REDIS_URL"] = os.environ["REDIS_URL"] if not quiet: note( "WILL_REDIS_URL not set, but it appears you're using Heroku Redis or another standard REDIS_URL. If so, all good." ) if "REDISCLOUD_URL" in os.environ: settings["REDIS_URL"] = os.environ["REDISCLOUD_URL"] if not quiet: note( "WILL_REDIS_URL not set, but it appears you're using RedisCloud. If so, all good." ) elif "REDISTOGO_URL" in os.environ: settings["REDIS_URL"] = os.environ["REDISTOGO_URL"] if not quiet: note( "WILL_REDIS_URL not set, but it appears you're using RedisToGo. If so, all good." ) elif "OPENREDIS_URL" in os.environ: settings["REDIS_URL"] = os.environ["OPENREDIS_URL"] if not quiet: note( "WILL_REDIS_URL not set, but it appears you're using OpenRedis. If so, all good." ) else: settings["REDIS_URL"] = "redis://localhost:6379/7" if not quiet: note( "WILL_REDIS_URL not set. Defaulting to redis://localhost:6379/7." ) if not settings["REDIS_URL"].startswith("redis://"): settings["REDIS_URL"] = "redis://%s" % settings["REDIS_URL"] if "REDIS_MAX_CONNECTIONS" not in settings or not settings[ "REDIS_MAX_CONNECTIONS"]: settings["REDIS_MAX_CONNECTIONS"] = 4 if not quiet: note("REDIS_MAX_CONNECTIONS not set. Defaulting to 4.") if settings["STORAGE_BACKEND"] == "file": if "FILE_DIR" not in settings: settings["FILE_DIR"] = "~/.will/" if not quiet: note("FILE_DIR not set. Defaulting to ~/.will/") if settings["STORAGE_BACKEND"] == "couchbase": if "COUCHBASE_URL" not in settings: settings["COUCHBASE_URL"] = "couchbase:///will" if not quiet: note( "COUCHBASE_URL not set. Defaulting to couchbase:///will" ) if "PUBLIC_URL" not in settings: default_public = "http://localhost:%s" % settings["HTTPSERVER_PORT"] settings["PUBLIC_URL"] = default_public if not quiet: note( "no PUBLIC_URL found in the environment or config.\n Defaulting to '%s'." % default_public) if not "REQUESTS_OPTIONS" in settings: settings["REQUESTS_OPTIONS"] = {} if "TEMPLATE_DIRS" not in settings: if "WILL_TEMPLATE_DIRS_PICKLED" in os.environ: # All good pass else: settings["TEMPLATE_DIRS"] = [] if "WILL_HANDLE" not in settings: if "HANDLE" in settings: settings["WILL_HANDLE"] = settings["HANDLE"] elif "SLACK_HANDLE" in settings: settings["WILL_HANDLE"] = settings["SLACK_HANDLE"] elif "HIPCHAT_HANDLE" in settings: settings["WILL_HANDLE"] = settings["HIPCHAT_HANDLE"] elif "ROCKETCHAT_HANDLE" in settings: settings["WILL_HANDLE"] = settings["ROCKETCHAT_HANDLE"] else: settings["WILL_HANDLE"] = "will" if "ADMINS" not in settings: settings["ADMINS"] = "*" else: if "WILL_ADMINS" in os.environ: settings["ADMINS"] = [ a.strip().lower() for a in settings.get('ADMINS', '').split(';') if a.strip() ] if "ADMINS" in settings and settings["ADMINS"] != "*": warn( "ADMINS is now deprecated, and will be removed at the end of 2017. Please use ACL instead. See below for details" ) note("Change your config.py to:\n ACL = {\n 'admins': %s\n }" % settings["ADMINS"]) if "DISABLE_ACL" not in settings: settings["DISABLE_ACL"] = False if "PROXY_URL" in settings: parsed_proxy_url = parse.urlparse(settings["PROXY_URL"]) settings["USE_PROXY"] = True settings["PROXY_HOSTNAME"] = parsed_proxy_url.hostname settings["PROXY_USERNAME"] = parsed_proxy_url.username settings["PROXY_PASSWORD"] = parsed_proxy_url.password settings["PROXY_PORT"] = parsed_proxy_url.port else: settings["USE_PROXY"] = False if "EVENT_LOOP_INTERVAL" not in settings: settings["EVENT_LOOP_INTERVAL"] = 0.025 if "LOGLEVEL" not in settings: settings["LOGLEVEL"] = "ERROR" if "ENABLE_INTERNAL_ENCRYPTION" not in settings: settings["ENABLE_INTERNAL_ENCRYPTION"] = True if "SECRET_KEY" not in settings: if not quiet: if "ENABLE_INTERNAL_ENCRYPTION" in settings and settings[ "ENABLE_INTERNAL_ENCRYPTION"]: key = auto_key() if key: warn( """No SECRET_KEY specified and ENABLE_INTERNAL_ENCRYPTION is on.\n Temporarily auto-generating a key specific to this computer:\n {}\n Please set WILL_SECRET_KEY in the environment as soon as possible to ensure \n Will is able to access information from previous runs.""" .format(key)) else: error( """ENABLE_INTERNAL_ENCRYPTION is turned on, but a SECRET_KEY has not been given.\n We tried to automatically generate temporary SECRET_KEY, but this appears to be a \n" shared or virtualized environment.\n Please set a unique secret key in the environment as WILL_SECRET_KEY to run will.""") print( " Unable to start will without a SECRET_KEY while encryption is turned on. Shutting down." ) sys.exit(1) settings["SECRET_KEY"] = key os.environ["WILL_SECRET_KEY"] = settings["SECRET_KEY"] os.environ["WILL_EPHEMERAL_SECRET_KEY"] = "True" if "FUZZY_MINIMUM_MATCH_CONFIDENCE" not in settings: settings["FUZZY_MINIMUM_MATCH_CONFIDENCE"] = 91 if "FUZZY_REGEX_ALLOWABLE_ERRORS" not in settings: settings["FUZZY_REGEX_ALLOWABLE_ERRORS"] = 3 # Set them in the module namespace for k in sorted(settings, key=lambda x: x[0]): if not quiet: show_valid(k) globals()[k] = settings[k]
from will.utils import warn from will.backends.storage.couchbase_backend import CouchbaseStorage warn( """Deprecation - will.storage.couchbase_storage has been moved to will.backends.storage.couchbase_backend, and will be removed in version 2.2. Please update your paths accordingly!""" )
from will.utils import warn from will.backends.storage.redis_backend import RedisStorage warn( "Deprecation - will.storage.redis_storage has been moved to will.backends.storage.redis_backend, " + "and will be removed in version 2.2. Please update your paths accordingly!" )
def bootstrap_plugins(self): puts("Bootstrapping plugins...") OTHER_HELP_HEADING = "Other" plugin_modules = {} plugin_modules_library = {} # NOTE: You can't access self.storage here, or it will deadlock when the threads try to access redis. with indent(2): parent_help_text = None for plugin_name, plugin_root in self.plugins_dirs.items(): for root, dirs, files in os.walk(plugin_root, topdown=False): for f in files: if f[-3:] == ".py" and f != "__init__.py": try: module_path = os.path.join(root, f) path_components = module_path.split(os.sep) module_name = path_components[-1][:-3] full_module_name = ".".join(path_components) # Check blacklist. blacklisted = False for b in settings.PLUGIN_BLACKLIST: if b in full_module_name: blacklisted = True break parent_mod = path_components[-2].split("/")[-1] parent_help_text = parent_mod.title() # Don't even *try* to load a blacklisted module. if not blacklisted: try: plugin_modules[full_module_name] = imp.load_source(module_name, module_path) parent_root = os.path.join(root, "__init__.py") parent = imp.load_source(parent_mod, parent_root) parent_help_text = getattr(parent, "MODULE_DESCRIPTION", parent_help_text) except: # If it's blacklisted, don't worry if this blows up. if blacklisted: pass else: raise plugin_modules_library[full_module_name] = { "full_module_name": full_module_name, "file_path": module_path, "name": module_name, "parent_name": plugin_name, "parent_module_name": parent_mod, "parent_help_text": parent_help_text, "blacklisted": blacklisted, } except Exception as e: self.startup_error("Error loading %s" % (module_path,), e) self.plugins = [] for name, module in plugin_modules.items(): try: for class_name, cls in inspect.getmembers(module, predicate=inspect.isclass): try: if hasattr(cls, "is_will_plugin") and cls.is_will_plugin and class_name != "WillPlugin": self.plugins.append({ "name": class_name, "class": cls, "module": module, "full_module_name": name, "parent_name": plugin_modules_library[name]["parent_name"], "parent_path": plugin_modules_library[name]["file_path"], "parent_module_name": plugin_modules_library[name]["parent_module_name"], "parent_help_text": plugin_modules_library[name]["parent_help_text"], "blacklisted": plugin_modules_library[name]["blacklisted"], }) except Exception as e: self.startup_error("Error bootstrapping %s" % (class_name,), e) except Exception as e: self.startup_error("Error bootstrapping %s" % (name,), e) self._plugin_modules_library = plugin_modules_library # Sift and Sort. self.message_listeners = {} self.periodic_tasks = [] self.random_tasks = [] self.bottle_routes = [] self.all_listener_regexes = [] self.help_modules = {} self.help_modules[OTHER_HELP_HEADING] = [] self.some_listeners_include_me = False self.plugins.sort(key=operator.itemgetter("parent_module_name")) self.required_settings_from_plugins = {} last_parent_name = None for plugin_info in self.plugins: try: if last_parent_name != plugin_info["parent_help_text"]: friendly_name = "%(parent_help_text)s " % plugin_info module_name = " %(parent_name)s" % plugin_info # Justify friendly_name = friendly_name.ljust(50, '-') module_name = module_name.rjust(40, '-') puts("") puts("%s%s" % (friendly_name, module_name)) last_parent_name = plugin_info["parent_help_text"] with indent(2): plugin_name = plugin_info["name"] plugin_warnings = [] # Just a little nicety if plugin_name[-6:] == "Plugin": plugin_name = plugin_name[:-6] if plugin_info["blacklisted"]: puts("✗ %s (blacklisted)" % plugin_name) else: plugin_instances = {} for function_name, fn in inspect.getmembers( plugin_info["class"], predicate=lambda x: inspect.ismethod(x) or inspect.isfunction(x) ): try: # Check for required_settings with indent(2): if hasattr(fn, "will_fn_metadata"): meta = fn.will_fn_metadata if "warnings" in meta: plugin_warnings.append(meta["warnings"]) if "required_settings" in meta: for s in meta["required_settings"]: self.required_settings_from_plugins[s] = { "plugin_name": plugin_name, "function_name": function_name, "setting_name": s, } if ( "listens_to_messages" in meta and meta["listens_to_messages"] and "listener_regex" in meta ): # puts("- %s" % function_name) regex = meta["listener_regex"] if not meta["case_sensitive"]: regex = "(?i)%s" % regex help_regex = meta["listener_regex"] if meta["listens_only_to_direct_mentions"]: help_regex = "@%s %s" % (settings.WILL_HANDLE, help_regex) self.all_listener_regexes.append(help_regex) if meta["__doc__"]: pht = plugin_info.get("parent_help_text", None) if pht: if pht in self.help_modules: self.help_modules[pht].append(u"%s" % meta["__doc__"]) else: self.help_modules[pht] = [u"%s" % meta["__doc__"]] else: self.help_modules[OTHER_HELP_HEADING].append(u"%s" % meta["__doc__"]) if meta["multiline"]: compiled_regex = re.compile(regex, re.MULTILINE | re.DOTALL) else: compiled_regex = re.compile(regex) if plugin_info["class"] in plugin_instances: instance = plugin_instances[plugin_info["class"]] else: instance = plugin_info["class"](bot=self) plugin_instances[plugin_info["class"]] = instance full_method_name = "%s.%s" % (plugin_info["name"], function_name) cleaned_info = copy.copy(plugin_info) del cleaned_info["module"] del cleaned_info["class"] self.message_listeners[full_method_name] = { "full_method_name": full_method_name, "function_name": function_name, "class_name": plugin_info["name"], "regex_pattern": meta["listener_regex"], "regex": compiled_regex, "fn": getattr(instance, function_name), "args": meta["listener_args"], "include_me": meta["listener_includes_me"], "case_sensitive": meta["case_sensitive"], "multiline": meta["multiline"], "direct_mentions_only": meta["listens_only_to_direct_mentions"], "admin_only": meta["listens_only_to_admin"], "acl": meta["listeners_acl"], "plugin_info": cleaned_info, } if meta["listener_includes_me"]: self.some_listeners_include_me = True elif "periodic_task" in meta and meta["periodic_task"]: # puts("- %s" % function_name) self.periodic_tasks.append((plugin_info, fn, function_name)) elif "random_task" in meta and meta["random_task"]: # puts("- %s" % function_name) self.random_tasks.append((plugin_info, fn, function_name)) elif "bottle_route" in meta: # puts("- %s" % function_name) self.bottle_routes.append((plugin_info["class"], function_name)) except Exception: error(plugin_name) self.startup_error( "Error bootstrapping %s.%s" % ( plugin_info["class"], function_name, ) ) if len(plugin_warnings) > 0: show_invalid(plugin_name) for w in plugin_warnings: warn(w) else: show_valid(plugin_name) except Exception as e: self.startup_error("Error bootstrapping %s" % (plugin_info["class"],), e) self.save("all_listener_regexes", self.all_listener_regexes) puts("")