More documentation
This commit is contained in:
parent
84c32a3cd9
commit
cd3ead3f30
12 changed files with 712 additions and 75 deletions
|
|
@ -27,11 +27,11 @@ year_current = date.today().year
|
|||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
#needs_sphinx = '1.3'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinxcontrib.httpdomain']
|
||||
extensions = ['sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinxcontrib.httpdomain', 'sphinx.ext.autosummary', 'sphinxcontrib.napoleon']
|
||||
todo_include_todos = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.. _sec-development-interface:
|
||||
|
||||
Interfaces
|
||||
==========
|
||||
Internal API
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
|
@ -9,3 +9,4 @@ Interfaces
|
|||
filemanager.rst
|
||||
plugin.rst
|
||||
printer.rst
|
||||
util.rst
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
.. _sec-development-interface-plugin:
|
||||
|
||||
``octoprint.plugin``
|
||||
--------------------
|
||||
octoprint.plugin
|
||||
----------------
|
||||
|
||||
.. automodule:: octoprint.plugin
|
||||
:members: plugin_manager, plugin_settings, call_plugin, PluginSettings
|
||||
:undoc-members:
|
||||
|
||||
.. _sec-development-interface-plugin-core:
|
||||
|
||||
octoprint.plugin.core
|
||||
---------------------
|
||||
|
||||
.. automodule:: octoprint.plugin.core
|
||||
|
||||
octoprint.plugin.types
|
||||
----------------------
|
||||
|
||||
.. automodule:: octoprint.plugin.types
|
||||
|
||||
|
|
|
|||
9
docs/development/interface/util.rst
Normal file
9
docs/development/interface/util.rst
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.. _sec-development-interface-util:
|
||||
|
||||
octoprint.util
|
||||
--------------
|
||||
|
||||
.. automodule:: octoprint.util
|
||||
:members:
|
||||
|
||||
|
||||
|
|
@ -3,7 +3,99 @@
|
|||
Available plugin mixins
|
||||
=======================
|
||||
|
||||
.. automodule:: octoprint.plugin
|
||||
:members: AppPlugin, AssetPlugin, BlueprintPlugin, EventHandlerPlugin, ProgressPlugin, SettingsPlugin, ShutdownPlugin, SimpleApiPlugin, SlicerPlugin, StartupPlugin, TemplatePlugin
|
||||
:undoc-members:
|
||||
The following plugin mixins are currently available:
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Please note that all plugin mixins inherit from :class:`~octoprint.plugin.core.Plugin` and
|
||||
:class:`~octoprint.plugin.OctoPrintPlugin`, which also provide attributes of interest to plugin developers.
|
||||
|
||||
.. _sec-plugins-mixins-startupplugin:
|
||||
|
||||
StartupPlugin
|
||||
-------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.StartupPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-shutdownplugin:
|
||||
|
||||
ShutdownPlugin
|
||||
--------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.ShutdownPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-settingsplugin:
|
||||
|
||||
SettingsPlugin
|
||||
--------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.SettingsPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-assetplugin:
|
||||
|
||||
AssetPlugin
|
||||
-----------
|
||||
|
||||
.. autoclass:: octoprint.plugin.AssetPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-templateplugin:
|
||||
|
||||
TemplatePlugin
|
||||
--------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.TemplatePlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-simpleapiplugin:
|
||||
|
||||
SimpleApiPlugin
|
||||
---------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.SimpleApiPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-blueprintplugin:
|
||||
|
||||
BlueprintPlugin
|
||||
---------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.BlueprintPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-eventhandlerplugin:
|
||||
|
||||
EventHandlerPlugin
|
||||
------------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.EventHandlerPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-progressplugin:
|
||||
|
||||
ProgressPlugin
|
||||
--------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.ProgressPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-slicerplugin:
|
||||
|
||||
SlicerPlugin
|
||||
------------
|
||||
|
||||
.. autoclass:: octoprint.plugin.SlicerPlugin
|
||||
:members:
|
||||
|
||||
.. _sec-plugins-mixins-appplugin:
|
||||
|
||||
AppPlugin
|
||||
---------
|
||||
|
||||
.. autoclass:: octoprint.plugin.AppPlugin
|
||||
:members:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
sphinxcontrib-httpdomain
|
||||
sphinxcontrib-napoleon
|
||||
sphinx_rtd_theme
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
This module represents OctoPrint's plugin subsystem. This includes management and helper methods as well as the
|
||||
registered plugin types.
|
||||
|
||||
.. autofunction:: plugin_manager
|
||||
|
||||
.. autofunction:: plugin_settings
|
||||
|
||||
.. autofunction:: call_plugin
|
||||
|
||||
.. autoclass:: PluginSettings
|
||||
:members:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
|
|
@ -18,9 +33,41 @@ from octoprint.util import deprecated
|
|||
_instance = None
|
||||
|
||||
def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_entry_points=None, plugin_disabled_list=None):
|
||||
"""
|
||||
Factory method for initially constructing and consecutively retrieving the :class:`~octoprint.plugin.core.PluginManager`
|
||||
singleton.
|
||||
|
||||
Arguments:
|
||||
init (boolean): A flag indicating whether this is the initial call to construct the singleton (True) or not
|
||||
(False, default). If this is set to True and the plugin manager has already been initialized, a :class:`ValueError`
|
||||
will be raised. The same will happen if the plugin manager has not yet been initialized and this is set to
|
||||
False.
|
||||
plugin_folders (list): A list of folders (as strings containing the absolute path to them) in which to look for
|
||||
potential plugin modules. If not provided this defaults to the configured ``plugins`` base folder and
|
||||
``src/plugins`` within OctoPrint's code base.
|
||||
plugin_types (list): A list of recognized plugin types for which to look for provided implementations. If not
|
||||
provided this defaults to the plugin types found in :mod:`octoprint.plugin.types` without
|
||||
:class:`~octoprint.plugin.OctoPrintPlugin`.
|
||||
plugin_entry_points (list): A list of entry points pointing to modules which to load as plugins. If not provided
|
||||
this defaults to the entry point ``octoprint.plugin``.
|
||||
plugin_disabled_list (list): A list of plugin identifiers that are currently disabled. If not provided this
|
||||
defaults to all plugins for which ``enabled`` is set to ``False`` in the settings.
|
||||
|
||||
Returns:
|
||||
PluginManager: A fully initialized :class:`~octoprint.plugin.core.PluginManager` instance to be used for plugin
|
||||
management tasks.
|
||||
|
||||
Raises:
|
||||
ValueError: ``init`` was True although the plugin manager was already initialized, or it was False although
|
||||
the plugin manager was not yet initialized.
|
||||
"""
|
||||
|
||||
global _instance
|
||||
if _instance is None:
|
||||
if init:
|
||||
if _instance is not None:
|
||||
raise ValueError("Plugin Manager already initialized")
|
||||
|
||||
if plugin_folders is None:
|
||||
plugin_folders = (settings().getBaseFolder("plugins"), os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "plugins")))
|
||||
if plugin_types is None:
|
||||
|
|
@ -51,10 +98,59 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
|
|||
|
||||
|
||||
def plugin_settings(plugin_key, defaults=None):
|
||||
"""
|
||||
Factory method for creating a :class:`PluginSettings` instance.
|
||||
|
||||
Arguments:
|
||||
plugin_key (string): The plugin identifier for which to create the settings instance.
|
||||
defaults (dict): The default settings for the plugin.
|
||||
|
||||
Returns:
|
||||
PluginSettings: A fully initialized :class:`PluginSettings` instance to be used to access the plugin's
|
||||
settings
|
||||
"""
|
||||
return PluginSettings(settings(), plugin_key, defaults=defaults)
|
||||
|
||||
|
||||
def call_plugin(types, method, args=None, kwargs=None, callback=None, error_callback=None):
|
||||
"""
|
||||
Helper method to invoke the indicated ``method`` on all registered plugin implementations implementing the
|
||||
indicated ``types``. Allows providing method arguments and registering callbacks to call in case of success
|
||||
and/or failure of each call which can be used to return individual results to the calling code.
|
||||
|
||||
Example:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def my_success_callback(name, plugin, result):
|
||||
print("{name} was called successfully and returned {result!r}".format(**locals()))
|
||||
|
||||
def my_error_callback(name, plugin, exc):
|
||||
print("{name} raised an exception: {exc!s}".format(**locals()))
|
||||
|
||||
octoprint.plugin.call_plugin(
|
||||
[octoprint.plugin.StartupPlugin],
|
||||
"on_startup",
|
||||
args=(my_host, my_port),
|
||||
callback=my_success_callback,
|
||||
error_callback=my_error_callback
|
||||
)
|
||||
|
||||
Arguments:
|
||||
types (list): A list of plugin implementation types to match against.
|
||||
method (string): Name of the method to call on all matching implementations.
|
||||
args (tuple): A tuple containing the arguments to supply to the called ``method``. Optional.
|
||||
kwargs (dict): A dictionary containing the keyword arguments to supply to the called ``method``. Optional.
|
||||
callback (function): A callback to invoke after an implementation has been called successfully. Will be called
|
||||
with the three arguments ``name``, ``plugin`` and ``result``. ``name`` will be the plugin identifier,
|
||||
``plugin`` the plugin implementation instance itself and ``result`` the result returned from the
|
||||
``method`` invocation.
|
||||
error_callback (function): A callback to invoke after the call of an implementation resulted in an exception.
|
||||
Will be called with the three arguments ``name``, ``plugin`` and ``exc``. ``name`` will be the plugin
|
||||
identifier, ``plugin`` the plugin implementation instance itself and ``exc`` the caught exception.
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(types, (list, tuple)):
|
||||
types = [types]
|
||||
if args is None:
|
||||
|
|
@ -69,14 +165,53 @@ def call_plugin(types, method, args=None, kwargs=None, callback=None, error_call
|
|||
result = getattr(plugin, method)(*args, **kwargs)
|
||||
if callback:
|
||||
callback(name, plugin, result)
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
logging.getLogger(__name__).exception("Error while calling plugin %s" % name)
|
||||
if error_callback:
|
||||
error_callback(name, plugin, e)
|
||||
error_callback(name, plugin, exc)
|
||||
|
||||
|
||||
class PluginSettings(object):
|
||||
"""
|
||||
The :class:`PluginSettings` class is the interface for plugins to their own or globally defined settings.
|
||||
|
||||
It provides a couple of convenience methods for directly accessing plugin settings via the regular
|
||||
:class:`octoprint.settings.Settings` interfaces as well as means to access plugin specific folder locations.
|
||||
|
||||
.. method:: get(path, merged=False, asdict=False)
|
||||
|
||||
Retrieves a raw key from the settings for ``path``, optionally merging the raw value with the default settings
|
||||
if ``merged`` is set to True.
|
||||
|
||||
:param list path: a list of path elements to navigate to the settings value
|
||||
:param boolean merged: whether to merge the returned result with the default settings (True) or not (False, default)
|
||||
:return: the retrieved settings value
|
||||
|
||||
.. method:: get_int(path)
|
||||
|
||||
.. method:: get_float(path)
|
||||
|
||||
.. method:: get_boolean(path)
|
||||
|
||||
.. method:: set(path, value, force=False)
|
||||
|
||||
.. method:: set_int(path, value, force=False)
|
||||
|
||||
.. method:: set_float(path, value, force=False)
|
||||
|
||||
.. method:: set_boolean(path, value, force=False)
|
||||
"""
|
||||
|
||||
def __init__(self, settings, plugin_key, defaults=None):
|
||||
"""
|
||||
Initializes the object with the provided :class:`octoprint.settings.Settings` manager as ``settings``, using
|
||||
the ``plugin_key`` and optional ``defaults``.
|
||||
|
||||
:param settings:
|
||||
:param plugin_key:
|
||||
:param defaults:
|
||||
:return:
|
||||
"""
|
||||
self.settings = settings
|
||||
self.plugin_key = plugin_key
|
||||
|
||||
|
|
@ -119,39 +254,30 @@ class PluginSettings(object):
|
|||
|
||||
def global_get(self, path, **kwargs):
|
||||
return self.settings.get(path, **kwargs)
|
||||
globalGet = deprecated("globalGet has been renamed to global_get")(global_get)
|
||||
|
||||
def global_get_int(self, path, **kwargs):
|
||||
return self.settings.getInt(path, **kwargs)
|
||||
globalGetInt = deprecated("globalGetInt has been renamed to global_get_int")(global_get_int)
|
||||
|
||||
def global_get_float(self, path, **kwargs):
|
||||
return self.settings.getFloat(path, **kwargs)
|
||||
globalGetFloat = deprecated("globalGetFloat has been renamed to global_get_float")(global_get_float)
|
||||
|
||||
def global_get_boolean(self, path, **kwargs):
|
||||
return self.settings.getBoolean(path, **kwargs)
|
||||
globalGetBoolean = deprecated("globalGetBoolean has been renamed to global_get_boolean")(global_get_boolean)
|
||||
|
||||
def global_set(self, path, value, **kwargs):
|
||||
self.settings.set(path, value, **kwargs)
|
||||
globalSet = deprecated("globalSet has been renamed to global_set")(global_set)
|
||||
|
||||
def global_set_int(self, path, value, **kwargs):
|
||||
self.settings.setInt(path, value, **kwargs)
|
||||
globalSetInt = deprecated("globalSetInt has been renamed to global_set_int")(global_set_int)
|
||||
|
||||
def global_set_float(self, path, value, **kwargs):
|
||||
self.settings.setFloat(path, value, **kwargs)
|
||||
globalSetFloat = deprecated("globalSetFloat has been renamed to global_set_float")(global_set_float)
|
||||
|
||||
def global_set_boolean(self, path, value, **kwargs):
|
||||
self.settings.setBoolean(path, value, **kwargs)
|
||||
globalSetBoolean = deprecated("globalSetBoolean has been renamed to global_set_boolean")(global_set_boolean)
|
||||
|
||||
def global_get_basefolder(self, folder_type, **kwargs):
|
||||
return self.settings.getBaseFolder(folder_type, **kwargs)
|
||||
globalGetBaseFolder = deprecated("globalGetBaseFolder has been renamed to global_get_basefolder")(global_get_basefolder)
|
||||
|
||||
def get_plugin_logfile_path(self, postfix=None):
|
||||
filename = "plugin_" + self.plugin_key
|
||||
|
|
@ -159,7 +285,6 @@ class PluginSettings(object):
|
|||
filename += "_" + postfix
|
||||
filename += ".log"
|
||||
return os.path.join(self.settings.getBaseFolder("logs"), filename)
|
||||
getPluginLogfilePath = deprecated("getPluginLogfilePath has been renamed to get_plugin_logfile_path")(get_plugin_logfile_path)
|
||||
|
||||
def __getattr__(self, item):
|
||||
all_access_methods = self.access_methods.keys() + self.deprecated_access_methods.keys()
|
||||
|
|
@ -179,7 +304,43 @@ class PluginSettings(object):
|
|||
def _func(*args, **kwargs):
|
||||
return orig_func(*args_mapper(args), **kwargs_mapper(kwargs))
|
||||
_func.__name__ = item
|
||||
_func.__doc__ = orig_func.__doc__ if "__doc__" in dir(orig_func) else None
|
||||
|
||||
return _func
|
||||
|
||||
return getattr(self.settings, item)
|
||||
|
||||
##~~ deprecated methods follow
|
||||
|
||||
# TODO: Remove with release of 1.2.0
|
||||
|
||||
globalGet = deprecated("globalGet has been renamed to global_get",
|
||||
includedoc="Replaced by :func:`global_get`",
|
||||
since="1.2.0-dev-546")(global_get)
|
||||
globalGetInt = deprecated("globalGetInt has been renamed to global_get_int",
|
||||
includedoc="Replaced by :func:`global_get_int`",
|
||||
since="1.2.0-dev-546")(global_get_int)
|
||||
globalGetFloat = deprecated("globalGetFloat has been renamed to global_get_float",
|
||||
includedoc="Replaced by :func:`global_get_float`",
|
||||
since="1.2.0-dev-546")(global_get_float)
|
||||
globalGetBoolean = deprecated("globalGetBoolean has been renamed to global_get_boolean",
|
||||
includedoc="Replaced by :func:`global_get_boolean`",
|
||||
since="1.2.0-dev-546")(global_get_boolean)
|
||||
globalSet = deprecated("globalSet has been renamed to global_set",
|
||||
includedoc="Replaced by :func:`global_set`",
|
||||
since="1.2.0-dev-546")(global_set)
|
||||
globalSetInt = deprecated("globalSetInt has been renamed to global_set_int",
|
||||
includedoc="Replaced by :func:`global_set_int`",
|
||||
since="1.2.0-dev-546")(global_set_int)
|
||||
globalSetFloat = deprecated("globalSetFloat has been renamed to global_set_float",
|
||||
includedoc="Replaced by :func:`global_set_float`",
|
||||
since="1.2.0-dev-546")(global_set_float)
|
||||
globalSetBoolean = deprecated("globalSetBoolean has been renamed to global_set_boolean",
|
||||
includedoc="Replaced by :func:`global_set_boolean`",
|
||||
since="1.2.0-dev-546")(global_set_boolean)
|
||||
globalGetBaseFolder = deprecated("globalGetBaseFolder has been renamed to global_get_basefolder",
|
||||
includedoc="Replaced by :func:`global_get_basefolder`",
|
||||
since="1.2.0-dev-546")(global_get_basefolder)
|
||||
getPluginLogfilePath = deprecated("getPluginLogfilePath has been renamed to get_plugin_logfile_path",
|
||||
includedoc="Replaced by :func:`get_plugin_logfile_path`",
|
||||
since="1.2.0-dev-546")(get_plugin_logfile_path)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
In this module resides the core data structures and logic of the plugin system. It is implemented in an OctoPrint-agnostic
|
||||
way and could be extracted into a separate Python module in the future.
|
||||
|
||||
.. autoclass:: PluginManager
|
||||
|
||||
.. autoclass:: PluginInfo
|
||||
|
||||
.. autoclass:: Plugin
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
|
|
@ -13,6 +25,15 @@ import logging
|
|||
|
||||
|
||||
class PluginInfo(object):
|
||||
"""
|
||||
The :class:`PluginInfo` class wraps all available information about a registered plugin.
|
||||
|
||||
This includes its meta data (like name, description, version, etc) as well as the actual plugin extensions like
|
||||
implementations, hooks and helpers.
|
||||
|
||||
It works on Python module objects and extracts the relevant data from those via accessing the
|
||||
:ref:`control properties <sec-plugins-infrastructure-controlproperties>`.
|
||||
"""
|
||||
|
||||
attr_name = '__plugin_name__'
|
||||
|
||||
|
|
@ -125,6 +146,12 @@ class PluginInfo(object):
|
|||
|
||||
|
||||
class PluginManager(object):
|
||||
"""
|
||||
The :class:`PluginManager` is the central component for finding, loading and accessing plugins provided to the
|
||||
system.
|
||||
|
||||
It is able to discover plugins both through possible file system locations as well as customizable entry points.
|
||||
"""
|
||||
|
||||
def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_prefix=None, plugin_disabled_list=None):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
|
@ -405,5 +432,34 @@ class PluginManager(object):
|
|||
|
||||
|
||||
class Plugin(object):
|
||||
"""
|
||||
The parent class of all plugin implementations.
|
||||
|
||||
.. attribute:: _identifier
|
||||
|
||||
The identifier of the plugin. Injected by the plugin core system upon initialization of the implementation.
|
||||
|
||||
.. attribute:: _plugin_name
|
||||
|
||||
The name of the plugin. Injected by the plugin core system upon initialization of the implementation.
|
||||
|
||||
.. attribute:: _plugin_version
|
||||
|
||||
The version of the plugin. Injected by the plugin core system upon initialization of the implementation.
|
||||
|
||||
.. attribute:: _basefolder
|
||||
|
||||
The base folder of the plugin. Injected by the plugin core system upon initialization of the implementation.
|
||||
|
||||
.. attribute:: _logger
|
||||
|
||||
The logger instance to use, with the logging name set to the :attr:`PluginManager.logging_prefix` of the
|
||||
:class:`PluginManager` concatenated with :attr:`_identifier`. Injected by the plugin core system upon
|
||||
initialization of the implementation.
|
||||
"""
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
Called by the plugin core after performing all injections.
|
||||
"""
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,4 +1,15 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
This module bundles all of OctoPrint's supported plugin implementation types as well as their common parent
|
||||
class, :class:`OctoPrintPlugin`.
|
||||
|
||||
Please note that the plugin implementation types are documented in the section
|
||||
:ref:`Available plugin mixins <sec-plugins-mixins>`.
|
||||
|
||||
.. autoclass: OctoPrintPlugin
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
|
|
@ -9,7 +20,29 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
|||
from .core import Plugin
|
||||
|
||||
|
||||
class StartupPlugin(Plugin):
|
||||
class OctoPrintPlugin(Plugin):
|
||||
"""
|
||||
.. attribute:: _plugin_manager
|
||||
|
||||
.. attribute:: _printer_profile_manager
|
||||
|
||||
.. attribute:: _event_bus
|
||||
|
||||
.. attribute:: _analysis_queue
|
||||
|
||||
.. attribute:: _slicing_manager
|
||||
|
||||
.. attribute:: _file_manager
|
||||
|
||||
.. attribute:: _printer
|
||||
|
||||
.. attribute:: _app_session_manager
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StartupPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
The ``StartupPlugin`` allows hooking into the startup of OctoPrint. It can be used to start up additional services
|
||||
on or just after the startup of the server.
|
||||
|
|
@ -37,7 +70,7 @@ class StartupPlugin(Plugin):
|
|||
pass
|
||||
|
||||
|
||||
class ShutdownPlugin(Plugin):
|
||||
class ShutdownPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
The ``ShutdownPlugin`` allows hooking into the shutdown of OctoPrint. It's usually used in conjunction with the
|
||||
:class:`StartupPlugin` mixin, to cleanly shut down additional services again that where started by the :class:`StartupPlugin`
|
||||
|
|
@ -51,14 +84,14 @@ class ShutdownPlugin(Plugin):
|
|||
pass
|
||||
|
||||
|
||||
class AssetPlugin(Plugin):
|
||||
class AssetPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
The ``AssetPlugin`` mixin allows plugins to define additional static assets such as Javascript or CSS files to
|
||||
be automatically embedded into the pages delivered by the server to be used within the client sided part of
|
||||
the plugin.
|
||||
|
||||
A typical usage of the ``AssetPlugin`` functionality is to embed a custom view model to be used by templates injected
|
||||
through :class:`TemplatePlugin`s.
|
||||
through a :class:`TemplatePlugin`.
|
||||
"""
|
||||
|
||||
def get_asset_folder(self):
|
||||
|
|
@ -104,7 +137,7 @@ class AssetPlugin(Plugin):
|
|||
return dict()
|
||||
|
||||
|
||||
class TemplatePlugin(Plugin):
|
||||
class TemplatePlugin(OctoPrintPlugin):
|
||||
"""
|
||||
Using the ``TemplatePlugin`` mixin plugins may inject their own components into the OctoPrint web interface.
|
||||
|
||||
|
|
@ -408,7 +441,7 @@ class TemplatePlugin(Plugin):
|
|||
return os.path.join(self._basefolder, "templates")
|
||||
|
||||
|
||||
class SimpleApiPlugin(Plugin):
|
||||
class SimpleApiPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
Utilizing the ``SimpleApiPlugin`` mixin plugins may implement a simple API based around one GET resource and one
|
||||
resource accepting JSON commands POSTed to it. This is the easy alternative for plugin's which don't need the
|
||||
|
|
@ -543,7 +576,7 @@ class SimpleApiPlugin(Plugin):
|
|||
return None
|
||||
|
||||
|
||||
class BlueprintPlugin(Plugin):
|
||||
class BlueprintPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
The ``BlueprintPlugin`` mixin allows plugins to define their own full fledged endpoints for whatever purpose,
|
||||
be it a more sophisticated API than what is possible via the :class:`SimpleApiPlugin` or a custom web frontend.
|
||||
|
|
@ -646,7 +679,7 @@ class BlueprintPlugin(Plugin):
|
|||
return True
|
||||
|
||||
|
||||
class SettingsPlugin(Plugin):
|
||||
class SettingsPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
Including the ``SettingsPlugin`` mixin allows plugins to store and retrieve their own settings within OctoPrint's
|
||||
configuration.
|
||||
|
|
@ -697,6 +730,11 @@ class SettingsPlugin(Plugin):
|
|||
|
||||
Of course, you are always free to completely override both :func:`on_settings_load` and :func:`on_settings_save` if the
|
||||
default implementations do not fit your requirements.
|
||||
|
||||
.. attribute:: _settings
|
||||
|
||||
The :class:`~octoprint.plugin.PluginSettings` instance to use for accessing the plugin's settings. Injected by
|
||||
the plugin core system upon initialization of the implementation.
|
||||
"""
|
||||
|
||||
def on_settings_load(self):
|
||||
|
|
@ -751,7 +789,7 @@ class SettingsPlugin(Plugin):
|
|||
return dict()
|
||||
|
||||
|
||||
class EventHandlerPlugin(Plugin):
|
||||
class EventHandlerPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
The ``EventHandlerPlugin`` mixin allows OctoPrint plugins to react to any of :ref:`OctoPrint's events <sec-events>`.
|
||||
OctoPrint will call the :func:`on_event` method for any event fired on its internal event bus, supplying the
|
||||
|
|
@ -774,7 +812,7 @@ class EventHandlerPlugin(Plugin):
|
|||
pass
|
||||
|
||||
|
||||
class SlicerPlugin(Plugin):
|
||||
class SlicerPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
Via the ``SlicerPlugin`` mixin plugins can add support for slicing engines to be used by OctoPrint.
|
||||
|
||||
|
|
@ -835,10 +873,10 @@ class SlicerPlugin(Plugin):
|
|||
``False`` (defaults to ``True``), an ``IOError`` should be raised.
|
||||
|
||||
:param string path: the path to which to save the profile
|
||||
:param :class:`SlicingProfile` profile: the profile to save
|
||||
:param SlicingProfile profile: the profile to save
|
||||
:param bool allow_overwrite: whether to allow to overwrite an existing profile at the indicated path (``True``, default)
|
||||
or not (``False``) - if a profile already exists on the path and this is ``False``
|
||||
and :class:`IOError` should be raised
|
||||
or not (``False``) - if a profile already exists on the path and this is ``False``
|
||||
and :class:`IOError` should be raised
|
||||
:param dict overrides: profile overrides to apply to the ``profile`` before saving it
|
||||
"""
|
||||
pass
|
||||
|
|
@ -881,7 +919,7 @@ class SlicerPlugin(Plugin):
|
|||
pass
|
||||
|
||||
|
||||
class ProgressPlugin(Plugin):
|
||||
class ProgressPlugin(OctoPrintPlugin):
|
||||
"""
|
||||
Via the ``ProgressPlugin`` mixing plugins can let themselves be called upon progress in print jobs or slicing jobs,
|
||||
limited to minimally 1% steps.
|
||||
|
|
@ -911,7 +949,7 @@ class ProgressPlugin(Plugin):
|
|||
pass
|
||||
|
||||
|
||||
class AppPlugin(Plugin):
|
||||
class AppPlugin(OctoPrintPlugin):
|
||||
def get_additional_apps(self):
|
||||
return []
|
||||
|
||||
|
|
|
|||
|
|
@ -719,7 +719,7 @@ class Printer(comm.MachineComPrintCallback):
|
|||
self.refreshSdFiles(blocking=True)
|
||||
existingSdFiles = map(lambda x: x[0], self._comm.getSdFiles())
|
||||
|
||||
remoteName = util.get_dos_filename(filename, existingSdFiles)
|
||||
remoteName = util.get_dos_filename(filename, existing_filenames=existingSdFiles, extension="gco")
|
||||
self._timeEstimationData = TimeEstimationHelper()
|
||||
self._comm.startFileTransfer(absolutePath, filename, "/" + remoteName)
|
||||
|
||||
|
|
|
|||
|
|
@ -533,23 +533,33 @@ class Server():
|
|||
printer = Printer(fileManager, analysisQueue, printerProfileManager)
|
||||
appSessionManager = util.flask.AppSessionManager()
|
||||
|
||||
def plugin_settings_factory(name, implementation):
|
||||
def octoprint_plugin_inject_factory(name, implementation):
|
||||
if not isinstance(implementation, octoprint.plugin.OctoPrintPlugin):
|
||||
return None
|
||||
return dict(
|
||||
plugin_manager=pluginManager,
|
||||
printer_profile_manager=printerProfileManager,
|
||||
event_bus=eventManager,
|
||||
analysis_queue=analysisQueue,
|
||||
slicing_manager=slicingManager,
|
||||
file_manager=fileManager,
|
||||
printer=printer,
|
||||
app_session_manager=appSessionManager
|
||||
)
|
||||
|
||||
def settings_plugin_inject_factory(name, implementation):
|
||||
if not isinstance(implementation, octoprint.plugin.SettingsPlugin):
|
||||
return None
|
||||
default_settings = implementation.get_settings_defaults()
|
||||
plugin_settings = octoprint.plugin.plugin_settings(name, defaults=default_settings)
|
||||
return dict(settings=plugin_settings)
|
||||
|
||||
pluginManager.initialize_implementations(additional_injects=dict(
|
||||
plugin_manager=pluginManager,
|
||||
printer_profile_manager=printerProfileManager,
|
||||
event_bus=eventManager,
|
||||
analysis_queue=analysisQueue,
|
||||
slicing_manager=slicingManager,
|
||||
file_manager=fileManager,
|
||||
printer=printer,
|
||||
app_session_manager=appSessionManager
|
||||
), additional_inject_factories=[plugin_settings_factory])
|
||||
pluginManager.initialize_implementations(
|
||||
additional_inject_factories=[
|
||||
octoprint_plugin_inject_factory,
|
||||
settings_plugin_inject_factory
|
||||
]
|
||||
)
|
||||
slicingManager.initialize()
|
||||
|
||||
# configure additional template folders for jinja2
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
This module bundles commonly used utility methods or helper classes that are used in multiple places withing
|
||||
OctoPrint's source code.
|
||||
"""
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
|
||||
|
|
@ -16,7 +21,7 @@ import warnings
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
def warning_decorator_factory(warning_type):
|
||||
def specific_warning(message, stacklevel=1):
|
||||
def specific_warning(message, stacklevel=1, since=None, includedoc=None):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def func_wrapper(*args, **kwargs):
|
||||
|
|
@ -24,19 +29,75 @@ def warning_decorator_factory(warning_type):
|
|||
# func_wrapper in the log, instead of our caller (which is the real caller of the wrapped function)
|
||||
warnings.warn(message, warning_type, stacklevel=stacklevel + 1)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if includedoc is not None and since is not None:
|
||||
docstring = "\n.. deprecated:: {since}\n {message}\n\n".format(since=since, message=includedoc)
|
||||
if hasattr(func_wrapper, "__doc__") and func_wrapper.__doc__ is not None:
|
||||
docstring = func_wrapper.__doc__ + "\n" + docstring
|
||||
func_wrapper.__doc__ = docstring
|
||||
|
||||
return func_wrapper
|
||||
|
||||
return decorator
|
||||
return specific_warning
|
||||
|
||||
deprecated = warning_decorator_factory(DeprecationWarning)
|
||||
"""
|
||||
A decorator for deprecated methods. Logs a deprecation warning via Python's `:mod:`warnings` module including the
|
||||
supplied ``message``. The call stack level used (for adding the source location of the offending call to the
|
||||
warning) can be overridden using the optional ``stacklevel`` parameter. If both ``since`` and ``includedoc`` are
|
||||
provided, a deprecation warning will also be added to the function's docstring by providing or extending its ``__doc__``
|
||||
property.
|
||||
|
||||
Arguments:
|
||||
message (string): The message to include in the deprecation warning.
|
||||
stacklevel (int): Stack level for including the caller of the offending method in the logged warning. Defaults to 1,
|
||||
meaning the direct caller of the method. It might make sense to increase this in case of the function call
|
||||
happening dynamically from a fixed position to not shadow the real caller (e.g. in case of overridden
|
||||
``getattr`` methods).
|
||||
includedoc (string): Message about the deprecation to include in the wrapped function's docstring.
|
||||
since (string): Version since when the function was deprecated, must be present for the docstring to get extended.
|
||||
|
||||
Returns:
|
||||
function: The wrapped function with the deprecation warnings in place.
|
||||
"""
|
||||
|
||||
pending_deprecation = warning_decorator_factory(PendingDeprecationWarning)
|
||||
"""
|
||||
A decorator for methods pending deprecation. Logs a pending deprecation warning via Python's `:mod:`warnings` module
|
||||
including the supplied ``message``. The call stack level used (for adding the source location of the offending call to
|
||||
the warning) can be overridden using the optional ``stacklevel`` parameter. If both ``since`` and ``includedoc`` are
|
||||
provided, a deprecation warning will also be added to the function's docstring by providing or extending its ``__doc__``
|
||||
property.
|
||||
|
||||
Arguments:
|
||||
message (string): The message to include in the deprecation warning.
|
||||
stacklevel (int): Stack level for including the caller of the offending method in the logged warning. Defaults to 1,
|
||||
meaning the direct caller of the method. It might make sense to increase this in case of the function call
|
||||
happening dynamically from a fixed position to not shadow the real caller (e.g. in case of overridden
|
||||
``getattr`` methods).
|
||||
includedoc (string): Message about the deprecation to include in the wrapped function's docstring.
|
||||
since (string): Version since when the function was deprecated, must be present for the docstring to get extended.
|
||||
|
||||
Returns:
|
||||
function: The wrapped function with the deprecation warnings in place.
|
||||
"""
|
||||
|
||||
def get_formatted_size(num):
|
||||
"""
|
||||
Taken from http://stackoverflow.com/a/1094933/2028598
|
||||
Formats the given byte count as a human readable rounded size expressed in the most pressing unit among B(ytes),
|
||||
K(ilo)B(ytes), M(ega)B(ytes), G(iga)B(ytes) and T(era)B(ytes), with one decimal place.
|
||||
|
||||
Based on http://stackoverflow.com/a/1094933/2028598
|
||||
|
||||
Arguments:
|
||||
num (int): The byte count to format
|
||||
|
||||
Returns:
|
||||
string: The formatted byte count.
|
||||
"""
|
||||
for x in ["bytes","KB","MB","GB"]:
|
||||
|
||||
for x in ["B","KB","MB","GB"]:
|
||||
if num < 1024.0:
|
||||
return "%3.1f%s" % (num, x)
|
||||
num /= 1024.0
|
||||
|
|
@ -44,10 +105,31 @@ def get_formatted_size(num):
|
|||
|
||||
|
||||
def is_allowed_file(filename, extensions):
|
||||
return "." in filename and filename.rsplit(".", 1)[1] in extensions
|
||||
"""
|
||||
Determines if the provided ``filename`` has one of the supplied ``extensions``. The check is done case-insensitive.
|
||||
|
||||
Arguments:
|
||||
filename (string): The file name to check against the extensions.
|
||||
extensions (list): The extensions to check against, a list of strings
|
||||
|
||||
Return:
|
||||
boolean: True if the file name's extension matches one of the allowed extensions, False otherwise.
|
||||
"""
|
||||
|
||||
return "." in filename and filename.rsplit(".", 1)[1].lower() in map(str.lower, extensions)
|
||||
|
||||
|
||||
def get_formatted_timedelta(d):
|
||||
"""
|
||||
Formats a timedelta instance as "HH:MM:ss" and returns the resulting string.
|
||||
|
||||
Arguments:
|
||||
d (datetime.timedelta): The timedelta instance to format
|
||||
|
||||
Returns:
|
||||
string: The timedelta formatted as "HH:MM:ss"
|
||||
"""
|
||||
|
||||
if d is None:
|
||||
return None
|
||||
hours = d.days * 24 + d.seconds // 3600
|
||||
|
|
@ -57,6 +139,16 @@ def get_formatted_timedelta(d):
|
|||
|
||||
|
||||
def get_formatted_datetime(d):
|
||||
"""
|
||||
Formats a datetime instance as "YYYY-mm-dd HH:MM" and returns the resulting string.
|
||||
|
||||
Arguments:
|
||||
d (datetime.datetime): The datetime instance to format
|
||||
|
||||
Returns:
|
||||
string: The datetime formatted as "YYYY-mm-dd HH:MM"
|
||||
"""
|
||||
|
||||
if d is None:
|
||||
return None
|
||||
|
||||
|
|
@ -65,8 +157,20 @@ def get_formatted_datetime(d):
|
|||
|
||||
def get_class(name):
|
||||
"""
|
||||
Taken from http://stackoverflow.com/a/452981/2028598
|
||||
Retrieves the class object for a given fully qualified class name.
|
||||
|
||||
Taken from http://stackoverflow.com/a/452981/2028598.
|
||||
|
||||
Arguments:
|
||||
name (string): The fully qualified class name, including all modules separated by ``.``
|
||||
|
||||
Returns:
|
||||
type: The class if it could be found.
|
||||
|
||||
Raises:
|
||||
AttributeError: The class could not be found.
|
||||
"""
|
||||
|
||||
parts = name.split(".")
|
||||
module = ".".join(parts[:-1])
|
||||
m = __import__(module)
|
||||
|
|
@ -76,14 +180,33 @@ def get_class(name):
|
|||
|
||||
|
||||
def get_exception_string():
|
||||
"""
|
||||
Retrieves the exception info of the last raised exception and returns it as a string formatted as
|
||||
``<exception type>: <exception message> @ <source file>:<function name>:<line number>'.
|
||||
|
||||
Returns:
|
||||
string: The formatted exception information.
|
||||
"""
|
||||
|
||||
locationInfo = traceback.extract_tb(sys.exc_info()[2])[0]
|
||||
return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])
|
||||
|
||||
|
||||
def get_free_bytes(path):
|
||||
"""
|
||||
Retrieves the number of free bytes on the partition ``path`` is located at and returns it. Works on both Windows and
|
||||
*nix.
|
||||
|
||||
Taken from http://stackoverflow.com/a/2372171/2028598
|
||||
|
||||
Arguments:
|
||||
path (string): The path for which to check the remaining partition space.
|
||||
|
||||
Returns:
|
||||
int: The amount of bytes still left on the partition.
|
||||
"""
|
||||
|
||||
path = os.path.abspath(path)
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
freeBytes = ctypes.c_ulonglong(0)
|
||||
|
|
@ -94,28 +217,109 @@ def get_free_bytes(path):
|
|||
return st.f_bavail * st.f_frsize
|
||||
|
||||
|
||||
def get_dos_filename(input, existingFilenames, extension=None):
|
||||
if input is None:
|
||||
def get_dos_filename(origin, existing_filenames=None, extension=None, **kwargs):
|
||||
"""
|
||||
Converts the provided input filename to a 8.3 DOS compatible filename. If ``existing_filenames`` is provided, the
|
||||
conversion result will be guaranteed not to collide with any of the filenames provided thus.
|
||||
|
||||
Uses :func:`find_collision_free_name` internally.
|
||||
|
||||
Arguments:
|
||||
input (string): The original filename incl. extension to convert to the 8.3 format.
|
||||
existing_filenames (list): A list of existing filenames with which the generated 8.3 name must not collide.
|
||||
Optional.
|
||||
extension (string): The .3 file extension to use for the generated filename. If not provided, the extension of
|
||||
the provided ``filename`` will simply be truncated to 3 characters.
|
||||
kwargs (dict): Additional keyword arguments to provide to :func:`find_collision_free_name`.
|
||||
|
||||
Returns:
|
||||
string: A 8.3 compatible translation of the original filename, not colliding with the optionally provided
|
||||
``existing_filenames`` and with the provided ``extension`` or the original extension shortened to
|
||||
a maximum of 3 characters.
|
||||
|
||||
Raises:
|
||||
ValueError: No 8.3 compatible name could be found that doesn't collide with the provided ``existing_filenames``.
|
||||
"""
|
||||
|
||||
if origin is None:
|
||||
return None
|
||||
|
||||
if existing_filenames is None:
|
||||
existing_filenames = []
|
||||
|
||||
filename, ext = os.path.splitext(origin)
|
||||
if extension is None:
|
||||
extension = "gco"
|
||||
extension = ext
|
||||
|
||||
filename, ext = input.rsplit(".", 1)
|
||||
return find_collision_free_name(filename, extension, existingFilenames)
|
||||
return find_collision_free_name(filename, extension, existing_filenames, **kwargs)
|
||||
|
||||
|
||||
def find_collision_free_name(input, extension, existingFilenames):
|
||||
filename = re.sub(r"\s+", "_", input.lower().translate({ord(i):None for i in ".\"/\\[]:;=,"}))
|
||||
def find_collision_free_name(filename, extension, existing_filenames, max_power=2):
|
||||
"""
|
||||
Tries to find a collision free translation of "<filename>.<extension>" to the 8.3 DOS compatible format,
|
||||
preventing collisions with any of the ``existing_filenames``.
|
||||
|
||||
First strips all of ``."/\\[]:;=,`` from the filename and extensions, converts them to lower case and truncates
|
||||
the ``extension`` to a maximum length of 3 characters.
|
||||
|
||||
If the filename is already equal or less than 8 characters in length after that procedure and "<filename>.<extension>"
|
||||
are not contained in the ``existing_files``, that concatenation will be returned as the result.
|
||||
|
||||
If not, the following algorithm will be applied to try to find a collision free name::
|
||||
|
||||
set counter := power := 1
|
||||
while counter < 10^max_power:
|
||||
set truncated := substr(filename, 0, 6 - power + 1) + "~" + counter
|
||||
set result := "<truncated>.<extension>"
|
||||
if result is collision free:
|
||||
return result
|
||||
counter++
|
||||
if counter >= 10 ** power:
|
||||
power++
|
||||
raise ValueError
|
||||
|
||||
This will basically -- for a given original filename of ``some_filename`` and an extension of ``gco`` -- iterate
|
||||
through names of the format ``some_f~1.gco``, ``some_f~2.gco``, ..., ``some_~10.gco``, ``some_~11.gco``, ...,
|
||||
``<prefix>~<n>.gco`` for ``n`` less than 10 ^ ``max_power``, returning as soon as one is found that is not colliding.
|
||||
|
||||
Arguments:
|
||||
filename (string): The filename without the extension to convert to 8.3.
|
||||
extension (string): The extension to convert to 8.3 -- will be truncated to 3 characters if it's longer than
|
||||
that.
|
||||
existing_filenames (list): A list of existing filenames to prevent name collisions with.
|
||||
max_power (int): Limits the possible attempts of generating a collision free name to 10 ^ ``max_power``
|
||||
variations. Defaults to 2, so the name generation will maximally reach ``<name>~99.<ext>`` before
|
||||
aborting and raising an exception.
|
||||
|
||||
Returns:
|
||||
string: A 8.3 representation of the provided original filename, ensured to not collide with the provided
|
||||
``existing_filenames``
|
||||
|
||||
Raises:
|
||||
ValueError: No collision free name could be found.
|
||||
"""
|
||||
|
||||
# TODO unit test!
|
||||
|
||||
def make_valid(text):
|
||||
return re.sub(r"\s+", "_", text.translate({ord(i):None for i in ".\"/\\[]:;=,"})).lower()
|
||||
|
||||
filename = make_valid(filename)
|
||||
extension = make_valid(extension)
|
||||
extension = extension[:3] if len(extension) > 3 else extension
|
||||
|
||||
if len(filename) <= 8 and not filename + "." + extension in existing_filenames:
|
||||
# early exit
|
||||
return filename + "." + extension
|
||||
|
||||
counter = 1
|
||||
power = 1
|
||||
while counter < (10 * power):
|
||||
while counter < (10 ** max_power):
|
||||
result = filename[:(6 - power + 1)] + "~" + str(counter) + "." + extension
|
||||
if result not in existingFilenames:
|
||||
if result not in existing_filenames:
|
||||
return result
|
||||
counter += 1
|
||||
if counter == 10 * power:
|
||||
if counter >= 10 ** power:
|
||||
power += 1
|
||||
|
||||
raise ValueError("Can't create a collision free filename")
|
||||
|
|
@ -132,8 +336,14 @@ def safe_rename(old, new, throw_error=False):
|
|||
|
||||
On other operating systems :func:`shutil.move` will be used instead.
|
||||
|
||||
@param old the path to the old file to be renamed
|
||||
@param new the path to the new file to be created/replaced
|
||||
Arguments:
|
||||
old (string): The path to the old file to be renamed.
|
||||
new (string): The path to the new file to be created/replaced.
|
||||
throw_error (boolean): Whether to throw an error upon errors during the renaming procedure (True) or not
|
||||
(False, default).
|
||||
|
||||
Raises:
|
||||
OSError: One of the renaming steps on windows failed and ``throw_error`` was True
|
||||
"""
|
||||
|
||||
if sys.platform == "win32":
|
||||
|
|
@ -163,7 +373,8 @@ def silent_remove(file):
|
|||
"""
|
||||
Silently removes a file. Does not raise an error if the file doesn't exist.
|
||||
|
||||
@param file the path of the file to be removed
|
||||
Arguments:
|
||||
file (string): The path of the file to be removed
|
||||
"""
|
||||
|
||||
try:
|
||||
|
|
@ -178,9 +389,13 @@ def sanitize_ascii(line):
|
|||
|
||||
def filter_non_ascii(line):
|
||||
"""
|
||||
Returns True if the line contains non-ascii characters, false otherwise
|
||||
Filter predicate to test if a line contains non ASCII characters.
|
||||
|
||||
@param line the line to test
|
||||
Arguments:
|
||||
line (string): The line to test
|
||||
|
||||
Returns:
|
||||
boolean: True if the line contains non ASCII characters, False otherwise.
|
||||
"""
|
||||
|
||||
try:
|
||||
|
|
@ -191,11 +406,18 @@ def filter_non_ascii(line):
|
|||
|
||||
|
||||
def dict_merge(a, b):
|
||||
'''recursively merges dict's. not just simple a['key'] = b['key'], if
|
||||
both a and bhave a key who's value is a dict then dict_merge is called
|
||||
on both values and the result stored in the returned dictionary.
|
||||
"""
|
||||
Recursively deep-merges two dictionaries.
|
||||
|
||||
Taken from https://www.xormedia.com/recursively-merge-dictionaries-in-python/'''
|
||||
Taken from https://www.xormedia.com/recursively-merge-dictionaries-in-python/
|
||||
|
||||
Arguments:
|
||||
a (dict): The dictionary to merge ``b`` into
|
||||
b (dict): The dictionary to merge into ``a``
|
||||
|
||||
Returns:
|
||||
dict: ``b`` deep-merged into ``a``
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
|
|
@ -211,6 +433,17 @@ def dict_merge(a, b):
|
|||
|
||||
|
||||
def dict_clean(a, b):
|
||||
"""
|
||||
Recursively deep-cleans ``b`` from ``a``, removing all keys and corresponding values from ``a`` that appear in
|
||||
``b``.
|
||||
|
||||
Arguments:
|
||||
a (dict): The dictionary to clean from ``b``.
|
||||
b (dict): The dictionary to clean ``b`` from.
|
||||
|
||||
Results:
|
||||
dict: A new dict based on ``a`` with all keys (and corresponding values) found in ``b`` removed.
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
if not isinstance(b, dict):
|
||||
|
|
@ -228,6 +461,24 @@ def dict_clean(a, b):
|
|||
|
||||
|
||||
def dict_contains_keys(a, b):
|
||||
"""
|
||||
Recursively deep-checks if ``a`` contains all keys found in ``b``.
|
||||
|
||||
Example::
|
||||
|
||||
>>> dict_contains_keys(dict(foo="bar", fnord=dict(a=1, b=2, c=3)), dict(foo="some_other_bar", fnord=dict(b=100)))
|
||||
True
|
||||
>>> dict_contains_keys(dict(foo="bar", fnord=dict(a=1, b=2, c=3)), dict(foo="some_other_bar", fnord=dict(b=100, d=20)))
|
||||
False
|
||||
|
||||
Arguments:
|
||||
a (dict): The dictionary to check for the keys from ``b``.
|
||||
b (dict): The dictionary whose keys to check ``a`` for.
|
||||
|
||||
Returns:
|
||||
boolean: True if all keys found in ``b`` are also present in ``a``, False otherwise.
|
||||
"""
|
||||
|
||||
if not isinstance(a, dict) or not isinstance(b, dict):
|
||||
return False
|
||||
|
||||
|
|
@ -240,11 +491,14 @@ def dict_contains_keys(a, b):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
class Object(object):
|
||||
pass
|
||||
|
||||
def interface_addresses(family=None):
|
||||
"""
|
||||
Retrieves all of the host's network interface addresses.
|
||||
"""
|
||||
|
||||
import netifaces
|
||||
if not family:
|
||||
family = netifaces.AF_INET
|
||||
|
|
@ -260,6 +514,10 @@ def interface_addresses(family=None):
|
|||
yield ifaddress["addr"]
|
||||
|
||||
def address_for_client(host, port):
|
||||
"""
|
||||
Determines the address of the network interface on this host needed to connect to the indicated client host and port.
|
||||
"""
|
||||
|
||||
import socket
|
||||
|
||||
for address in interface_addresses():
|
||||
|
|
|
|||
Loading…
Reference in a new issue