2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-14 16:29:57 +00:00

App ready warning (#10938)

* Fix for currency functions

- Prevent database access until after the 'common' app has loaded

* Add decorator to selectively ignore warnings

* Add reference to PR

* Fix variable assignment

* Use functools.wraps

* Add wrapper for loading machine registry

* Move decorator to ready.py

* Add missing code

* Set backup values to match default currency codes

* Bump API version
This commit is contained in:
Oliver
2025-12-04 19:30:14 +11:00
committed by GitHub
parent 2ffc2cb9fc
commit c443b4e9b8
8 changed files with 105 additions and 22 deletions

View File

@@ -1,11 +1,15 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 428 INVENTREE_API_VERSION = 429
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v429 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10938
- Adjust default values for currency codes in the API schema
- Note that this does not change any functional behavior, only the schema documentation
v428 -> 2025-11-28 : https://github.com/inventree/InvenTree/pull/10926 v428 -> 2025-11-28 : https://github.com/inventree/InvenTree/pull/10926
- Various typo fixes in API - no functional changes - Various typo fixes in API - no functional changes

View File

@@ -18,6 +18,7 @@ import InvenTree.conversion
import InvenTree.ready import InvenTree.ready
import InvenTree.tasks import InvenTree.tasks
from InvenTree.config import get_setting from InvenTree.config import get_setting
from InvenTree.ready import ignore_ready_warning
logger = structlog.get_logger('inventree') logger = structlog.get_logger('inventree')
MIGRATIONS_CHECK_DONE = False MIGRATIONS_CHECK_DONE = False
@@ -70,9 +71,7 @@ class InvenTreeConfig(AppConfig):
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations) InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
self.update_site_url() self.update_site_url()
self.load_unit_registry()
# Ensure the unit registry is loaded
InvenTree.conversion.get_unit_registry()
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV: if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
self.add_user_on_startup() self.add_user_on_startup()
@@ -84,6 +83,7 @@ class InvenTreeConfig(AppConfig):
social_account_updated.connect(sso.ensure_sso_groups) social_account_updated.connect(sso.ensure_sso_groups)
@ignore_ready_warning
def remove_obsolete_tasks(self): def remove_obsolete_tasks(self):
"""Delete any obsolete scheduled tasks in the database.""" """Delete any obsolete scheduled tasks in the database."""
obsolete = [ obsolete = [
@@ -112,6 +112,7 @@ class InvenTreeConfig(AppConfig):
except Exception: except Exception:
logger.exception('Failed to remove obsolete tasks - database not ready') logger.exception('Failed to remove obsolete tasks - database not ready')
@ignore_ready_warning
def start_background_tasks(self): def start_background_tasks(self):
"""Start all background tests for InvenTree.""" """Start all background tests for InvenTree."""
logger.info('Starting background tasks...') logger.info('Starting background tasks...')
@@ -171,6 +172,7 @@ class InvenTreeConfig(AppConfig):
logger.info('Started %s scheduled background tasks...', len(tasks)) logger.info('Started %s scheduled background tasks...', len(tasks))
@ignore_ready_warning
def add_heartbeat(self): def add_heartbeat(self):
"""Ensure there is at least one background task in the queue.""" """Ensure there is at least one background task in the queue."""
import django_q.models import django_q.models
@@ -185,6 +187,7 @@ class InvenTreeConfig(AppConfig):
except Exception: except Exception:
pass pass
@ignore_ready_warning
def collect_tasks(self): def collect_tasks(self):
"""Collect all background tasks.""" """Collect all background tasks."""
for app_name, app in apps.app_configs.items(): for app_name, app in apps.app_configs.items():
@@ -197,6 +200,7 @@ class InvenTreeConfig(AppConfig):
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
logger.exception('Error loading tasks for %s: %s', app_name, e) logger.exception('Error loading tasks for %s: %s', app_name, e)
@ignore_ready_warning
def update_site_url(self): def update_site_url(self):
"""Update the site URL setting. """Update the site URL setting.
@@ -223,6 +227,12 @@ class InvenTreeConfig(AppConfig):
except Exception: except Exception:
pass pass
@ignore_ready_warning
def load_unit_registry(self):
"""Ensure the unit registry is loaded."""
InvenTree.conversion.get_unit_registry()
@ignore_ready_warning
def add_user_on_startup(self): def add_user_on_startup(self):
"""Add a user on startup.""" """Add a user on startup."""
# stop if checks were already created # stop if checks were already created
@@ -281,6 +291,7 @@ class InvenTreeConfig(AppConfig):
except IntegrityError: except IntegrityError:
logger.warning('The user "%s" could not be created', add_user) logger.warning('The user "%s" could not be created', add_user)
@ignore_ready_warning
def add_user_from_file(self): def add_user_from_file(self):
"""Add the superuser from a file.""" """Add the superuser from a file."""
# stop if checks were already created # stop if checks were already created

View File

@@ -1,8 +1,31 @@
"""Functions to check if certain parts of InvenTree are ready.""" """Functions to check if certain parts of InvenTree are ready."""
import functools
import inspect import inspect
import os import os
import sys import sys
import warnings
# Keep track of loaded apps, to prevent multiple executions of ready functions
_loaded_apps = set()
def clearLoadedApps():
"""Clear the set of loaded apps."""
global _loaded_apps
_loaded_apps = set()
def setAppLoaded(app_name: str):
"""Mark an app as loaded."""
global _loaded_apps
_loaded_apps.add(app_name)
def isAppLoaded(app_name: str) -> bool:
"""Return True if the app has been marked as loaded."""
global _loaded_apps
return app_name in _loaded_apps
def isInTestMode(): def isInTestMode():
@@ -157,3 +180,22 @@ def isPluginRegistryLoaded():
from plugin import registry from plugin import registry
return registry.plugins_loaded return registry.plugins_loaded
def ignore_ready_warning(func):
"""Decorator to ignore 'AppRegistryNotReady' warnings in functions called during app ready phase.
Ref: https://github.com/inventree/InvenTree/issues/10806
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore',
message='Accessing the database during app initialization is discouraged',
category=RuntimeWarning,
)
return func(*args, **kwargs)
return wrapper

View File

@@ -6,6 +6,7 @@ import structlog
import InvenTree.ready import InvenTree.ready
from common.settings import get_global_setting, set_global_setting from common.settings import get_global_setting, set_global_setting
from InvenTree.ready import ignore_ready_warning
logger = structlog.get_logger('inventree') logger = structlog.get_logger('inventree')
@@ -20,11 +21,16 @@ class CommonConfig(AppConfig):
def ready(self): def ready(self):
"""Initialize restart flag clearance on startup.""" """Initialize restart flag clearance on startup."""
from InvenTree.ready import setAppLoaded
setAppLoaded(self.name)
if InvenTree.ready.isRunningMigrations(): # pragma: no cover if InvenTree.ready.isRunningMigrations(): # pragma: no cover
return return
self.clear_restart_flag() self.clear_restart_flag()
@ignore_ready_warning
def clear_restart_flag(self): def clear_restart_flag(self):
"""Clear the SERVER_RESTART_REQUIRED setting.""" """Clear the SERVER_RESTART_REQUIRED setting."""
try: try:

View File

@@ -11,6 +11,7 @@ import structlog
from moneyed import CURRENCIES from moneyed import CURRENCIES
import InvenTree.helpers import InvenTree.helpers
import InvenTree.ready
logger = structlog.get_logger('inventree') logger = structlog.get_logger('inventree')
@@ -19,15 +20,18 @@ def currency_code_default(create: bool = True):
"""Returns the default currency code (or USD if not specified).""" """Returns the default currency code (or USD if not specified)."""
from common.settings import get_global_setting from common.settings import get_global_setting
try: code = ''
code = get_global_setting(
'INVENTREE_DEFAULT_CURRENCY', create=create, cache=True
)
except Exception: # pragma: no cover
# Database may not yet be ready, no need to throw an error here
code = ''
if code not in CURRENCIES: if InvenTree.ready.isAppLoaded('common'):
try:
code = get_global_setting(
'INVENTREE_DEFAULT_CURRENCY', create=create, cache=True
)
except Exception: # pragma: no cover
# Database may not yet be ready, no need to throw an error here
code = ''
if not code or code not in CURRENCIES:
code = 'USD' # pragma: no cover code = 'USD' # pragma: no cover
return code return code
@@ -47,9 +51,13 @@ def currency_codes() -> list:
"""Returns the current currency codes.""" """Returns the current currency codes."""
from common.settings import get_global_setting from common.settings import get_global_setting
codes = get_global_setting( codes = None
'CURRENCY_CODES', create=False, environment_key='INVENTREE_CURRENCY_CODES'
).strip() # Ensure we do not hit the database until the common app is loaded
if InvenTree.ready.isAppLoaded('common'):
codes = get_global_setting(
'CURRENCY_CODES', create=False, environment_key='INVENTREE_CURRENCY_CODES'
).strip()
if not codes: if not codes:
codes = currency_codes_default_list() codes = currency_codes_default_list()

View File

@@ -7,6 +7,7 @@ import structlog
from InvenTree.ready import ( from InvenTree.ready import (
canAppAccessDatabase, canAppAccessDatabase,
ignore_ready_warning,
isImportingData, isImportingData,
isInMainThread, isInMainThread,
isPluginRegistryLoaded, isPluginRegistryLoaded,
@@ -32,12 +33,17 @@ class MachineConfig(AppConfig):
logger.debug('Machine app: Skipping machine loading sequence') logger.debug('Machine app: Skipping machine loading sequence')
return return
from machine import registry
try: try:
logger.info('Loading InvenTree machines') self.initialize_registry()
if not registry.is_ready:
registry.initialize(main=isInMainThread())
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError):
# Database might not yet be ready # Database might not yet be ready
logger.warn('Database was not ready for initializing machines') logger.warn('Database was not ready for initializing machines')
@ignore_ready_warning
def initialize_registry(self):
"""Initialize the machine registry."""
from machine import registry
if not registry.is_ready:
logger.info('Loading InvenTree machines')
registry.initialize(main=isInMainThread())

View File

@@ -9,7 +9,12 @@ from django.apps import AppConfig
import structlog import structlog
from maintenance_mode.core import set_maintenance_mode from maintenance_mode.core import set_maintenance_mode
from InvenTree.ready import canAppAccessDatabase, isInMainThread, isInWorkerThread from InvenTree.ready import (
canAppAccessDatabase,
ignore_ready_warning,
isInMainThread,
isInWorkerThread,
)
from plugin import registry from plugin import registry
logger = structlog.get_logger('inventree') logger = structlog.get_logger('inventree')
@@ -24,6 +29,7 @@ class PluginAppConfig(AppConfig):
"""The ready method is extended to initialize plugins.""" """The ready method is extended to initialize plugins."""
self.reload_plugin_registry() self.reload_plugin_registry()
@ignore_ready_warning
def reload_plugin_registry(self): def reload_plugin_registry(self):
"""Reload the plugin registry.""" """Reload the plugin registry."""
if not isInMainThread() and not isInWorkerThread(): if not isInMainThread() and not isInWorkerThread():

View File

@@ -1466,7 +1466,7 @@ def schema(
'False' # Disable plugins to ensure they are kep out of schema 'False' # Disable plugins to ensure they are kep out of schema
) )
envs['INVENTREE_CURRENCY_CODES'] = ( envs['INVENTREE_CURRENCY_CODES'] = (
'AUD,CNY,EUR,USD' # Default currency codes to ensure they are stable 'AUD,CAD,CNY,EUR,GBP,JPY,NZD,USD' # Default currency codes to ensure they are stable
) )
manage(c, cmd, pty=True, env=envs) manage(c, cmd, pty=True, env=envs)