fix docstrings 5

This commit is contained in:
Matthias 2022-05-28 02:51:15 +02:00
parent 61287dba2b
commit 391c8b4ac1
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
7 changed files with 85 additions and 219 deletions

View File

@ -1,6 +1,5 @@
"""
Plugin mixin classes for barcode plugin
"""
"""Plugin mixin classes for barcode plugin"""
import hashlib
import string
@ -10,15 +9,13 @@ from stock.serializers import LocationSerializer, StockItemSerializer
def hash_barcode(barcode_data):
"""
Calculate an MD5 hash of barcode data.
"""Calculate an MD5 hash of barcode data.
HACK: Remove any 'non printable' characters from the hash,
as it seems browers will remove special control characters...
TODO: Work out a way around this!
"""
barcode_data = str(barcode_data).strip()
printable_chars = filter(lambda x: x in string.printable, barcode_data)
@ -30,16 +27,16 @@ def hash_barcode(barcode_data):
class BarcodeMixin:
"""
Mixin that enables barcode handeling
"""Mixin that enables barcode handeling
Custom barcode plugins should use and extend this mixin as necessary.
"""
ACTION_NAME = ""
class MixinMeta:
"""
meta options for this mixin
"""
"""Meta options for this mixin"""
MIXIN_NAME = 'Barcode'
def __init__(self):
@ -48,35 +45,27 @@ class BarcodeMixin:
@property
def has_barcode(self):
"""
Does this plugin have everything needed to process a barcode
"""
"""Does this plugin have everything needed to process a barcode"""
return True
def init(self, barcode_data):
"""
Initialize the BarcodePlugin instance
"""Initialize the BarcodePlugin instance
Args:
barcode_data - The raw barcode data
"""
self.data = barcode_data
def getStockItem(self):
"""
Attempt to retrieve a StockItem associated with this barcode.
"""Attempt to retrieve a StockItem associated with this barcode.
Default implementation returns None
"""
return None # pragma: no cover
def getStockItemByHash(self):
"""Attempt to retrieve a StockItem associated with this barcode, based on the barcode hash.
"""
Attempt to retrieve a StockItem associated with this barcode,
based on the barcode hash.
"""
try:
item = StockItem.objects.get(uid=self.hash())
return item
@ -84,48 +73,37 @@ class BarcodeMixin:
return None
def renderStockItem(self, item):
"""
Render a stock item to JSON response
"""
"""Render a stock item to JSON response"""
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
return serializer.data
def getStockLocation(self):
"""
Attempt to retrieve a StockLocation associated with this barcode.
"""Attempt to retrieve a StockLocation associated with this barcode.
Default implementation returns None
"""
return None # pragma: no cover
def renderStockLocation(self, loc):
"""
Render a stock location to a JSON response
"""
"""Render a stock location to a JSON response"""
serializer = LocationSerializer(loc)
return serializer.data
def getPart(self):
"""
Attempt to retrieve a Part associated with this barcode.
"""Attempt to retrieve a Part associated with this barcode.
Default implementation returns None
"""
return None # pragma: no cover
def renderPart(self, part):
"""
Render a part to JSON response
"""
"""Render a part to JSON response"""
serializer = PartSerializer(part)
return serializer.data
def hash(self):
"""
Calculate a hash for the barcode data.
"""Calculate a hash for the barcode data.
This is supposed to uniquely identify the barcode contents,
at least within the bardcode sub-type.
@ -134,13 +112,9 @@ class BarcodeMixin:
This may be sufficient for most applications, but can obviously be overridden
by a subclass.
"""
return hash_barcode(self.data)
def validate(self):
"""
Default implementation returns False
"""
"""Default implementation returns False"""
return False # pragma: no cover

View File

