Add import linter (#13248)

* add import-linter

* move machine imports to plugin space

* clean up samples

* extend scope

* extend checks

* move plugin internals into a seperat folder

* remove unneeded re-export

* adjust import

* update import checker

* update src ref

* fix mocks
This commit is contained in:
Matthias Mair authored and GitHub committed 2026-11-12 18:41:59 +11:00
1 parent c80090b3ce
commit 7e57196d76
75 files changed
+284 -174

No files matched your search

+8
View File
@@ -64,6 +64,14 @@ repos:
rev: v1.36.4
hooks:
- id: djlint-django
- repo: local
hooks:
- id: import-linter
name: import-linter
entry: bash -c 'cd src/backend/InvenTree && lint-imports --config ../../../pyproject.toml'
language: python
additional_dependencies: [import-linter==2.16]
pass_filenames: false
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
+1 -1
View File
@@ -52,7 +52,7 @@ from plugin.mixins import APICallMixin, SettingsMixin, ScheduleMixin, BarcodeMix
### Plugin Base Class
Custom plugins must inherit from the [InvenTreePlugin class]({{ sourcefile("src/backend/InvenTree/plugin/plugin.py") }}). Any plugins installed via the methods outlined above will be "discovered" when the InvenTree server launches.
Custom plugins must inherit from the [InvenTreePlugin class]({{ sourcefile("src/backend/InvenTree/plugin/_internal/base.py") }}). Any plugins installed via the methods outlined above will be "discovered" when the InvenTree server launches.
### Imports
+105
View File
@@ -142,3 +142,108 @@ pythonpath = ["src/backend/InvenTree"]
DJANGO_SETTINGS_MODULE = "InvenTree.settings"
python_files = ["test*.py",]
timeout = "120"
[tool.importlinter]
# Architectural import boundaries for the backend, enforced by import-linter (prek).
root_packages = [
"InvenTree",
"audit",
"build",
"common",
"company",
"data_exporter",
"generic",
"importer",
"machine",
"order",
"part",
"plugin",
"plugins",
"report",
"stock",
"users",
"web",
]
include_external_packages = false
[[tool.importlinter.contracts]]
# Plugins are only allowed to use the documented, stable import targets:
# `plugin` , `plugin.mixins`, `plugin.machine`
name = "Plugins only import the public plugin API"
type = "forbidden"
source_modules = ["plugin.builtin", "plugin.samples", "plugins"]
forbidden_modules = [
"machine",
"plugin",
"plugin._internal",
"plugin.admin",
"plugin.api",
"plugin.apps",
"plugin.base",
"plugin.broken",
"plugin.builtin",
"plugin.events",
# "plugin.machine", - allowed
"plugin.migrations",
# "plugin.mixins", - allowed
"plugin.mock",
"plugin.models",
"plugin.samples",
"plugin.serializers",
"plugin.staticfiles",
"plugin.template",
"plugin.test_api",
"plugin.test_plugin",
"plugin.test_staticfiles",
"plugin.testing",
"plugin.urls",
]
allow_indirect_imports = true
# [[tool.importlinter.contracts]]
# name = "Plugin mixins must be layered"
# type = "independence"
# modules=[
# "plugin.base.*"
# ]
[[tool.importlinter.contracts]]
# `generic` holds shared enums, events and state machines
name = "Generic app does not depend on feature apps"
type = "forbidden"
source_modules = ["generic"]
forbidden_modules = [
"build",
"company",
"importer",
"machine",
"order",
"part",
"report",
"stock",
"users",
"web",
]
allow_indirect_imports = true
ignore_imports = [
"generic.states.tests -> build.status_codes",
"generic.states.test_transition -> order.models",
"generic.states.test_transition -> order.status_codes",
"generic.states.api_helpers -> users.permissions",
"generic.states.test_transition -> users.models",
]
[[tool.importlinter.contracts]]
# Ban dependency cycles between sibling modules/subpackages
name = "No dependency cycles inside an app"
type = "acyclic_siblings"
ancestors = [
"company",
"data_exporter",
"generic",
"report",
"stock",
"web",
]
ignore_imports = ["generic.templatetags.generic -> generic.states.tags"]
+2 -2
View File
@@ -158,7 +158,7 @@ InvenTreeBackupMetadata = dict[str, str | int | bool | None]
def _gather_environment_metadata() -> InvenTreeBackupMetadata:
"""Gather metadata about the current environment to be stored with the backup."""
import plugin.installer
from plugin._internal.installer import plugins_file_hash
new_data: InvenTreeBackupMetadata = {}
@@ -166,7 +166,7 @@ def _gather_environment_metadata() -> InvenTreeBackupMetadata:
new_data['ivt_1_version'] = InvenTree.version.inventreeVersion()
new_data['ivt_1_version_api'] = InvenTree.version.inventreeApiVersion()
new_data['ivt_1_plugins_enabled'] = settings.PLUGINS_ENABLED
new_data['ivt_1_plugins_file_hash'] = plugin.installer.plugins_file_hash()
new_data['ivt_1_plugins_file_hash'] = plugins_file_hash()
new_data['ivt_1_installer'] = InvenTree.config.inventreeInstaller()
new_data['ivt_1_backup_time'] = datetime.now().isoformat()
+1 -2
View File
@@ -1154,8 +1154,7 @@ def pui_url(subpath: str) -> str:
def plugins_info(*args, **kwargs):
"""Return information about activated plugins."""
from plugin import PluginMixinEnum
from plugin.registry import registry
from plugin import PluginMixinEnum, registry
# Check if plugins are even enabled
if not settings.PLUGINS_ENABLED:
@@ -13,7 +13,7 @@ import InvenTree.helpers
import plugin.models
from common.settings import get_global_setting
from InvenTree import version
from plugin.plugin import InvenTreePlugin
from plugin import InvenTreePlugin
register = template.Library()
+1 -2
View File
@@ -868,9 +868,8 @@ class GeneralApiTests(InvenTreeAPITestCase):
def test_info_view(self):
"""Test that we can read the 'info-view' endpoint."""
from plugin import PluginMixinEnum
from plugin import PluginMixinEnum, registry
from plugin.models import PluginConfig
from plugin.registry import registry
self.ensurePluginsLoaded()
+1 -1
View File
@@ -473,7 +473,7 @@ class PluginRegistryMixin:
from time import sleep
from common.models import InvenTreeSetting
from plugin.registry import registry
from plugin import registry
while not registry.is_ready:
print('Waiting for plugin registry to be ready...')
@@ -328,7 +328,7 @@ class DataExportViewMixin:
- If a plugin is specified, the plugin can be used to augment or replace the export functionality.
"""
if export_plugin is None:
from plugin.registry import registry
from plugin import registry
export_plugin = registry.get_plugin('inventree-exporter')
+2 -2
View File
@@ -137,7 +137,7 @@ class MachineRegistry(
def discover_machine_types(self):
"""Discovers all machine types by discovering all plugins which implement the Machine mixin class."""
from plugin import PluginMixinEnum
from plugin.registry import registry as plugin_registry
from plugin import registry as plugin_registry
logger.debug('Collecting machine types')
@@ -187,7 +187,7 @@ class MachineRegistry(
def discover_drivers(self):
"""Discovers all machine drivers by discovering all plugins which implement the Machine mixin class."""
from plugin import PluginMixinEnum
from plugin.registry import registry as plugin_registry
from plugin import registry as plugin_registry
logger.debug('Collecting machine drivers')
drivers: dict[str, type[BaseDriver]] = {}
+1 -1
View File
@@ -10,7 +10,7 @@ from machine import registry
from machine.machine_type import BaseDriver
from machine.models import MachineConfig
from machine.tests import TestMachineRegistryMixin
from plugin.registry import registry as plg_registry
from plugin import registry as plg_registry
from stock.models import StockLocation
+1 -1
View File
@@ -10,8 +10,8 @@ from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase
from machine.models import MachineConfig
from machine.registry import registry
from part.models import Part
from plugin import registry as plg_registry
from plugin.models import PluginConfig
from plugin.registry import registry as plg_registry
from report.models import LabelTemplate
+4 -3
View File
@@ -1,13 +1,14 @@
"""Utility file to enable simper imports."""
from .helpers import MixinImplementationError, MixinNotImplementedError
from .plugin import InvenTreePlugin, PluginMixinEnum
from .registry import registry
from ._internal.base import InvenTreePlugin, PluginMixinEnum
from ._internal.helpers import MixinImplementationError, MixinNotImplementedError
from ._internal.registry import call_plugin_function, registry
__all__ = [
'InvenTreePlugin',
'MixinImplementationError',
'MixinNotImplementedError',
'PluginMixinEnum',
'call_plugin_function',
'registry',
]
Whitespace-only changes.
@@ -19,7 +19,8 @@ import structlog
import InvenTree.helpers
from generic.enums import StringEnum
from plugin.helpers import get_git_log
from .helpers import get_git_log
logger = structlog.get_logger('inventree')
@@ -173,7 +174,7 @@ class MetaBase:
@mark_final
def plugin_config(self):
"""Return the PluginConfig object associated with this plugin."""
from plugin.registry import registry
from .registry import registry
return registry.get_plugin_config(self.plugin_slug())
@@ -52,7 +52,7 @@ class MixinNotImplementedError(NotImplementedError):
def log_registry_error(error, reference: str = 'general'):
"""Log an plugin error."""
from plugin.registry import registry
from .registry import registry
# make sure the registry is set up
if reference not in registry.errors:
@@ -368,7 +368,7 @@ def install_plugin(
update_plugins_file(package_reference=package_ref)
# Reload the plugin registry, to discover the new plugin
from plugin.registry import registry
from .registry import registry
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
@@ -404,7 +404,7 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T
user: User performing the uninstall
delete_config: If True, delete the plugin configuration from the database
"""
from plugin.registry import registry
from .registry import registry
if user and not user.is_superuser:
raise ValidationError(_('Only superuser accounts can administer plugins'))
@@ -33,6 +33,7 @@ from common.settings import get_global_setting, set_global_setting
from InvenTree.config import get_plugin_dir
from InvenTree.exceptions import log_error
from .base import InvenTreePlugin
from .helpers import (
IntegrationPluginError,
MixinNotImplementedError,
@@ -42,7 +43,6 @@ from .helpers import (
log_registry_error,
)
from .lease import release_lease, try_acquire_lease
from .plugin import InvenTreePlugin
logger = structlog.get_logger('inventree')
@@ -81,10 +81,10 @@ def registry_entrypoint(check_reload: bool = True, default_value: Any = None) ->
class PluginsRegistry:
"""The PluginsRegistry class."""
from .base.integration.AppMixin import AppMixin
from .base.integration.ScheduleMixin import ScheduleMixin
from .base.integration.SettingsMixin import SettingsMixin
from .base.integration.UrlsMixin import UrlsMixin
from ..base.integration.AppMixin import AppMixin
from ..base.integration.ScheduleMixin import ScheduleMixin
from ..base.integration.SettingsMixin import SettingsMixin
from ..base.integration.UrlsMixin import UrlsMixin
DEFAULT_MIXIN_ORDER = [SettingsMixin, ScheduleMixin, AppMixin, UrlsMixin]
@@ -834,7 +834,7 @@ class PluginsRegistry:
- Also store the hash as a local marker file in the current environment
"""
from plugin.installer import (
from .installer import (
get_env_plugin_hash,
install_plugins_file,
plugins_file_hash,
+1 -1
View File
@@ -4,7 +4,7 @@ from django.contrib import admin
from common.admin_shared import NoAddAdminMixin
from plugin import models
from plugin.registry import registry as pl_registry
from plugin import registry as pl_registry
def plugin_update(queryset, new_status: bool):
+1 -2
View File
@@ -28,14 +28,13 @@ from InvenTree.mixins import (
RetrieveUpdateAPI,
UpdateAPI,
)
from plugin import InvenTreePlugin, registry
from plugin.base.action.api import ActionPluginView
from plugin.base.barcodes.api import barcode_api_urls
from plugin.base.locate.api import LocatePluginView
from plugin.base.supplier.api import supplier_api_urls
from plugin.base.ui.api import ui_plugins_api_urls
from plugin.models import PluginConfig, PluginSetting, PluginUserSetting
from plugin.plugin import InvenTreePlugin
from plugin.registry import registry
class PluginFilter(FilterSet):
@@ -16,8 +16,7 @@ import InvenTree.exceptions
from common.settings import get_global_setting
from InvenTree.ready import canAppAccessDatabase, isImportingData, isRunningMigrations
from InvenTree.tasks import bulk_offload_task, offload_task
from plugin import PluginMixinEnum
from plugin.registry import registry
from plugin import PluginMixinEnum, registry
tracer = trace.get_tracer(__name__)
logger = structlog.get_logger('inventree')
@@ -1,7 +1,6 @@
"""Plugin mixin class for events."""
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
class EventMixin:
@@ -3,8 +3,7 @@
import structlog
from common.icons import IconPack, reload_icon_packs
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
logger = structlog.get_logger('inventree')
@@ -7,8 +7,7 @@ from typing import Optional
import requests
import structlog
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
logger = structlog.get_logger('inventree')
@@ -1,7 +1,6 @@
"""Plugin mixin class for supporting currency exchange data."""
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
class CurrencyExchangeMixin:
@@ -2,8 +2,8 @@
import structlog
from machine.machine_type import BaseDriver, BaseMachineType
from plugin import PluginMixinEnum
from plugin.machine import BaseDriver, BaseMachineType
logger = structlog.get_logger('inventree')
@@ -2,8 +2,7 @@
import structlog
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
logger = structlog.get_logger('inventree')
@@ -6,8 +6,7 @@ from django.db.utils import OperationalError, ProgrammingError
import structlog
from common.settings import get_global_setting
from plugin import PluginMixinEnum
from plugin.helpers import MixinImplementationError
from plugin import MixinImplementationError, PluginMixinEnum
logger = structlog.get_logger('inventree')
@@ -100,8 +100,8 @@ class SettingsMixin:
value: The value to be set for the setting
user: The user who is making the change (optional)
"""
from plugin import registry
from plugin.models import PluginSetting
from plugin.registry import registry
try:
plugin = registry.get_plugin_config(self.plugin_slug(), self.plugin_name())
@@ -145,8 +145,8 @@ class SettingsMixin:
value: The value to be set for the user setting
user: The user for which the setting is to be set
"""
from plugin import registry
from plugin.models import PluginUserSetting
from plugin.registry import registry
try:
plugin = registry.get_plugin_config(self.plugin_slug(), self.plugin_name())
@@ -1,7 +1,6 @@
"""Plugin mixin class for adding well-known urls."""
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
class WellKnownMixin:
@@ -10,8 +10,7 @@ from django.urls import include, path, re_path
import requests_mock
from InvenTree.unit_test import InvenTreeTestCase
from plugin import InvenTreePlugin
from plugin.helpers import MixinNotImplementedError
from plugin import InvenTreePlugin, MixinNotImplementedError
from plugin.mixins import (
AllocateMixin,
APICallMixin,
@@ -7,7 +7,7 @@ import structlog
import common.notifications
from InvenTree.exceptions import log_error
from plugin.registry import registry
from plugin import registry
logger = structlog.get_logger('inventree')
@@ -10,9 +10,8 @@ from rest_framework.request import Request
from common.models import DataOutput, InvenTreeSetting
from InvenTree.exceptions import log_error
from InvenTree.tasks import offload_task
from plugin import PluginMixinEnum
from plugin import MixinNotImplementedError, PluginMixinEnum
from plugin.base.label import label as plugin_label
from plugin.helpers import MixinNotImplementedError
from report.models import LabelTemplate
@@ -15,12 +15,11 @@ from rest_framework import serializers
from InvenTree.config import get_testfolder_dir
from InvenTree.unit_test import InvenTreeAPITestCase
from machine import registry as machine_registry
from machine.models import MachineConfig
from part.models import Part
from plugin import InvenTreePlugin, PluginMixinEnum, registry
from plugin import InvenTreePlugin, MixinNotImplementedError, PluginMixinEnum, registry
from plugin.base.label.mixins import LabelPrintingMixin
from plugin.helpers import MixinNotImplementedError
from plugin.machine import registry as machine_registry
from report.models import LabelTemplate
from report.tests import PrintTestMixins
from stock.models import StockItem, StockLocation
@@ -13,8 +13,7 @@ from rest_framework.response import Response
import InvenTree.permissions
from InvenTree.exceptions import log_error
from InvenTree.tasks import offload_task
from plugin import PluginMixinEnum, registry
from plugin.registry import call_plugin_function
from plugin import PluginMixinEnum, call_plugin_function, registry
from stock.models import StockItem, StockLocation
from users.permissions import check_user_permission
@@ -2,8 +2,7 @@
import structlog
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
logger = structlog.get_logger('inventree')
@@ -7,8 +7,7 @@ from django.core.mail.message import EmailMessage, EmailMultiAlternatives
from common.models import EmailMessage as InventreeEmailMessage
from common.settings import get_global_setting
from InvenTree.backends import logger
from plugin.plugin import PluginMixinEnum
from plugin.registry import registry
from plugin import PluginMixinEnum, registry
def process_mail_out(
@@ -2,8 +2,7 @@
from django.core.mail import EmailMessage
from plugin import PluginMixinEnum
from plugin.helpers import MixinNotImplementedError
from plugin import MixinNotImplementedError, PluginMixinEnum
class MailMixin:
@@ -13,8 +13,7 @@ from rest_framework.views import APIView
from InvenTree import permissions
from part.models import PartCategoryParameterTemplate
from plugin import registry
from plugin.plugin import PluginMixinEnum
from plugin import PluginMixinEnum, registry
from .serializers import (
ImportRequestSerializer,
@@ -13,11 +13,15 @@ from typing import cast
from django.utils.translation import gettext_lazy as _
import plugin.base.barcodes.helper
from InvenTree.helpers import hash_barcode
from InvenTree.models import InvenTreeBarcodeMixin
from plugin import InvenTreePlugin
from plugin.mixins import BarcodeMixin, SettingsMixin
from plugin.mixins import (
BarcodeMixin,
SettingsMixin,
get_supported_barcode_model_codes_map,
get_supported_barcode_models,
)
class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugin):
@@ -67,9 +71,7 @@ class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugi
):
model_type_code, pk = m.groups()
supported_models_map = (
plugin.base.barcodes.helper.get_supported_barcode_model_codes_map()
)
supported_models_map = get_supported_barcode_model_codes_map()
model = supported_models_map.get(model_type_code, None)
if model is None:
@@ -96,7 +98,7 @@ class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugi
except json.JSONDecodeError:
pass
supported_models = plugin.base.barcodes.helper.get_supported_barcode_models()
supported_models = get_supported_barcode_models()
succcess_message = _('Found matching item')
@@ -16,9 +16,8 @@ from build.events import BuildEvents
from build.models import Build
from build.status_codes import BuildStatus, BuildStatusGroups
from InvenTree.tasks import offload_task
from plugin import InvenTreePlugin
from plugin import InvenTreePlugin, call_plugin_function
from plugin.mixins import EventMixin, SettingsMixin
from plugin.registry import call_plugin_function
logger = structlog.get_logger('inventree')
@@ -5,7 +5,7 @@ from django.urls import reverse
from InvenTree.helpers import escape_export_value
from InvenTree.unit_test import InvenTreeAPITestCase
from plugin.registry import registry
from plugin import registry
class StocktakeExporterTest(InvenTreeAPITestCase):
@@ -2,8 +2,9 @@
from django.utils.translation import gettext_lazy as _
from machine.machine_type import BaseDriver, BaseMachineType
from plugin import InvenTreePlugin
from plugin.machine import BaseDriver, BaseMachineType
from plugin.machine.machine_types import LabelPrinterMachine
from plugin.mixins import MachineDriverMixin
@@ -19,8 +20,6 @@ class InvenTreeMachineTypes(MachineDriverMixin, InvenTreePlugin):
def get_machine_types(self) -> list[BaseMachineType]:
"""Return all built-in machine types."""
from machine.machine_types.label_printer import LabelPrinterMachine
return [LabelPrinterMachine]
def get_machine_drivers(self) -> list[BaseDriver]:
@@ -5,7 +5,7 @@ from django.urls import reverse
from common.models import InvenTreeSetting
from InvenTree.unit_test import InvenTreeAPITestCase
from plugin.registry import registry
from plugin import registry
class InvenTreeWellKnownTest(InvenTreeAPITestCase):
@@ -10,9 +10,9 @@ from rest_framework import serializers
from common.models import InvenTreeUserSetting
from InvenTree.serializers import DependentField
from InvenTree.tasks import offload_task
from machine.machine_types import LabelPrinterBaseDriver, LabelPrinterMachine
from plugin import InvenTreePlugin
from plugin.machine import call_machine_function, registry
from plugin.machine.machine_types import LabelPrinterBaseDriver, LabelPrinterMachine
from plugin.mixins import LabelPrintingMixin
from report.models import LabelTemplate
@@ -6,7 +6,7 @@ from company.models import Company, ManufacturerPart, SupplierPart
from InvenTree.unit_test import InvenTreeAPITestCase
from order.models import PurchaseOrder, PurchaseOrderLineItem
from part.models import Part
from plugin.registry import registry
from plugin import registry
from stock.models import StockItem, StockLocation
@@ -1,11 +1,10 @@
from machine import (
from machine.machine_type import (
BaseDriver,
BaseMachineType,
MachineProperty,
MachineStatus,
registry,
)
from machine.registry import call_machine_function
from machine.registry import call_machine_function, registry
__all__ = [
'BaseDriver',
@@ -1,3 +1,8 @@
"""just re-export the machine types from the plugin InvenTree app."""
from machine.machine_types import * # noqa: F403
from machine.machine_types.label_printer import (
LabelPrinterBaseDriver,
LabelPrinterMachine,
)
__all__ = ['LabelPrinterBaseDriver', 'LabelPrinterMachine']
@@ -1,6 +1,10 @@
"""Utility class to enable simpler imports."""
from plugin.base.action.mixins import ActionMixin
from plugin.base.barcodes.helper import (
get_supported_barcode_model_codes_map,
get_supported_barcode_models,
)
from plugin.base.barcodes.mixins import BarcodeMixin, SupplierBarcodeMixin
from plugin.base.event.mixins import EventMixin
from plugin.base.icons.mixins import IconPackMixin
@@ -21,10 +25,12 @@ from plugin.base.integration.ValidationMixin import ValidationMixin
from plugin.base.integration.WellKnownMixin import WellKnownMixin
from plugin.base.label.mixins import LabelPrintingMixin
from plugin.base.locate.mixins import LocateMixin
from plugin.base.mail.mail import process_mail_in, process_mail_out
from plugin.base.mail.mixins import MailMixin
from plugin.base.supplier import helpers as supplier
from plugin.base.supplier.mixins import SupplierMixin
from plugin.base.ui.mixins import UserInterfaceMixin
from plugin.events import trigger_event
__all__ = [
'APICallMixin',
@@ -52,5 +58,10 @@ __all__ = [
'UserInterfaceMixin',
'ValidationMixin',
'WellKnownMixin',
'get_supported_barcode_model_codes_map',
'get_supported_barcode_models',
'process_mail_in',
'process_mail_out',
'supplier',
'trigger_event',
]
+1 -2
View File
@@ -14,9 +14,8 @@ from django.utils.translation import gettext_lazy as _
import common.models
import InvenTree.models
import plugin.staticfiles
from plugin import InvenTreePlugin
from plugin import InvenTreePlugin, registry
from plugin.events import PluginEvents, trigger_event
from plugin.registry import registry
class PluginConfig(InvenTree.models.MetadataMixin, models.Model):
@@ -3,10 +3,8 @@
from django.test import TestCase
from common.models import InvenTreeSetting
from plugin import InvenTreePlugin, registry
from plugin.base.event.events import trigger_event
from plugin.helpers import MixinNotImplementedError
from plugin.mixins import EventMixin
from plugin import InvenTreePlugin, MixinNotImplementedError, registry
from plugin.mixins import EventMixin, trigger_event
class EventPluginSampleTests(TestCase):
@@ -4,7 +4,7 @@ from django.test import TestCase
from common.models import InvenTreeSetting
from plugin import registry
from plugin.base.event.events import trigger_event
from plugin.mixins import trigger_event
class FilteredEventPluginSampleTests(TestCase):
@@ -3,8 +3,8 @@
from django.templatetags.static import static
from common.icons import IconPack
from plugin.base.icons.mixins import IconPackMixin
from plugin.plugin import InvenTreePlugin
from plugin import InvenTreePlugin
from plugin.mixins import IconPackMixin
class SampleIconPlugin(IconPackMixin, InvenTreePlugin):
@@ -3,8 +3,7 @@
from django.urls import reverse
from InvenTree.unit_test import InvenTreeAPITestCase
from plugin import InvenTreePlugin, registry
from plugin.helpers import MixinNotImplementedError
from plugin import InvenTreePlugin, MixinNotImplementedError, registry
from plugin.mixins import IconPackMixin
@@ -5,7 +5,7 @@ from company.models import Company
from InvenTree.unit_test import InvenTreeTestCase
from order.models import SalesOrder, SalesOrderLineItem
from part.models import BomItem, Part
from plugin.registry import registry
from plugin import registry
from stock.models import StockItem
@@ -7,10 +7,13 @@ from django.db import connection
from django.db.models.query import QuerySet
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
from plugin import InvenTreePlugin
from plugin.helpers import MixinImplementationError
from plugin import (
InvenTreePlugin,
MixinImplementationError,
call_plugin_function,
registry,
)
from plugin.mixins import ScheduleMixin
from plugin.registry import call_plugin_function, registry
class ExampleScheduledTaskPluginTests(TestCase):
@@ -1,7 +1,7 @@
"""Unit tests for action plugins."""
from InvenTree.unit_test import InvenTreeTestCase
from plugin.registry import registry
from plugin import registry
from plugin.samples.integration.simpleactionplugin import SimpleActionPlugin
@@ -6,7 +6,7 @@ from django.urls import reverse
import build.models
import part.models
from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase
from plugin.registry import registry
from plugin import registry
class SampleValidatorPluginTest(InvenTreeAPITestCase, InvenTreeTestCase):
@@ -3,8 +3,7 @@
from django.urls import reverse
from InvenTree.unit_test import InvenTreeAPITestCase
from plugin import InvenTreePlugin, registry
from plugin.helpers import MixinNotImplementedError
from plugin import InvenTreePlugin, MixinNotImplementedError, registry
from plugin.mixins import LocateMixin
@@ -6,9 +6,8 @@ from django.db import models
import structlog
from machine.machine_type import BaseDriver
from plugin import InvenTreePlugin
from plugin.machine import BaseMachineType
from plugin.machine import BaseDriver, BaseMachineType
from plugin.machine.machine_types import LabelPrinterBaseDriver, LabelPrinterMachine
from plugin.mixins import MachineDriverMixin, SettingsMixin
from report.models import LabelTemplate
@@ -3,11 +3,8 @@
from django.test import TestCase
from common.models import InvenTreeSetting
from plugin import InvenTreePlugin
from plugin.base.mail.mail import process_mail_in, process_mail_out
from plugin.helpers import MixinNotImplementedError
from plugin.mixins import MailMixin
from plugin.registry import registry
from plugin import InvenTreePlugin, MixinNotImplementedError, registry
from plugin.mixins import MailMixin, process_mail_in, process_mail_out
class MailPluginSampleTests(TestCase):
@@ -4,8 +4,8 @@ from django.conf import settings
from company.models import Company, ManufacturerPart, SupplierPart, SupplierPriceBreak
from part.models import Part
from plugin import InvenTreePlugin
from plugin.mixins import SupplierMixin, supplier
from plugin.plugin import InvenTreePlugin
class SampleSupplierPlugin(SupplierMixin, InvenTreePlugin):
+3 -3
View File
@@ -156,7 +156,7 @@ class PluginConfigInstallSerializer(serializers.Serializer):
def save(self):
"""Install a plugin from a package registry and set operational results as instance data."""
from plugin.installer import install_plugin
from plugin._internal.installer import install_plugin
data = self.validated_data
@@ -210,7 +210,7 @@ class PluginReloadSerializer(serializers.Serializer):
def save(self):
"""Reload the plugin registry."""
from plugin.registry import registry
from plugin import registry
registry.reload_plugins(
full_reload=self.validated_data.get('full_reload', False),
@@ -267,7 +267,7 @@ class PluginUninstallSerializer(serializers.Serializer):
def update(self, instance, validated_data):
"""Uninstall the specified plugin."""
from plugin.installer import uninstall_plugin
from plugin._internal.installer import uninstall_plugin
user = self.context['request'].user
+2 -2
View File
@@ -20,8 +20,8 @@ from django.contrib.staticfiles.storage import staticfiles_storage
import structlog
from plugin.lease import acquire_lease_blocking, release_lease
from plugin.registry import registry
from plugin import registry
from plugin._internal.lease import acquire_lease_blocking, release_lease
logger = structlog.get_logger('inventree')
@@ -3,7 +3,7 @@
from django import template
from django.urls import reverse
from plugin.registry import registry
from plugin import registry
register = template.Library()
+5 -6
View File
@@ -498,7 +498,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
def test_settings(self):
"""Test settings endpoint for plugin."""
from plugin.registry import registry
from plugin import registry
registry.set_plugin_state('sample', True)
url = reverse('api-plugin-settings', kwargs={'plugin': 'sample'})
@@ -519,8 +519,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
def test_plugin_filter_by_mixin(self):
"""Test filtering plugins by mixin."""
from plugin import PluginMixinEnum
from plugin.registry import registry
from plugin import PluginMixinEnum, registry
# Ensure we have some plugins loaded
registry.reload_plugins(full_reload=True, collect=True)
@@ -548,8 +547,8 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
def test_plugin_filters(self):
"""Unit testing for plugin API filters."""
from plugin import registry
from plugin.models import PluginConfig
from plugin.registry import registry
PluginConfig.objects.all().delete()
registry.reload_plugins(full_reload=True, collect=True)
@@ -709,7 +708,7 @@ class PluginLockedSettingsTest(PluginMixin, InvenTreeAPITestCase):
"""Activate the sample plugin and confirm it is ready."""
super().setUp()
from plugin.registry import registry
from plugin import registry
registry.set_plugin_state(self.PLUGIN_SLUG, True)
@@ -844,7 +843,7 @@ class PluginUnauthenticatedAccessTest(PluginMixin, InvenTreeAPITestCase):
def setUp(self):
"""Activate sample plugin, then log out to simulate an anonymous client."""
super().setUp()
from plugin.registry import registry
from plugin import registry
registry.set_plugin_state(self.PLUGIN_SLUG, True)
self.client.logout()
+43 -35
View File
@@ -17,9 +17,8 @@ from django.test import TestCase, override_settings
import plugin.templatetags.plugin_extras as plugin_tags
from InvenTree.unit_test import PluginRegistryMixin, TestQueryMixin
from plugin import InvenTreePlugin, PluginMixinEnum
from plugin.installer import install_plugin, update_plugins_file
from plugin.registry import registry
from plugin import InvenTreePlugin, PluginMixinEnum, registry
from plugin._internal.installer import install_plugin, update_plugins_file
from plugin.samples.integration.another_sample import (
NoIntegrationPlugin,
WrongIntegrationPlugin,
@@ -817,7 +816,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
from django.db.utils import ProgrammingError
from common.models import InvenTreeSetting
from plugin.lease import release_lease, try_acquire_lease
from plugin._internal.lease import release_lease, try_acquire_lease
with mock.patch.object(
InvenTreeSetting.objects,
@@ -836,7 +835,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_lease_acquire_release(self):
"""Test that try_acquire_lease / release_lease provide mutual exclusion."""
from plugin.lease import release_lease, try_acquire_lease
from plugin._internal.lease import release_lease, try_acquire_lease
key = '_test_lease_acquire_release'
@@ -859,7 +858,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
from django.utils import timezone
from common.settings import set_global_setting
from plugin.lease import CLAIM_LEASE, release_lease, try_acquire_lease
from plugin._internal.lease import CLAIM_LEASE, release_lease, try_acquire_lease
key = '_test_lease_expiry'
@@ -881,7 +880,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
"""Test that acquire_lease_blocking gives up after its timeout, rather than hanging."""
import time
from plugin.lease import (
from plugin._internal.lease import (
acquire_lease_blocking,
release_lease,
try_acquire_lease,
@@ -916,7 +915,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
installed, and no future check would ever retry it.
"""
from common.settings import get_global_setting
from plugin.registry import registry
from plugin import registry
hash_during_install = 'unset'
@@ -933,16 +932,17 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
with (
tempfile.TemporaryDirectory() as tmpdir,
mock.patch('plugin.installer._env_plugin_hash_cache', None),
mock.patch('plugin._internal.installer._env_plugin_hash_cache', None),
mock.patch(
'plugin.installer.plugin_env_marker_path',
'plugin._internal.installer.plugin_env_marker_path',
return_value=Path(tmpdir) / '.inventree_plugins_hash',
),
mock.patch(
'plugin.installer.plugins_file_hash', return_value='test-hash-1'
'plugin._internal.installer.plugins_file_hash',
return_value='test-hash-1',
),
mock.patch(
'plugin.installer.install_plugins_file',
'plugin._internal.installer.install_plugins_file',
side_effect=fake_install_plugins_file,
),
):
@@ -960,21 +960,24 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_install_plugin_file_failure_does_not_persist_hash(self):
"""Test that a failed install leaves the hash unset, so it is retried next time."""
from common.settings import get_global_setting, set_global_setting
from plugin.registry import registry
from plugin import registry
set_global_setting('_PLUGIN_FILE_HASH', '')
with (
tempfile.TemporaryDirectory() as tmpdir,
mock.patch('plugin.installer._env_plugin_hash_cache', None),
mock.patch('plugin._internal.installer._env_plugin_hash_cache', None),
mock.patch(
'plugin.installer.plugin_env_marker_path',
'plugin._internal.installer.plugin_env_marker_path',
return_value=Path(tmpdir) / '.inventree_plugins_hash',
),
mock.patch(
'plugin.installer.plugins_file_hash', return_value='test-hash-2'
'plugin._internal.installer.plugins_file_hash',
return_value='test-hash-2',
),
mock.patch(
'plugin._internal.installer.install_plugins_file', return_value=False
),
mock.patch('plugin.installer.install_plugins_file', return_value=False),
):
registry.install_plugin_file()
@@ -982,20 +985,21 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_install_plugin_file_skips_if_already_current(self):
"""Test that install_plugin_file() is a no-op once the hash already matches."""
from plugin.registry import registry
from plugin import registry
with (
tempfile.TemporaryDirectory() as tmpdir,
mock.patch('plugin.installer._env_plugin_hash_cache', None),
mock.patch('plugin._internal.installer._env_plugin_hash_cache', None),
mock.patch(
'plugin.installer.plugin_env_marker_path',
'plugin._internal.installer.plugin_env_marker_path',
return_value=Path(tmpdir) / '.inventree_plugins_hash',
),
mock.patch(
'plugin.installer.plugins_file_hash', return_value='test-hash-3'
'plugin._internal.installer.plugins_file_hash',
return_value='test-hash-3',
),
mock.patch(
'plugin.installer.install_plugins_file', return_value=True
'plugin._internal.installer.install_plugins_file', return_value=True
) as mock_install,
):
registry.install_plugin_file()
@@ -1015,7 +1019,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
previous environment's successful install of the exact same file.
"""
from common.settings import set_global_setting
from plugin.registry import registry
from plugin import registry
# Simulate a database that already believes this hash is installed -
# as if a *previous* (now-replaced) environment installed it
@@ -1023,17 +1027,18 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
with (
tempfile.TemporaryDirectory() as tmpdir,
mock.patch('plugin.installer._env_plugin_hash_cache', None),
mock.patch('plugin._internal.installer._env_plugin_hash_cache', None),
mock.patch(
'plugin.installer.plugin_env_marker_path',
'plugin._internal.installer.plugin_env_marker_path',
# A fresh environment has no marker file at all
return_value=Path(tmpdir) / '.inventree_plugins_hash',
),
mock.patch(
'plugin.installer.plugins_file_hash', return_value='test-hash-4'
'plugin._internal.installer.plugins_file_hash',
return_value='test-hash-4',
),
mock.patch(
'plugin.installer.install_plugins_file', return_value=True
'plugin._internal.installer.install_plugins_file', return_value=True
) as mock_install,
):
registry.install_plugin_file()
@@ -1057,7 +1062,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
process start.
"""
from common.settings import set_global_setting
from plugin.registry import registry
from plugin import registry
set_global_setting('_PLUGIN_FILE_HASH', '')
@@ -1066,13 +1071,16 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
marker.write_text.side_effect = OSError('Read-only file system')
with (
mock.patch('plugin.installer._env_plugin_hash_cache', None),
mock.patch('plugin.installer.plugin_env_marker_path', return_value=marker),
mock.patch('plugin._internal.installer._env_plugin_hash_cache', None),
mock.patch(
'plugin.installer.plugins_file_hash', return_value='test-hash-5'
'plugin._internal.installer.plugin_env_marker_path', return_value=marker
),
mock.patch(
'plugin.installer.install_plugins_file', return_value=True
'plugin._internal.installer.plugins_file_hash',
return_value='test-hash-5',
),
mock.patch(
'plugin._internal.installer.install_plugins_file', return_value=True
) as mock_install,
):
registry.install_plugin_file()
@@ -1085,8 +1093,8 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_builtin_mandatory_plugins(self):
"""Test that mandatory builtin plugins are always loaded."""
from plugin import registry
from plugin.models import PluginConfig
from plugin.registry import registry
# Start with a 'clean slate'
PluginConfig.objects.all().delete()
@@ -1131,8 +1139,8 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_mandatory_plugins(self):
"""Test that plugins marked as 'mandatory' are always active."""
from plugin import registry
from plugin.models import PluginConfig
from plugin.registry import registry
# Start with a 'clean slate'
PluginConfig.objects.all().delete()
@@ -1187,8 +1195,8 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_with_mixin(self):
"""Tests for the 'with_mixin' registry method."""
from plugin import registry
from plugin.models import PluginConfig
from plugin.registry import registry
self.ensurePluginsLoaded()
@@ -14,8 +14,8 @@ from django.test import TestCase
import plugin.staticfiles as plugin_staticfiles
from InvenTree.unit_test import PluginRegistryMixin
from plugin.lease import try_acquire_lease
from plugin.registry import registry
from plugin import registry
from plugin._internal.lease import try_acquire_lease
class FakePlugin:
@@ -3,9 +3,8 @@
import structlog
from rest_framework import serializers
from machine.machine_type import BaseDriver
from plugin import InvenTreePlugin
from plugin.machine import BaseMachineType
from plugin.machine import BaseDriver, BaseMachineType
from plugin.machine.machine_types import LabelPrinterBaseDriver
from plugin.mixins import MachineDriverMixin, SettingsMixin
+3 -3
View File
@@ -15,7 +15,7 @@ PLUGIN_BASE = 'plugin' # Constant for links
def get_plugin_urls():
"""Returns a urlpattern that can be integrated into the global urls."""
from plugin.registry import registry
from plugin import registry
urls = []
@@ -60,7 +60,7 @@ def get_plugin_urls():
def wellknownindexview(request):
"""Simple view that returns a list of all well-known URLs as JSON."""
from plugin.registry import registry
from plugin import registry
well_known_urls = {}
if registry.is_ready:
@@ -78,7 +78,7 @@ def wellknownindexview(request):
def get_wellknown_urls():
"""Returns a urlpattern that can be integrated into the global urls (as redirects)."""
from plugin.registry import registry
from plugin import registry
urls = []
+1 -2
View File
@@ -33,8 +33,7 @@ from common.settings import get_global_setting
from company.models import Company
from InvenTree.helpers_model import get_base_url
from InvenTree.models import MetadataMixin
from plugin import InvenTreePlugin, PluginMixinEnum
from plugin.registry import registry
from plugin import InvenTreePlugin, PluginMixinEnum, registry
from report.security import restricted_db_access
try:
+1 -1
View File
@@ -94,7 +94,7 @@ def print_labels(
and will continuously update the status of the DataOutput object.
"""
from common.models import DataOutput
from plugin.registry import registry
from plugin import registry
from report.models import LabelTemplate
try:
+1 -1
View File
@@ -32,7 +32,7 @@ from InvenTree.template import InvenTreeStorageTemplateLoader
from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase
from order.models import PurchaseOrder, ReturnOrder, SalesOrder
from part.models import Part
from plugin.registry import registry
from plugin import registry
from report.models import LabelTemplate, ReportTemplate
from report.tasks import print_labels
from stock.models import StockItem, StockLocation
+2 -2
View File
@@ -1371,7 +1371,7 @@ class StockBarcodeTest(StockTestBase):
def test_location_barcode_basics(self):
"""Simple tests for the StockLocation barcode integration."""
# Set the barcode plugin to use the legacy barcode format
from plugin.registry import registry
from plugin import registry
plugin = registry.get_plugin('inventreebarcode')
@@ -1760,7 +1760,7 @@ class TestResultTest(StockTestBase):
"""Test duplicate item behaviour."""
# Create an example stock item by copying one from the database (because we are lazy)
from plugin.registry import registry
from plugin import registry
item = StockItem.objects.get(pk=522)