mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	* 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:
		| @@ -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): | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
| @@ -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', | ||||
| ] | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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: {}, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user