@ -1,8 +1,4 @@
# -*- coding: utf-8 -*-
"""
Unit tests for Barcode endpoints
"""
"""Unit tests for Barcode endpoints"""
from django.urls import reverse
@ -62,10 +58,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
self.assertIsNone(data['plugin'])
def test_find_part(self):
"""
Test that we can lookup a part based on ID
"""
"""Test that we can lookup a part based on ID"""
response = self.client.post(
self.scan_url,
{
@ -98,10 +91,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
self.assertEqual(response.data['part'], 'Part does not exist')
def test_find_stock_item(self):
"""
Test that we can lookup a stock item based on ID
"""
"""Test that we can lookup a stock item based on ID"""
response = self.client.post(
self.scan_url,
{
@ -119,7 +109,6 @@ class BarcodeAPITest(InvenTreeAPITestCase):
def test_invalid_item(self):
"""Test response for invalid stock item"""
response = self.client.post(
self.scan_url,
{
@ -135,10 +124,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
self.assertEqual(response.data['stockitem'], 'Stock item does not exist')
def test_find_location(self):
"""
Test that we can lookup a stock location based on ID
"""
"""Test that we can lookup a stock location based on ID"""
response = self.client.post(
self.scan_url,
{
@ -156,7 +142,6 @@ class BarcodeAPITest(InvenTreeAPITestCase):
def test_invalid_location(self):
"""Test response for an invalid location"""
response = self.client.post(
self.scan_url,
{
@ -215,10 +200,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
self.assertEqual(pk, item.pk)
def test_association(self):
"""
Test that a barcode can be associated with a StockItem
"""
"""Test that a barcode can be associated with a StockItem"""
item = StockItem.objects.get(pk=522)
self.assertEqual(len(item.uid), 0)

View File

@ -1,6 +1,4 @@
"""
Functions for triggering and responding to server side events
"""
"""Functions for triggering and responding to server side events"""
import logging
@ -17,13 +15,11 @@ logger = logging.getLogger('inventree')
def trigger_event(event, *args, **kwargs):
"""
Trigger an event with optional arguments.
"""Trigger an event with optional arguments.
This event will be stored in the database,
and the worker will respond to it later on.
"""
if not settings.PLUGINS_ENABLED:
# Do nothing if plugins are not enabled
return # pragma: no cover
@ -44,8 +40,7 @@ def trigger_event(event, *args, **kwargs):
def register_event(event, *args, **kwargs):
"""
Register the event with any interested plugins.
"""Register the event with any interested plugins.
Note: This function is processed by the background worker,
as it performs multiple database access operations.
@ -80,14 +75,11 @@ def register_event(event, *args, **kwargs):
def process_event(plugin_slug, event, *args, **kwargs):
"""
Respond to a triggered event.
"""Respond to a triggered event.
This function is run by the background worker process.
This function may queue multiple functions to be handled by the background worker.
"""
logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'")
plugin = registry.plugins.get(plugin_slug, None)
@ -100,11 +92,10 @@ def process_event(plugin_slug, event, *args, **kwargs):
def allow_table_event(table_name):
"""
Determine if an automatic event should be fired for a given table.
"""Determine if an automatic event should be fired for a given table.
We *do not* want events to be fired for some tables!
"""
if isImportingData():
# Prevent table events during the data import process
return False # pragma: no cover
@ -143,10 +134,7 @@ def allow_table_event(table_name):
@receiver(post_save)
def after_save(sender, instance, created, **kwargs):
"""
Trigger an event whenever a database entry is saved
"""
"""Trigger an event whenever a database entry is saved"""
table = sender.objects.model._meta.db_table
instance_id = getattr(instance, 'id', None)
@ -173,10 +161,7 @@ def after_save(sender, instance, created, **kwargs):
@receiver(post_delete)
def after_delete(sender, instance, **kwargs):
"""
Trigger an event whenever a database entry is deleted
"""
"""Trigger an event whenever a database entry is deleted"""
table = sender.objects.model._meta.db_table
if not allow_table_event(table):

View File

@ -4,24 +4,22 @@ from plugin.helpers import MixinNotImplementedError
class EventMixin:
"""
Mixin that provides support for responding to triggered events.
"""Mixin that provides support for responding to triggered events.
Implementing classes must provide a "process_event" function:
"""
def process_event(self, event, *args, **kwargs):
"""
Function to handle events
"""Function to handle events
Must be overridden by plugin
"""
# Default implementation does not do anything
raise MixinNotImplementedError
class MixinMeta:
"""
Meta options for this mixin
"""
"""Meta options for this mixin"""
MIXIN_NAME = 'Events'
def __init__(self):

View File

@ -1,6 +1,4 @@
"""
Plugin mixin classes
"""
"""Plugin mixin classes"""
import json
import logging
@ -21,9 +19,7 @@ logger = logging.getLogger('inventree')
class SettingsMixin:
"""
Mixin that enables global settings for the plugin
"""
"""Mixin that enables global settings for the plugin"""
class MixinMeta:
MIXIN_NAME = 'Settings'
@ -35,23 +31,15 @@ class SettingsMixin:
@property
def has_settings(self):
"""
Does this plugin use custom global settings
"""
"""Does this plugin use custom global settings"""
return bool(self.settings)
def get_setting(self, key):
"""
Return the 'value' of the setting associated with this plugin
"""
"""Return the 'value' of the setting associated with this plugin"""
return PluginSetting.get_setting(key, plugin=self)
def set_setting(self, key, value, user=None):
"""
Set plugin setting value by key
"""
"""Set plugin setting value by key"""
try:
plugin, _ = PluginConfig.objects.get_or_create(key=self.plugin_slug(), name=self.plugin_name())
except (OperationalError, ProgrammingError): # pragma: no cover
@ -66,8 +54,7 @@ class SettingsMixin:
class ScheduleMixin:
"""
Mixin that provides support for scheduled tasks.
"""Mixin that provides support for scheduled tasks.
Implementing classes must provide a dict object called SCHEDULED_TASKS,
which provides information on the tasks to be scheduled.
@ -99,9 +86,8 @@ class ScheduleMixin:
SCHEDULED_TASKS = {}
class MixinMeta:
"""
Meta options for this mixin
"""
"""Meta options for this mixin"""
MIXIN_NAME = 'Schedule'
def __init__(self):
@ -116,16 +102,11 @@ class ScheduleMixin:
@property
def has_scheduled_tasks(self):
"""
Are tasks defined for this plugin
"""
"""Are tasks defined for this plugin"""
return bool(self.scheduled_tasks)
def validate_scheduled_tasks(self):
"""
Check that the provided scheduled tasks are valid
"""
"""Check that the provided scheduled tasks are valid"""
if not self.has_scheduled_tasks:
raise MixinImplementationError("SCHEDULED_TASKS not defined")
@ -147,25 +128,18 @@ class ScheduleMixin:
raise MixinImplementationError(f"Task '{key}' is missing 'minutes' parameter")
def get_task_name(self, key):
"""
Task name for key
"""
"""Task name for key"""
# Generate a 'unique' task name
slug = self.plugin_slug()
return f"plugin.{slug}.{key}"
def get_task_names(self):
"""
All defined task names
"""
"""All defined task names"""
# Returns a list of all task names associated with this plugin instance
return [self.get_task_name(key) for key in self.scheduled_tasks.keys()]
def register_tasks(self):
"""
Register the tasks with the database
"""
"""Register the tasks with the database"""
try:
from django_q.models import Schedule
@ -182,10 +156,7 @@ class ScheduleMixin:
func_name = task['func'].strip()
if '.' in func_name:
"""
Dotted notation indicates that we wish to run a globally defined function,
from a specified Python module.
"""
"""Dotted notation indicates that we wish to run a globally defined function, from a specified Python module."""
Schedule.objects.create(
name=task_name,
@ -196,8 +167,7 @@ class ScheduleMixin:
)
else:
"""
Non-dotted notation indicates that we wish to call a 'member function' of the calling plugin.
"""Non-dotted notation indicates that we wish to call a 'member function' of the calling plugin.
This is managed by the plugin registry itself.
"""
@ -218,10 +188,7 @@ class ScheduleMixin:
logger.warning("register_tasks failed, database not ready")
def unregister_tasks(self):
"""
Deregister the tasks with the database
"""
"""Deregister the tasks with the database"""
try:
from django_q.models import Schedule
@ -240,14 +207,11 @@ class ScheduleMixin:
class UrlsMixin:
"""
Mixin that enables custom URLs for the plugin
"""
"""Mixin that enables custom URLs for the plugin"""
class MixinMeta:
"""
Meta options for this mixin
"""
"""Meta options for this mixin"""
MIXIN_NAME = 'URLs'
def __init__(self):
@ -256,54 +220,41 @@ class UrlsMixin:
self.urls = self.setup_urls()
def setup_urls(self):
"""
Setup url endpoints for this plugin
"""
"""Setup url endpoints for this plugin"""
return getattr(self, 'URLS', None)
@property
def base_url(self):
"""
Base url for this plugin
"""
"""Base url for this plugin"""
return f'{PLUGIN_BASE}/{self.slug}/'
@property
def internal_name(self):
"""
Internal url pattern name
"""
"""Internal url pattern name"""
return f'plugin:{self.slug}:'
@property
def urlpatterns(self):
"""
Urlpatterns for this plugin
"""
"""Urlpatterns for this plugin"""
if self.has_urls:
return re_path(f'^{self.slug}/', include((self.urls, self.slug)), name=self.slug)
return None
@property
def has_urls(self):
"""
Does this plugin use custom urls
"""
"""Does this plugin use custom urls"""
return bool(self.urls)
class NavigationMixin:
"""
Mixin that enables custom navigation links with the plugin
"""
"""Mixin that enables custom navigation links with the plugin"""
NAVIGATION_TAB_NAME = None
NAVIGATION_TAB_ICON = "fas fa-question"
class MixinMeta:
"""
Meta options for this mixin
"""
"""Meta options for this mixin"""
MIXIN_NAME = 'Navigation Links'
def __init__(self):
@ -312,9 +263,7 @@ class NavigationMixin:
self.navigation = self.setup_navigation()
def setup_navigation(self):
"""
Setup navigation links for this plugin
"""
"""Setup navigation links for this plugin"""
nav_links = getattr(self, 'NAVIGATION', None)
if nav_links:
# check if needed values are configured
@ -325,16 +274,12 @@ class NavigationMixin:
@property
def has_naviation(self):
"""
Does this plugin define navigation elements
"""
"""Does this plugin define navigation elements"""
return bool(self.navigation)
@property
def navigation_name(self):
"""
Name for navigation tab
"""
"""Name for navigation tab"""
name = getattr(self, 'NAVIGATION_TAB_NAME', None)
if not name:
name = self.human_name
@ -342,21 +287,16 @@ class NavigationMixin:
@property
def navigation_icon(self):
"""
Icon-name for navigation tab
"""
"""Icon-name for navigation tab"""
return getattr(self, 'NAVIGATION_TAB_ICON', "fas fa-question")
class AppMixin:
"""
Mixin that enables full django app functions for a plugin
"""
"""Mixin that enables full django app functions for a plugin"""
class MixinMeta:
"""m
Mta options for this mixin
"""
"""Meta options for this mixin"""
MIXIN_NAME = 'App registration'
def __init__(self):
@ -365,15 +305,12 @@ class AppMixin:
@property
def has_app(self):
"""
This plugin is always an app with this plugin
"""
"""This plugin is always an app with this plugin"""
return True
class APICallMixin:
"""
Mixin that enables easier API calls for a plugin
"""Mixin that enables easier API calls for a plugin
Steps to set up:
1. Add this mixin before (left of) SettingsMixin and PluginBase
@ -424,7 +361,7 @@ class APICallMixin:
API_TOKEN = 'Bearer'
class MixinMeta:
"""meta options for this mixin"""
"""Meta options for this mixin"""
MIXIN_NAME = 'API calls'
def __init__(self):
@ -487,8 +424,7 @@ class APICallMixin:
class PanelMixin:
"""
Mixin which allows integration of custom 'panels' into a particular page.
"""Mixin which allows integration of custom 'panels' into a particular page.
The mixin provides a number of key functionalities:
@ -540,17 +476,15 @@ class PanelMixin:
self.add_mixin('panel', True, __class__)
def get_custom_panels(self, view, request):
""" This method *must* be implemented by the plugin class """
"""This method *must* be implemented by the plugin class"""
raise MixinNotImplementedError(f"{__class__} is missing the 'get_custom_panels' method")
def get_panel_context(self, view, request, context):
"""
Build the context data to be used for template rendering.
"""Build the context data to be used for template rendering.
Custom class can override this to provide any custom context data.
(See the example in "custom_panel_sample.py")
"""
# Provide some standard context items to the template for rendering
context['plugin'] = self
context['request'] = request

View File

@ -1,4 +1,4 @@
""" Unit tests for base mixins for plugins """
"""Unit tests for base mixins for plugins"""
from django.conf import settings
from django.test import TestCase
@ -170,9 +170,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
API_TOKEN_SETTING = 'API_TOKEN'
def get_external_url(self, simple: bool = True):
'''
returns data from the sample endpoint
'''
"""Returns data from the sample endpoint"""
return self.api_call('api/users/2', simple_response=simple)
self.mixin = MixinCls()
@ -263,7 +261,6 @@ class PanelMixinTests(InvenTreeTestCase):
def test_installed(self):
"""Test that the sample panel plugin is installed"""
plugins = registry.with_mixin('panel')
self.assertTrue(len(plugins) > 0)
@ -276,7 +273,6 @@ class PanelMixinTests(InvenTreeTestCase):
def test_disabled(self):
"""Test that the panels *do not load* if the plugin is not enabled"""
plugin = registry.get_plugin('samplepanel')
plugin.set_setting('ENABLE_HELLO_WORLD', True)
@ -308,7 +304,6 @@ class PanelMixinTests(InvenTreeTestCase):
"""
Test that the panels *do* load if the plugin is enabled
"""
plugin = registry.get_plugin('samplepanel')
self.assertEqual(len(registry.with_mixin('panel', active=True)), 0)
@ -383,7 +378,6 @@ class PanelMixinTests(InvenTreeTestCase):
def test_mixin(self):
"""Test that ImplementationError is raised"""
with self.assertRaises(MixinNotImplementedError):
class Wrong(PanelMixin, InvenTreePlugin):
pass

View File

@ -1,4 +1,5 @@
"""Functions to print a label to a mixin printer"""
import logging
from django.utils.translation import gettext_lazy as _
@ -10,8 +11,7 @@ logger = logging.getLogger('inventree')
def print_label(plugin_slug, label_image, label_instance=None, user=None):
"""
Print label with the provided plugin.
"""Print label with the provided plugin.
This task is nominally handled by the background worker.
@ -21,7 +21,6 @@ def print_label(plugin_slug, label_image, label_instance=None, user=None):
plugin_slug: The unique slug (key) of the plugin
label_image: A PIL.Image image object to be printed
"""
logger.info(f"Plugin '{plugin_slug}' is printing a label")
plugin = registry.plugins.get(plugin_slug, None)