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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,6 +20,9 @@ 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
|
||||||
|
|
||||||
|
code = ''
|
||||||
|
|
||||||
|
if InvenTree.ready.isAppLoaded('common'):
|
||||||
try:
|
try:
|
||||||
code = get_global_setting(
|
code = get_global_setting(
|
||||||
'INVENTREE_DEFAULT_CURRENCY', create=create, cache=True
|
'INVENTREE_DEFAULT_CURRENCY', create=create, cache=True
|
||||||
@@ -27,7 +31,7 @@ def currency_code_default(create: bool = True):
|
|||||||
# Database may not yet be ready, no need to throw an error here
|
# Database may not yet be ready, no need to throw an error here
|
||||||
code = ''
|
code = ''
|
||||||
|
|
||||||
if code not in CURRENCIES:
|
if not code or code not in CURRENCIES:
|
||||||
code = 'USD' # pragma: no cover
|
code = 'USD' # pragma: no cover
|
||||||
|
|
||||||
return code
|
return code
|
||||||
@@ -47,6 +51,10 @@ 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 = None
|
||||||
|
|
||||||
|
# Ensure we do not hit the database until the common app is loaded
|
||||||
|
if InvenTree.ready.isAppLoaded('common'):
|
||||||
codes = get_global_setting(
|
codes = get_global_setting(
|
||||||
'CURRENCY_CODES', create=False, environment_key='INVENTREE_CURRENCY_CODES'
|
'CURRENCY_CODES', create=False, environment_key='INVENTREE_CURRENCY_CODES'
|
||||||
).strip()
|
).strip()
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
2
tasks.py
2
tasks.py
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user