2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 03:26:45 +00:00

Call machine func (#9191) (#9298)

* Force label printing to background worker

* Refactor "check_reload" state of machine registry

- In line with plugin registry
- More work can be done here (i.e. session caching)

* Better handling of call_plugin_function

* Wrapper for calling machine function

* Use AttributeError instead

* Simplify function offloading

* Check plugin registry hash when reloading machine registry

* Cleanup

* Fixes

* Adjust unit test

* Cleanup

* Allow running in foreground if background worker not running

* Simplify call structure
This commit is contained in:
Oliver 2025-03-14 13:40:37 +11:00 committed by GitHub
parent 75420fc97e
commit af0a2822d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 118 additions and 16 deletions

View File

@ -233,17 +233,21 @@ class BaseMachineType(
# always fetch the machine_config if needed to ensure we get the newest reference
from .models import MachineConfig
return MachineConfig.objects.get(pk=self.pk)
return MachineConfig.objects.filter(pk=self.pk).first()
@property
def name(self):
"""The machines name."""
return self.machine_config.name
if config := self.machine_config:
return config.name
@property
def active(self):
"""The machines active status."""
return self.machine_config.active
if config := self.machine_config:
return config.active
return False
# --- hook functions
def initialize(self):

View File

@ -5,7 +5,9 @@ from typing import Union, cast
from uuid import UUID
from django.core.cache import cache
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
from common.settings import get_global_setting, set_global_setting
from InvenTree.helpers_mixin import get_shared_class_instance_state_mixin
from machine.machine_type import BaseDriver, BaseMachineType
@ -29,6 +31,7 @@ class MachineRegistry(
self.base_drivers: list[type[BaseDriver]] = []
# Keep an internal hash of the machine registry state
self._hash = None
@property
@ -266,8 +269,14 @@ class MachineRegistry(
"""Calculate a hash of the machine registry state."""
from hashlib import md5
from plugin import registry as plugin_registry
data = md5()
# If the plugin registry has changed, the machine registry hash will change
plugin_registry.update_plugin_hash()
data.update(plugin_registry.registry_hash.encode())
for pk, machine in self.machines.items():
data.update(str(pk).encode())
try:
@ -283,16 +292,84 @@ class MachineRegistry(
if not self._hash:
self._hash = self._calculate_registry_hash()
last_hash = self.get_shared_state('hash', None)
try:
reg_hash = get_global_setting('_MACHINE_REGISTRY_HASH', '', create=False)
except Exception as exc:
logger.exception('Failed to get machine registry hash: %s', str(exc))
return False
if last_hash and last_hash != self._hash:
if reg_hash and reg_hash != self._hash:
logger.info('Machine registry has changed - reloading machines')
self.reload_machines()
return True
return False
def _update_registry_hash(self):
"""Save the current registry hash."""
self._hash = self._calculate_registry_hash()
self.set_shared_state('hash', self._hash)
try:
old_hash = get_global_setting('_MACHINE_REGISTRY_HASH')
except Exception:
old_hash = None
if old_hash != self._hash:
try:
logger.info('Updating machine registry hash: %s', str(self._hash))
set_global_setting('_MACHINE_REGISTRY_HASH', self._hash)
except (IntegrityError, OperationalError, ProgrammingError):
pass
except Exception as exc:
logger.exception('Failed to update machine registry hash: %s', str(exc))
def call_machine_function(
self, machine_id: str, function_name: str, *args, **kwargs
):
"""Call a named function against a machine instance.
Arguments:
machine_id: The UUID of the machine to call the function against
function_name: The name of the function to call
"""
logger.info('call_machine_function: %s -> %s', machine_id, function_name)
raise_error = kwargs.pop('raise_error', True)
self._check_reload()
# Fetch the machine instance based on the provided UUID
machine = self.get_machine(machine_id)
if not machine:
if raise_error:
raise AttributeError(f"Machine '{machine_id}' not found")
return
# Fetch the driver instance based on the machine driver
driver = machine.driver
if not driver:
if raise_error:
raise AttributeError(f"Machine '{machine_id}' has no specified driver")
return
# The function must be registered against the driver
func = getattr(driver, function_name)
if not func or not callable(func):
if raise_error:
raise AttributeError(
f"Driver '{driver.SLUG}' has no callable method '{function_name}'"
)
return
return func(machine, *args, **kwargs)
registry: MachineRegistry = MachineRegistry()
def call_machine_function(machine_id: str, function: str, *args, **kwargs):
"""Global helper function to call a specific function on a machine instance."""
return registry.call_machine_function(machine_id, function, *args, **kwargs)

View File

@ -2,6 +2,7 @@
from typing import cast
from django.conf import settings
from django.http import JsonResponse
from django.utils.translation import gettext_lazy as _
@ -12,7 +13,7 @@ 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 registry
from plugin.machine import call_machine_function, registry
from plugin.mixins import LabelPrintingMixin
from report.models import LabelTemplate
@ -91,12 +92,15 @@ class InvenTreeLabelPlugin(LabelPrintingMixin, InvenTreePlugin):
user=request.user,
)
# execute the print job
if driver.USE_BACKGROUND_WORKER is False:
return driver.print_labels(machine, label, items, **print_kwargs)
offload_task(
driver.print_labels, machine, label, items, group='plugin', **print_kwargs
call_machine_function,
machine.pk,
'print_labels',
label,
items,
force_sync=settings.TESTING or driver.USE_BACKGROUND_WORKER,
group='plugin',
**print_kwargs,
)
return JsonResponse({

View File

@ -1,3 +1,10 @@
from machine import BaseDriver, BaseMachineType, MachineStatus, registry
from machine.registry import call_machine_function
__all__ = ['BaseDriver', 'BaseMachineType', 'MachineStatus', 'registry']
__all__ = [
'BaseDriver',
'BaseMachineType',
'MachineStatus',
'call_machine_function',
'registry',
]

View File

@ -170,13 +170,22 @@ class PluginsRegistry:
# Check if the registry needs to be reloaded
self.check_reload()
raise_error = kwargs.pop('raise_error', True)
plugin = self.get_plugin(slug)
if not plugin:
if raise_error:
raise AttributeError(f"Plugin '{slug}' not found")
return
plugin_func = getattr(plugin, func)
if not plugin_func or not callable(plugin_func):
if raise_error:
raise AttributeError(f"Plugin '{slug}' has no callable method '{func}'")
return
return plugin_func(*args, **kwargs)
# region registry functions

View File

@ -65,12 +65,13 @@ class ExampleScheduledTaskPluginTests(TestCase):
self.assertEqual(len(scheduled_plugin_tasks), 0)
def test_calling(self):
"""Check if a function can be called without errors."""
"""Test calling of plugin functions by name."""
# Check with right parameters
self.assertEqual(call_plugin_function('schedule', 'member_func'), False)
# Check with wrong key
self.assertEqual(call_plugin_function('does_not_exist', 'member_func'), None)
with self.assertRaises(AttributeError):
call_plugin_function('does_not_exist', 'member_func'), None
class ScheduledTaskPluginTests(TestCase):

View File

@ -511,7 +511,7 @@ export function MachineListTable({
}, [machineDrivers, createFormMachineType]);
const createMachineForm = useCreateApiFormModal({
title: t`Add machine`,
title: t`Add Machine`,
url: ApiEndpoints.machine_list,
fields: {
name: {},