fix docstrings 7

This commit is contained in:
Matthias 2022-05-28 03:04:48 +02:00
parent bd4da62964
commit d3d0b76c58
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
6 changed files with 98 additions and 242 deletions

View File

@ -1,6 +1,4 @@
"""
JSON API for the plugin app
"""
"""JSON API for the plugin app"""
from django.conf import settings
from django.urls import include, re_path
@ -20,7 +18,7 @@ from plugin.registry import registry
class PluginList(generics.ListAPIView):
""" API endpoint for list of PluginConfig objects
"""API endpoint for list of PluginConfig objects
- GET: Return a list of all PluginConfig objects
"""
@ -79,7 +77,7 @@ class PluginList(generics.ListAPIView):
class PluginDetail(generics.RetrieveUpdateDestroyAPIView):
""" API detail endpoint for PluginConfig object
"""API detail endpoint for PluginConfig object
get:
Return a single PluginConfig object
@ -96,9 +94,8 @@ class PluginDetail(generics.RetrieveUpdateDestroyAPIView):
class PluginInstall(generics.CreateAPIView):
"""
Endpoint for installing a new plugin
"""
"""Endpoint for installing a new plugin"""
queryset = PluginConfig.objects.none()
serializer_class = PluginSerializers.PluginConfigInstallSerializer
@ -115,8 +112,7 @@ class PluginInstall(generics.CreateAPIView):
class PluginSettingList(generics.ListAPIView):
"""
List endpoint for all plugin related settings.
"""List endpoint for all plugin related settings.
- read only
- only accessible by staff users
@ -140,8 +136,7 @@ class PluginSettingList(generics.ListAPIView):
class PluginSettingDetail(generics.RetrieveUpdateAPIView):
"""
Detail endpoint for a plugin-specific setting.
"""Detail endpoint for a plugin-specific setting.
Note that these cannot be created or deleted via the API
"""
@ -150,13 +145,11 @@ class PluginSettingDetail(generics.RetrieveUpdateAPIView):
serializer_class = PluginSerializers.PluginSettingSerializer
def get_object(self):
"""
Lookup the plugin setting object, based on the URL.
The URL provides the 'slug' of the plugin, and the 'key' of the setting.
"""Lookup the plugin setting object, based on the URL.
The URL provides the 'slug' of the plugin, and the 'key' of the setting.
Both the 'slug' and 'key' must be valid, else a 404 error is raised
"""
plugin_slug = self.kwargs['plugin']
key = self.kwargs['key']

View File

@ -1,6 +1,4 @@
"""
Import helper for events
"""
"""Import helper for events"""
from plugin.base.event.events import (process_event, register_event,
trigger_event)

View File

@ -1,6 +1,5 @@
"""
Helpers for plugin app
"""
"""Helpers for plugin app"""
import inspect
import logging
import os
@ -20,9 +19,8 @@ logger = logging.getLogger('inventree')
# region logging / errors
class IntegrationPluginError(Exception):
"""
Error that encapsulates another error and adds the path / reference of the raising plugin
"""
"""Error that encapsulates another error and adds the path / reference of the raising plugin"""
def __init__(self, path, message):
self.path = path
self.message = message
@ -32,24 +30,20 @@ class IntegrationPluginError(Exception):
class MixinImplementationError(ValueError):
"""
Error if mixin was implemented wrong in plugin
"""Error if mixin was implemented wrong in plugin
Mostly raised if constant is missing
"""
pass
class MixinNotImplementedError(NotImplementedError):
"""
Error if necessary mixin function was not overwritten
"""
"""Error if necessary mixin function was not overwritten"""
pass
def log_error(error, reference: str = 'general'):
"""
Log an plugin error
"""
"""Log an plugin error"""
from plugin import registry
# make sure the registry is set up
@ -61,9 +55,7 @@ def log_error(error, reference: str = 'general'):
def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: str = ''):
"""
Handles an error and casts it as an IntegrationPluginError
"""
"""Handles an error and casts it as an IntegrationPluginError"""
package_path = traceback.extract_tb(error.__traceback__)[-1].filename
install_path = sysconfig.get_paths()["purelib"]
try:
@ -99,9 +91,7 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st
# region git-helpers
def get_git_log(path):
"""
Get dict with info of the last commit to file named in path
"""
"""Get dict with info of the last commit to file named in path"""
from plugin import registry
output = None
@ -122,8 +112,7 @@ def get_git_log(path):
def check_git_version():
"""returns if the current git version supports modern features"""
"""Returns if the current git version supports modern features"""
# get version string
try:
output = str(subprocess.check_output(['git', '--version'], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
@ -143,13 +132,11 @@ def check_git_version():
class GitStatus:
"""
Class for resolving git gpg singing state
"""
"""Class for resolving git gpg singing state"""
class Definition:
"""
Definition of a git gpg sing state
"""
"""Definition of a git gpg sing state"""
key: str = 'N'
status: int = 2
msg: str = ''
@ -172,8 +159,7 @@ class GitStatus:
# region plugin finders
def get_modules(pkg):
"""get all modules in a package"""
"""Get all modules in a package"""
context = {}
for loader, name, ispkg in pkgutil.walk_packages(pkg.__path__):
try:
@ -195,18 +181,16 @@ def get_modules(pkg):
def get_classes(module):
"""get all classes in a given module"""
"""Get all classes in a given module"""
return inspect.getmembers(module, inspect.isclass)
def get_plugins(pkg, baseclass):
"""
Return a list of all modules under a given package.
"""Return a list of all modules under a given package.
- Modules must be a subclass of the provided 'baseclass'
- Modules must have a non-empty NAME parameter
"""
plugins = []
modules = get_modules(pkg)
@ -225,10 +209,7 @@ def get_plugins(pkg, baseclass):
# region templates
def render_template(plugin, template_file, context=None):
"""
Locate and render a template file, available in the global template context.
"""
"""Locate and render a template file, available in the global template context."""
try:
tmp = template.loader.get_template(template_file)
except template.TemplateDoesNotExist:
@ -247,10 +228,7 @@ def render_template(plugin, template_file, context=None):
def render_text(text, context=None):
"""
Locate a raw string with provided context
"""
"""Locate a raw string with provided context"""
ctx = template.Context(context)
return template.Template(text).render(ctx)

View File

@ -1,6 +1,4 @@
"""
Plugin model definitions
"""
"""Plugin model definitions"""
import warnings
@ -14,8 +12,7 @@ from plugin import InvenTreePlugin, registry
class MetadataMixin(models.Model):
"""
Model mixin class which adds a JSON metadata field to a model,
"""Model mixin class which adds a JSON metadata field to a model,
for use by any (and all) plugins.
The intent of this mixin is to provide a metadata field on a model instance,
@ -37,8 +34,7 @@ class MetadataMixin(models.Model):
)
def get_metadata(self, key: str, backup_value=None):
"""
Finds metadata for this model instance, using the provided key for lookup
"""Finds metadata for this model instance, using the provided key for lookup
Args:
key: String key for requesting metadata. e.g. if a plugin is accessing the metadata, the plugin slug should be used
@ -46,22 +42,19 @@ class MetadataMixin(models.Model):
Returns:
Python dict object containing requested metadata. If no matching metadata is found, returns None
"""
if self.metadata is None:
return backup_value
return self.metadata.get(key, backup_value)
def set_metadata(self, key: str, data, commit=True):
"""
Save the provided metadata under the provided key.
"""Save the provided metadata under the provided key.
Args:
key: String key for saving metadata
data: Data object to save - must be able to be rendered as a JSON string
overwrite: If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted
"""
if self.metadata is None:
# Handle a null field value
self.metadata = {}
@ -73,14 +66,14 @@ class MetadataMixin(models.Model):
class PluginConfig(models.Model):
"""
A PluginConfig object holds settings for plugins.
"""A PluginConfig object holds settings for plugins.
Attributes:
key: slug of the plugin (this must be unique across all installed plugins!)
name: PluginName of the plugin - serves for a manual double check if the right plugin is used
active: Should the plugin be loaded?
"""
class Meta:
verbose_name = _("Plugin Configuration")
verbose_name_plural = _("Plugin Configurations")
@ -123,10 +116,7 @@ class PluginConfig(models.Model):
# functions
def __init__(self, *args, **kwargs):
"""
Override to set original state of the plugin-config instance
"""
"""Override to set original state of the plugin-config instance"""
super().__init__(*args, **kwargs)
self.__org_active = self.active
@ -145,9 +135,7 @@ class PluginConfig(models.Model):
}
def save(self, force_insert=False, force_update=False, *args, **kwargs):
"""
Extend save method to reload plugins if the 'active' status changes
"""
"""Extend save method to reload plugins if the 'active' status changes"""
reload = kwargs.pop('no_reload', False) # check if no_reload flag is set
ret = super().save(force_insert, force_update, *args, **kwargs)
@ -163,9 +151,7 @@ class PluginConfig(models.Model):
class PluginSetting(common.models.BaseInvenTreeSetting):
"""
This model represents settings for individual plugins
"""
"""This model represents settings for individual plugins"""
class Meta:
unique_together = [
@ -182,8 +168,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
@classmethod
def get_setting_definition(cls, key, **kwargs):
"""
In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS',
"""In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS',
which is a dict object that fully defines all the setting parameters.
Here, unlike the BaseInvenTreeSetting, we do not know the definitions of all settings
@ -209,20 +194,14 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
return super().get_setting_definition(key, **kwargs)
def get_kwargs(self):
"""
Explicit kwargs required to uniquely identify a particular setting object,
in addition to the 'key' parameter
"""
"""Explicit kwargs required to uniquely identify a particular setting object, in addition to the 'key' parameter"""
return {
'plugin': self.plugin,
}
class NotificationUserSetting(common.models.BaseInvenTreeSetting):
"""
This model represents notification settings for a user
"""
"""This model represents notification settings for a user"""
class Meta:
unique_together = [
@ -238,11 +217,7 @@ class NotificationUserSetting(common.models.BaseInvenTreeSetting):
return super().get_setting_definition(key, **kwargs)
def get_kwargs(self):
"""
Explicit kwargs required to uniquely identify a particular setting object,
in addition to the 'key' parameter
"""
"""Explicit kwargs required to uniquely identify a particular setting object, in addition to the 'key' parameter"""
return {
'method': self.method,
'user': self.user,

View File

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
"""
Base Class for InvenTree plugins
"""
"""Base Class for InvenTree plugins"""
import inspect
import logging
import os
@ -55,24 +53,18 @@ class MetaBase:
return value
def plugin_name(self):
"""
Name of plugin
"""
"""Name of plugin"""
return self.get_meta_value('NAME', 'PLUGIN_NAME')
@property
def name(self):
"""
Name of plugin
"""
"""Name of plugin"""
return self.plugin_name()
def plugin_slug(self):
"""
Slug of plugin
If not set plugin name slugified
"""
"""Slug of plugin
If not set plugin name slugified"""
slug = self.get_meta_value('SLUG', 'PLUGIN_SLUG', None)
if not slug:
slug = self.plugin_name()
@ -81,16 +73,11 @@ class MetaBase:
@property
def slug(self):
"""
Slug of plugin
"""
"""Slug of plugin"""
return self.plugin_slug()
def plugin_title(self):
"""
Title of plugin
"""
"""Title of plugin"""
title = self.get_meta_value('TITLE', 'PLUGIN_TITLE', None)
if title:
return title
@ -98,16 +85,11 @@ class MetaBase:
@property
def human_name(self):
"""
Human readable name of plugin
"""
"""Human readable name of plugin"""
return self.plugin_title()
def plugin_config(self):
"""
Return the PluginConfig object associated with this plugin
"""
"""Return the PluginConfig object associated with this plugin"""
try:
import plugin.models
@ -121,10 +103,7 @@ class MetaBase:
return cfg
def is_active(self):
"""
Return True if this plugin is currently active
"""
"""Return True if this plugin is currently active"""
cfg = self.plugin_config()
if cfg:
@ -134,9 +113,7 @@ class MetaBase:
class MixinBase:
"""
Base set of mixin functions and mechanisms
"""
"""Base set of mixin functions and mechanisms"""
def __init__(self, *args, **kwargs) -> None:
self._mixinreg = {}
@ -144,15 +121,11 @@ class MixinBase:
super().__init__(*args, **kwargs)
def mixin(self, key):
"""
Check if mixin is registered
"""
"""Check if mixin is registered"""
return key in self._mixins
def mixin_enabled(self, key):
"""
Check if mixin is registered, enabled and ready
"""
"""Check if mixin is registered, enabled and ready"""
if self.mixin(key):
fnc_name = self._mixins.get(key)
@ -164,18 +137,12 @@ class MixinBase:
return False
def add_mixin(self, key: str, fnc_enabled=True, cls=None):
"""
Add a mixin to the plugins registry
"""
"""Add a mixin to the plugins registry"""
self._mixins[key] = fnc_enabled
self.setup_mixin(key, cls=cls)
def setup_mixin(self, key, cls=None):
"""
Define mixin details for the current mixin -> provides meta details for all active mixins
"""
"""Define mixin details for the current mixin -> provides meta details for all active mixins"""
# get human name
human_name = getattr(cls.MixinMeta, 'MIXIN_NAME', key) if cls and hasattr(cls, 'MixinMeta') else key
@ -187,10 +154,7 @@ class MixinBase:
@property
def registered_mixins(self, with_base: bool = False):
"""
Get all registered mixins for the plugin
"""
"""Get all registered mixins for the plugin"""
mixins = getattr(self, '_mixinreg', None)
if mixins:
# filter out base
@ -202,8 +166,7 @@ class MixinBase:
class InvenTreePlugin(MixinBase, MetaBase):
"""
The InvenTreePlugin class is used to integrate with 3rd party software
"""The InvenTreePlugin class is used to integrate with 3rd party software
DO NOT USE THIS DIRECTLY, USE plugin.InvenTreePlugin
"""
@ -226,9 +189,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
# region properties
@property
def description(self):
"""
Description of plugin
"""
"""Description of plugin"""
description = getattr(self, 'DESCRIPTION', None)
if not description:
description = self.plugin_name()
@ -236,9 +197,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
@property
def author(self):
"""
Author of plugin - either from plugin settings or git
"""
"""Author of plugin - either from plugin settings or git"""
author = getattr(self, 'AUTHOR', None)
if not author:
author = self.package.get('author')
@ -248,9 +207,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
@property
def pub_date(self):
"""
Publishing date of plugin - either from plugin settings or git
"""
"""Publishing date of plugin - either from plugin settings or git"""
pub_date = getattr(self, 'PUBLISH_DATE', None)
if not pub_date:
pub_date = self.package.get('date')
@ -262,77 +219,57 @@ class InvenTreePlugin(MixinBase, MetaBase):
@property
def version(self):
"""
Version of plugin
"""
"""Version of plugin"""
version = getattr(self, 'VERSION', None)
return version
@property
def website(self):
"""
Website of plugin - if set else None
"""
"""Website of plugin - if set else None"""
website = getattr(self, 'WEBSITE', None)
return website
@property
def license(self):
"""
License of plugin
"""
"""License of plugin"""
lic = getattr(self, 'LICENSE', None)
return lic
# endregion
@property
def _is_package(self):
"""
Is the plugin delivered as a package
"""
"""Is the plugin delivered as a package"""
return getattr(self, 'is_package', False)
@property
def is_sample(self):
"""
Is this plugin part of the samples?
"""
"""Is this plugin part of the samples?"""
path = str(self.package_path)
return path.startswith('plugin/samples/')
@property
def package_path(self):
"""
Path to the plugin
"""
"""Path to the plugin"""
if self._is_package:
return self.__module__ # pragma: no cover
return pathlib.Path(self.def_path).relative_to(settings.BASE_DIR)
@property
def settings_url(self):
"""
URL to the settings panel for this plugin
"""
"""URL to the settings panel for this plugin"""
return f'{reverse("settings")}#select-plugin-{self.slug}'
# region package info
def _get_package_commit(self):
"""
Get last git commit for the plugin
"""
"""Get last git commit for the plugin"""
return get_git_log(self.def_path)
def _get_package_metadata(self):
"""
Get package metadata for plugin
"""
"""Get package metadata for plugin"""
return {} # pragma: no cover # TODO add usage for package metadata
def define_package(self):
"""
Add package info of the plugin into plugins context
"""
"""Add package info of the plugin into plugins context"""
package = self._get_package_metadata() if self._is_package else self._get_package_commit()
# process date

View File

@ -30,9 +30,7 @@ logger = logging.getLogger('inventree')
class PluginsRegistry:
"""
The PluginsRegistry class
"""
"""The PluginsRegistry class"""
def __init__(self) -> None:
# plugin registry
@ -54,10 +52,7 @@ class PluginsRegistry:
self.mixins_settings = {}
def get_plugin(self, slug):
"""
Lookup plugin by slug (unique key).
"""
"""Lookup plugin by slug (unique key)."""
if slug not in self.plugins:
logger.warning(f"Plugin registry has no record of plugin '{slug}'")
return None
@ -65,15 +60,13 @@ class PluginsRegistry:
return self.plugins[slug]
def call_plugin_function(self, slug, func, *args, **kwargs):
"""
Call a member function (named by 'func') of the plugin named by 'slug'.
"""Call a member function (named by 'func') of the plugin named by 'slug'.
As this is intended to be run by the background worker,
we do not perform any try/except here.
Instead, any error messages are returned to the worker.
"""
plugin = self.get_plugin(slug)
if not plugin:
@ -86,9 +79,7 @@ class PluginsRegistry:
# region public functions
# region loading / unloading
def load_plugins(self):
"""
Load and activate all IntegrationPlugins
"""
"""Load and activate all IntegrationPlugins"""
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
@ -143,10 +134,7 @@ class PluginsRegistry:
logger.info('Finished loading plugins')
def unload_plugins(self):
"""
Unload and deactivate all IntegrationPlugins
"""
"""Unload and deactivate all IntegrationPlugins"""
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
@ -170,10 +158,7 @@ class PluginsRegistry:
logger.info('Finished unloading plugins')
def reload_plugins(self):
"""
Safely reload IntegrationPlugins
"""
"""Safely reload IntegrationPlugins"""
# Do not reload whe currently loading
if self.is_loading:
return # pragma: no cover
@ -188,7 +173,6 @@ class PluginsRegistry:
def collect_plugins(self):
"""Collect plugins from all possible ways of loading"""
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
@ -217,10 +201,7 @@ class PluginsRegistry:
logger.info(", ".join([a.__module__ for a in self.plugin_modules]))
def install_plugin_file(self):
"""
Make sure all plugins are installed in the current enviroment
"""
"""Make sure all plugins are installed in the current enviroment"""
if settings.PLUGIN_FILE_CHECKED:
logger.info('Plugin file was already checked')
return True
@ -241,9 +222,7 @@ class PluginsRegistry:
# region registry functions
def with_mixin(self, mixin: str, active=None):
"""
Returns reference to all plugins that have a specified mixin enabled
"""
"""Returns reference to all plugins that have a specified mixin enabled"""
result = []
for plugin in self.plugins.values():
@ -264,14 +243,12 @@ class PluginsRegistry:
# region general internal loading /activating / deactivating / deloading
def _init_plugins(self, disabled=None):
"""
Initialise all found plugins
"""Initialise all found plugins
:param disabled: loading path of disabled app, defaults to None
:type disabled: str, optional
:raises error: IntegrationPluginError
"""
from plugin.models import PluginConfig
logger.info('Starting plugin initialisation')
@ -335,8 +312,7 @@ class PluginsRegistry:
self.plugins_inactive[plug_key] = plugin_db_setting # pragma: no cover
def _activate_plugins(self, force_reload=False):
"""
Run activation functions for all plugins
"""Run activation functions for all plugins
:param force_reload: force reload base apps, defaults to False
:type force_reload: bool, optional
@ -351,7 +327,6 @@ class PluginsRegistry:
def _deactivate_plugins(self):
"""Run deactivation functions for all plugins"""
self.deactivate_plugin_app()
self.deactivate_plugin_schedule()
self.deactivate_plugin_settings()
@ -425,15 +400,14 @@ class PluginsRegistry:
logger.warning("activate_integration_schedule failed, database not ready")
def deactivate_plugin_schedule(self):
"""
Deactivate ScheduleMixin
"""Deactivate ScheduleMixin
currently nothing is done
"""
pass
def activate_plugin_app(self, plugins, force_reload=False):
"""
Activate AppMixin plugins - add custom apps and reload
"""Activate AppMixin plugins - add custom apps and reload
:param plugins: list of IntegrationPlugins that should be installed
:type plugins: dict
@ -471,9 +445,10 @@ class PluginsRegistry:
self._update_urls()
def _reregister_contrib_apps(self):
"""fix reloading of contrib apps - models and admin
this is needed if plugins were loaded earlier and then reloaded as models and admins rely on imports
those register models and admin in their respective objects (e.g. admin.site for admin)
"""Fix reloading of contrib apps - models and admin
This is needed if plugins were loaded earlier and then reloaded as models and admins rely on imports.
Those register models and admin in their respective objects (e.g. admin.site for admin).
"""
for plugin_path in self.installed_apps:
try:
@ -503,8 +478,9 @@ class PluginsRegistry:
reload(app_config.module.admin)
def _get_plugin_path(self, plugin):
"""parse plugin path
the input can be eiter:
"""Parse plugin path
The input can be eiter:
- a local file / dir
- a package
"""
@ -518,7 +494,6 @@ class PluginsRegistry:
def deactivate_plugin_app(self):
"""Deactivate AppMixin plugins - some magic required"""
# unregister models from admin
for plugin_path in self.installed_apps:
models = [] # the modelrefs need to be collected as poping an item in a iter is not welcomed
@ -601,9 +576,9 @@ class PluginsRegistry:
self.is_loading = False
def _try_reload(self, cmd, *args, **kwargs):
"""
wrapper to try reloading the apps
throws an custom error that gets handled by the loading function
"""Wrapper to try reloading the apps
Throws an custom error that gets handled by the loading function
"""
try:
cmd(*args, **kwargs)
@ -617,5 +592,5 @@ registry = PluginsRegistry()
def call_function(plugin_name, function_name, *args, **kwargs):
""" Global helper function to call a specific member function of a plugin """
"""Global helper function to call a specific member function of a plugin"""
return registry.call_plugin_function(plugin_name, function_name, *args, **kwargs)