def f(name, implementation): """Factory for injections for all OctoPrintPlugins""" if not isinstance(implementation, octoprint.plugin.OctoPrintPlugin): return None components_copy = dict(components) if "printer" in components: import functools import wrapt def tagwrap(f): @functools.wraps(f) def wrapper(*args, **kwargs): tags = kwargs.get("tags", set()) | { "source:plugin", "plugin:{}".format(name), } kwargs["tags"] = tags return f(*args, **kwargs) wrapper.__tagwrapped__ = True return wrapper class TaggedFuncsPrinter(wrapt.ObjectProxy): def __getattribute__(self, attr): __wrapped__ = super(TaggedFuncsPrinter, self).__getattribute__("__wrapped__") if attr == "__wrapped__": return __wrapped__ item = getattr(__wrapped__, attr) if (callable(item) and ("tags" in item.__code__.co_varnames or "kwargs" in item.__code__.co_varnames) and not getattr(item, "__tagwrapped__", False)): return tagwrap(item) else: return item components_copy["printer"] = TaggedFuncsPrinter( components["printer"]) props = {} props.update(components_copy) props.update({ "data_folder": os.path.join(settings.getBaseFolder("data"), name) }) return props
def get_plugin_blacklist(settings, connectivity_checker=None): import requests import os import time import yaml from octoprint.util import bom_aware_open from octoprint.util.version import is_octoprint_compatible logger = log.getLogger(__name__ + ".startup") if connectivity_checker is not None and not connectivity_checker.online: logger.info("We don't appear to be online, not fetching plugin blacklist") return [] def format_blacklist(entries): format_entry = lambda x: "{} ({})".format(x[0], x[1]) if isinstance(x, (list, tuple)) and len(x) == 2 \ else "{} (any)".format(x) return ", ".join(map(format_entry, entries)) def process_blacklist(entries): result = [] if not isinstance(entries, list): return result for entry in entries: if not "plugin" in entry: continue if "octoversions" in entry and not is_octoprint_compatible(*entry["octoversions"]): continue if "version" in entry: logger.debug("Blacklisted plugin: {}, version: {}".format(entry["plugin"], entry["version"])) result.append((entry["plugin"], entry["version"])) elif "versions" in entry: logger.debug("Blacklisted plugin: {}, versions: {}".format(entry["plugin"], ", ".join(entry["versions"]))) for version in entry["versions"]: result.append((entry["plugin"], version)) else: logger.debug("Blacklisted plugin: {}".format(entry["plugin"])) result.append(entry["plugin"]) return result def fetch_blacklist_from_cache(path, ttl): if not os.path.isfile(path): return None if os.stat(path).st_mtime + ttl < time.time(): return None with bom_aware_open(path, encoding="utf-8", mode="r") as f: result = yaml.safe_load(f) if isinstance(result, list): return result def fetch_blacklist_from_url(url, timeout=3, cache=None): result = [] try: r = requests.get(url, timeout=timeout) result = process_blacklist(r.json()) if cache is not None: try: with bom_aware_open(cache, encoding="utf-8", mode="w") as f: yaml.safe_dump(result, f) except: logger.info("Fetched plugin blacklist but couldn't write it to its cache file.") except: logger.info("Unable to fetch plugin blacklist from {}, proceeding without it.".format(url)) return result try: # first attempt to fetch from cache cache_path = os.path.join(settings.getBaseFolder("data"), "plugin_blacklist.yaml") ttl = settings.getInt(["server", "pluginBlacklist", "ttl"]) blacklist = fetch_blacklist_from_cache(cache_path, ttl) if blacklist is None: # no result from the cache, let's fetch it fresh url = settings.get(["server", "pluginBlacklist", "url"]) timeout = settings.getFloat(["server", "pluginBlacklist", "timeout"]) blacklist = fetch_blacklist_from_url(url, timeout=timeout, cache=cache_path) if blacklist is None: # still now result, so no blacklist blacklist = [] if blacklist: logger.info("Blacklist processing done, " "adding {} blacklisted plugin versions: {}".format(len(blacklist), format_blacklist(blacklist))) else: logger.info("Blacklist processing done") return blacklist except: logger.exception("Something went wrong while processing the plugin blacklist. Proceeding without it.")
def init_pluginsystem(settings, safe_mode=False, ignore_blacklist=True, connectivity_checker=None): """Initializes the plugin manager based on the settings.""" import os logger = log.getLogger(__name__ + ".startup") plugin_folders = [(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "plugins")), True), settings.getBaseFolder("plugins")] plugin_entry_points = ["octoprint.plugin"] plugin_disabled_list = settings.get(["plugins", "_disabled"]) plugin_blacklist = [] if not ignore_blacklist and settings.getBoolean(["server", "pluginBlacklist", "enabled"]): plugin_blacklist = get_plugin_blacklist(settings, connectivity_checker=connectivity_checker) plugin_validators = [] if safe_mode: def validator(phase, plugin_info): if phase in ("before_import", "before_load", "before_enable"): setattr(plugin_info, "safe_mode_victim", not plugin_info.bundled) if not plugin_info.bundled: return False return True plugin_validators.append(validator) from octoprint.plugin import plugin_manager pm = plugin_manager(init=True, plugin_folders=plugin_folders, plugin_entry_points=plugin_entry_points, plugin_disabled_list=plugin_disabled_list, plugin_blacklist=plugin_blacklist, plugin_validators=plugin_validators) settings_overlays = dict() disabled_from_overlays = dict() def handle_plugin_loaded(name, plugin): if plugin.instance and hasattr(plugin.instance, "__plugin_settings_overlay__"): plugin.needs_restart = True # plugin has a settings overlay, inject it overlay_definition = getattr(plugin.instance, "__plugin_settings_overlay__") if isinstance(overlay_definition, (tuple, list)): overlay_definition, order = overlay_definition else: order = None overlay = settings.load_overlay(overlay_definition) if "plugins" in overlay and "_disabled" in overlay["plugins"]: disabled_plugins = overlay["plugins"]["_disabled"] del overlay["plugins"]["_disabled"] disabled_from_overlays[name] = (disabled_plugins, order) settings_overlays[name] = overlay logger.debug("Found settings overlay on plugin {}".format(name)) def handle_plugins_loaded(startup=False, initialize_implementations=True, force_reload=None): if not startup: return sorted_disabled_from_overlays = sorted([(key, value[0], value[1]) for key, value in disabled_from_overlays.items()], key=lambda x: (x[2] is None, x[2], x[0])) disabled_list = pm.plugin_disabled_list already_processed = [] for name, addons, _ in sorted_disabled_from_overlays: if not name in disabled_list and not name.endswith("disabled"): for addon in addons: if addon in disabled_list: continue if addon in already_processed: logger.info("Plugin {} wants to disable plugin {}, but that was already processed".format(name, addon)) if not addon in already_processed and not addon in disabled_list: disabled_list.append(addon) logger.info("Disabling plugin {} as defined by plugin {}".format(addon, name)) already_processed.append(name) def handle_plugin_enabled(name, plugin): if name in settings_overlays: settings.add_overlay(settings_overlays[name]) logger.info("Added settings overlay from plugin {}".format(name)) pm.on_plugin_loaded = handle_plugin_loaded pm.on_plugins_loaded = handle_plugins_loaded pm.on_plugin_enabled = handle_plugin_enabled pm.reload_plugins(startup=True, initialize_implementations=False) return pm
def init_logging(settings, use_logging_file=True, logging_file=None, default_config=None, debug=False, verbosity=0, uncaught_logger=None, uncaught_handler=None): """Sets up logging.""" import os from octoprint.util import dict_merge # default logging configuration if default_config is None: default_config = { "version": 1, "formatters": { "simple": { "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" }, "serial": { "format": "%(asctime)s - %(message)s" } }, "handlers": { "console": { "class": "logging.StreamHandler", "level": "DEBUG", "formatter": "simple", "stream": "ext://sys.stdout" }, "file": { "class": "octoprint.logging.handlers.OctoPrintLogHandler", "level": "DEBUG", "formatter": "simple", "when": "D", "backupCount": 6, "filename": os.path.join(settings.getBaseFolder("logs"), "octoprint.log") }, "serialFile": { "class": "octoprint.logging.handlers.SerialLogHandler", "level": "DEBUG", "formatter": "serial", "backupCount": 3, "filename": os.path.join(settings.getBaseFolder("logs"), "serial.log"), "delay": True } }, "loggers": { "SERIAL": { "level": "INFO", "handlers": ["serialFile"], "propagate": False }, "octoprint": { "level": "INFO" }, "octoprint.util": { "level": "INFO" }, "octoprint.plugins": { "level": "INFO" } }, "root": { "level": "WARN", "handlers": ["console", "file"] } } if debug or verbosity > 0: default_config["loggers"]["octoprint"]["level"] = "DEBUG" default_config["root"]["level"] = "INFO" if verbosity > 1: default_config["loggers"]["octoprint.plugins"]["level"] = "DEBUG" if verbosity > 2: default_config["root"]["level"] = "DEBUG" config = default_config if use_logging_file: # further logging configuration from file... if logging_file is None: logging_file = os.path.join(settings.getBaseFolder("base"), "logging.yaml") config_from_file = {} if os.path.exists(logging_file) and os.path.isfile(logging_file): import yaml with open(logging_file, "r") as f: config_from_file = yaml.safe_load(f) # we merge that with the default config if config_from_file is not None and isinstance(config_from_file, dict): config = dict_merge(default_config, config_from_file) # configure logging globally return set_logging_config(config, debug, verbosity, uncaught_logger, uncaught_handler)
def init_pluginsystem(settings, safe_mode=False): """Initializes the plugin manager based on the settings.""" import os logger = log.getLogger(__name__) plugin_folders = [(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "plugins")), True), settings.getBaseFolder("plugins")] plugin_entry_points = ["octoprint.plugin"] plugin_disabled_list = settings.get(["plugins", "_disabled"]) plugin_validators = [] if safe_mode: def validator(phase, plugin_info): if phase == "after_load": setattr(plugin_info, "safe_mode_victim", not plugin_info.bundled) setattr(plugin_info, "safe_mode_enabled", False) elif phase == "before_enable": if not plugin_info.bundled: setattr(plugin_info, "safe_mode_enabled", True) return False return True plugin_validators.append(validator) from octoprint.plugin import plugin_manager pm = plugin_manager(init=True, plugin_folders=plugin_folders, plugin_entry_points=plugin_entry_points, plugin_disabled_list=plugin_disabled_list, plugin_validators=plugin_validators) settings_overlays = dict() disabled_from_overlays = dict() def handle_plugin_loaded(name, plugin): if hasattr(plugin.instance, "__plugin_settings_overlay__"): plugin.needs_restart = True # plugin has a settings overlay, inject it overlay_definition = getattr(plugin.instance, "__plugin_settings_overlay__") if isinstance(overlay_definition, (tuple, list)): overlay_definition, order = overlay_definition else: order = None overlay = settings.load_overlay(overlay_definition) if "plugins" in overlay and "_disabled" in overlay["plugins"]: disabled_plugins = overlay["plugins"]["_disabled"] del overlay["plugins"]["_disabled"] disabled_from_overlays[name] = (disabled_plugins, order) settings_overlays[name] = overlay logger.debug("Found settings overlay on plugin {}".format(name)) def handle_plugins_loaded(startup=False, initialize_implementations=True, force_reload=None): if not startup: return sorted_disabled_from_overlays = sorted([(key, value[0], value[1]) for key, value in disabled_from_overlays.items()], key=lambda x: (x[2] is None, x[2], x[0])) disabled_list = pm.plugin_disabled_list already_processed = [] for name, addons, _ in sorted_disabled_from_overlays: if not name in disabled_list and not name.endswith("disabled"): for addon in addons: if addon in disabled_list: continue if addon in already_processed: logger.info("Plugin {} wants to disable plugin {}, but that was already processed".format(name, addon)) if not addon in already_processed and not addon in disabled_list: disabled_list.append(addon) logger.info("Disabling plugin {} as defined by plugin {} through settings overlay".format(addon, name)) already_processed.append(name) def handle_plugin_enabled(name, plugin): if name in settings_overlays: settings.add_overlay(settings_overlays[name]) logger.info("Added settings overlay from plugin {}".format(name)) pm.on_plugin_loaded = handle_plugin_loaded pm.on_plugins_loaded = handle_plugins_loaded pm.on_plugin_enabled = handle_plugin_enabled pm.reload_plugins(startup=True, initialize_implementations=False) return pm
def init_logging(settings, use_logging_file=True, logging_file=None, default_config=None, debug=False, verbosity=0, uncaught_logger=None, uncaught_handler=None): """Sets up logging.""" import os from octoprint.util import dict_merge # default logging configuration if default_config is None: default_config = { "version": 1, "formatters": { "simple": { "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" }, "serial": { "format": "%(asctime)s - %(message)s" } }, "handlers": { "console": { "class": "logging.StreamHandler", "level": "DEBUG", "formatter": "simple", "stream": "ext://sys.stdout" }, "file": { "class": "octoprint.logging.handlers.OctoPrintLogHandler", "level": "DEBUG", "formatter": "simple", "when": "D", "backupCount": 6, "filename": os.path.join(settings.getBaseFolder("logs"), "octoprint.log") }, "serialFile": { "class": "octoprint.logging.handlers.SerialLogHandler", "level": "DEBUG", "formatter": "serial", "backupCount": 3, "filename": os.path.join(settings.getBaseFolder("logs"), "serial.log") } }, "loggers": { "SERIAL": { "level": "CRITICAL", "handlers": ["serialFile"], "propagate": False }, "octoprint": { "level": "INFO" }, "octoprint.util": { "level": "INFO" }, "octoprint.plugins": { "level": "INFO" } }, "root": { "level": "WARN", "handlers": ["console", "file"] } } if debug or verbosity > 0: default_config["loggers"]["octoprint"]["level"] = "DEBUG" default_config["root"]["level"] = "INFO" if verbosity > 1: default_config["loggers"]["octoprint.plugins"]["level"] = "DEBUG" if verbosity > 2: default_config["root"]["level"] = "DEBUG" config = default_config if use_logging_file: # further logging configuration from file... if logging_file is None: logging_file = os.path.join(settings.getBaseFolder("base"), "logging.yaml") config_from_file = {} if os.path.exists(logging_file) and os.path.isfile(logging_file): import yaml with open(logging_file, "r") as f: config_from_file = yaml.safe_load(f) # we merge that with the default config if config_from_file is not None and isinstance(config_from_file, dict): config = dict_merge(default_config, config_from_file) # configure logging globally return set_logging_config(config, debug, verbosity, uncaught_logger, uncaught_handler)
def init_logging( settings, use_logging_file=True, logging_file=None, default_config=None, debug=False, verbosity=0, uncaught_logger=None, uncaught_handler=None, ): """Sets up logging.""" import os from octoprint.util import dict_merge # default logging configuration if default_config is None: simple_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" default_config = { "version": 1, "formatters": { "simple": { "format": simple_format }, "colored": { "()": "colorlog.ColoredFormatter", "format": "%(log_color)s" + simple_format + "%(reset)s", "reset": True, "log_colors": { "DEBUG": "cyan", "INFO": "white", "WARNING": "yellow", "ERROR": "red", "CRITICAL": "bold_red", }, }, "serial": { "format": "%(asctime)s - %(message)s" }, "timings": { "format": "%(asctime)s - %(message)s" }, "timingscsv": { "format": "%(asctime)s;%(func)s;%(timing)f" }, }, "handlers": { "console": { "class": "octoprint.logging.handlers.OctoPrintStreamHandler", "level": "DEBUG", "formatter": "colored", "stream": "ext://sys.stdout", }, "file": { "class": "octoprint.logging.handlers.OctoPrintLogHandler", "level": "DEBUG", "formatter": "simple", "when": "D", "backupCount": 6, "filename": os.path.join(settings.getBaseFolder("logs"), "octoprint.log"), }, "serialFile": { "class": "octoprint.logging.handlers.SerialLogHandler", "level": "DEBUG", "formatter": "serial", "backupCount": 3, "filename": os.path.join(settings.getBaseFolder("logs"), "serial.log"), "delay": True, }, "pluginTimingsFile": { "class": "octoprint.logging.handlers.PluginTimingsLogHandler", "level": "DEBUG", "formatter": "timings", "backupCount": 3, "filename": os.path.join(settings.getBaseFolder("logs"), "plugintimings.log"), "delay": True, }, "pluginTimingsCsvFile": { "class": "octoprint.logging.handlers.PluginTimingsLogHandler", "level": "DEBUG", "formatter": "timingscsv", "backupCount": 3, "filename": os.path.join(settings.getBaseFolder("logs"), "plugintimings.csv"), "delay": True, }, }, "loggers": { "SERIAL": { "level": "INFO", "handlers": ["serialFile"], "propagate": False, }, "PLUGIN_TIMINGS": { "level": "INFO", "handlers": ["pluginTimingsFile", "pluginTimingsCsvFile"], "propagate": False, }, "PLUGIN_TIMINGS.octoprint.plugin": { "level": "INFO" }, "octoprint": { "level": "INFO" }, "octoprint.util": { "level": "INFO" }, "octoprint.plugins": { "level": "INFO" }, }, "root": { "level": "WARN", "handlers": ["console", "file"] }, } if debug or verbosity > 0: default_config["loggers"]["octoprint"]["level"] = "DEBUG" default_config["root"]["level"] = "INFO" if verbosity > 1: default_config["loggers"]["octoprint.plugins"]["level"] = "DEBUG" if verbosity > 2: default_config["root"]["level"] = "DEBUG" config = default_config if use_logging_file: # further logging configuration from file... if logging_file is None: logging_file = os.path.join(settings.getBaseFolder("base"), "logging.yaml") config_from_file = {} if os.path.exists(logging_file) and os.path.isfile(logging_file): import yaml with io.open(logging_file, "rt", encoding="utf-8") as f: config_from_file = yaml.safe_load(f) # we merge that with the default config if config_from_file is not None and isinstance(config_from_file, dict): config = dict_merge(default_config, config_from_file) # configure logging globally return set_logging_config(config, debug, verbosity, uncaught_logger, uncaught_handler)