mirror of
https://github.com/inventree/InvenTree.git
synced 2026-02-19 13:18:03 +00:00
[setup] invoke command updates (#11340)
* invoke command updates - wait for db before migrating data - improve task state reporting - early return from isGeneratingSchema * Disable warning message (for now) * Fix typo - This caused large delay when restoring data * Remove debug statement * Add warning message if isGeneratingSchema called falls through unexpectedly
This commit is contained in:
@@ -84,10 +84,11 @@ class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
|
|||||||
|
|
||||||
r -= 1
|
r -= 1
|
||||||
|
|
||||||
if r == 0:
|
# Disable this warning message (for now) as it is confusing users with no upside
|
||||||
logger.warning(
|
# if r == 0:
|
||||||
'Failed to set maintenance mode state after %s retries', retries
|
# logger.warning(
|
||||||
)
|
# 'Failed to set maintenance mode state after %s retries', retries
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeMailLoggingBackend(BaseEmailBackend):
|
class InvenTreeMailLoggingBackend(BaseEmailBackend):
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
# Keep track of loaded apps, to prevent multiple executions of ready functions
|
# Keep track of loaded apps, to prevent multiple executions of ready functions
|
||||||
_loaded_apps = set()
|
_loaded_apps = set()
|
||||||
|
|
||||||
@@ -33,6 +38,11 @@ def isInTestMode():
|
|||||||
return 'test' in sys.argv or sys.argv[0].endswith('pytest')
|
return 'test' in sys.argv or sys.argv[0].endswith('pytest')
|
||||||
|
|
||||||
|
|
||||||
|
def isWaitingForDatabase():
|
||||||
|
"""Return True if we are currently waiting for the database to be ready."""
|
||||||
|
return 'wait_for_db' in sys.argv
|
||||||
|
|
||||||
|
|
||||||
def isImportingData():
|
def isImportingData():
|
||||||
"""Returns True if the database is currently importing (or exporting) data, e.g. 'loaddata' command is performed."""
|
"""Returns True if the database is currently importing (or exporting) data, e.g. 'loaddata' command is performed."""
|
||||||
return any(x in sys.argv for x in ['flush', 'loaddata', 'dumpdata'])
|
return any(x in sys.argv for x in ['flush', 'loaddata', 'dumpdata'])
|
||||||
@@ -61,7 +71,7 @@ def isRunningBackup():
|
|||||||
'backup',
|
'backup',
|
||||||
'restore',
|
'restore',
|
||||||
'dbbackup',
|
'dbbackup',
|
||||||
'dbresotore',
|
'dbrestore',
|
||||||
'mediabackup',
|
'mediabackup',
|
||||||
'mediarestore',
|
'mediarestore',
|
||||||
]
|
]
|
||||||
@@ -82,11 +92,23 @@ def isGeneratingSchema():
|
|||||||
if isInTestMode():
|
if isInTestMode():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if isWaitingForDatabase():
|
||||||
|
return False
|
||||||
|
|
||||||
if 'schema' in sys.argv:
|
if 'schema' in sys.argv:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# This is a very inefficient call - so we only use it as a last resort
|
# This is a very inefficient call - so we only use it as a last resort
|
||||||
return any('drf_spectacular' in frame.filename for frame in inspect.stack())
|
result = any('drf_spectacular' in frame.filename for frame in inspect.stack())
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
# We should only get here if we *are* generating schema
|
||||||
|
# Any other time this is called, it should be from a server thread, worker thread, or test mode
|
||||||
|
logger.warning(
|
||||||
|
'isGeneratingSchema called outside of expected contexts - this may be a sign of a problem with the ready() function'
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def isInWorkerThread():
|
def isInWorkerThread():
|
||||||
|
|||||||
48
tasks.py
48
tasks.py
@@ -139,16 +139,16 @@ def state_logger(fn=None, method_name=None):
|
|||||||
"""Decorator to log state markers before/after function execution, optionally accepting arguments."""
|
"""Decorator to log state markers before/after function execution, optionally accepting arguments."""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func.method_name = method_name or f'invoke task named `{func.__name__}`'
|
func.method_name = method_name or func.__name__
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped(c, *args, **kwargs):
|
def wrapped(c, *args, **kwargs):
|
||||||
do_log = is_debug_environment()
|
do_log = is_debug_environment()
|
||||||
if do_log:
|
if do_log:
|
||||||
info(f'# {func.method_name}| start')
|
info(f'# task | {func.method_name} | start')
|
||||||
func(c, *args, **kwargs)
|
func(c, *args, **kwargs)
|
||||||
if do_log:
|
if do_log:
|
||||||
info(f'# {func.method_name}| done')
|
info(f'# task | {func.method_name} | done')
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
@@ -530,10 +530,18 @@ def check_file_existence(filename: Path, overwrite: bool = False):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
@state_logger
|
||||||
|
def wait(c):
|
||||||
|
"""Wait until the database connection is ready."""
|
||||||
|
info('Waiting for database connection...')
|
||||||
|
return manage(c, 'wait_for_db')
|
||||||
|
|
||||||
|
|
||||||
# Install tasks
|
# Install tasks
|
||||||
# region tasks
|
# region tasks
|
||||||
@task(help={'uv': 'Use UV (experimental package manager)'})
|
@task(help={'uv': 'Use UV (experimental package manager)'})
|
||||||
@state_logger('TASK01')
|
@state_logger
|
||||||
def plugins(c, uv=False):
|
def plugins(c, uv=False):
|
||||||
"""Installs all plugins as specified in 'plugins.txt'."""
|
"""Installs all plugins as specified in 'plugins.txt'."""
|
||||||
from src.backend.InvenTree.InvenTree.config import ( # type: ignore[import]
|
from src.backend.InvenTree.InvenTree.config import ( # type: ignore[import]
|
||||||
@@ -553,7 +561,7 @@ def plugins(c, uv=False):
|
|||||||
'dev': 'Install development requirements instead of production requirements',
|
'dev': 'Install development requirements instead of production requirements',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@state_logger('TASK02')
|
@state_logger
|
||||||
def install(c, uv=False, skip_plugins=False, dev=False):
|
def install(c, uv=False, skip_plugins=False, dev=False):
|
||||||
"""Installs required python packages."""
|
"""Installs required python packages."""
|
||||||
if dev:
|
if dev:
|
||||||
@@ -639,7 +647,7 @@ def rebuild_thumbnails(c):
|
|||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
@state_logger('TASK09')
|
@state_logger
|
||||||
def clean_settings(c):
|
def clean_settings(c):
|
||||||
"""Clean the setting tables of old settings."""
|
"""Clean the setting tables of old settings."""
|
||||||
info('Cleaning old settings from the database')
|
info('Cleaning old settings from the database')
|
||||||
@@ -669,7 +677,7 @@ def remove_mfa(c, mail='', username=''):
|
|||||||
'skip_plugins': 'Ignore collection of plugin static files',
|
'skip_plugins': 'Ignore collection of plugin static files',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@state_logger('TASK08')
|
@state_logger
|
||||||
def static(c, frontend=False, clear=True, skip_plugins=False):
|
def static(c, frontend=False, clear=True, skip_plugins=False):
|
||||||
"""Copies required static files to the STATIC_ROOT directory, as per Django requirements."""
|
"""Copies required static files to the STATIC_ROOT directory, as per Django requirements."""
|
||||||
if frontend and node_available():
|
if frontend and node_available():
|
||||||
@@ -725,7 +733,7 @@ def translate(c, ignore_static=False, no_frontend=False):
|
|||||||
'skip_media': 'Skip media backup step (only backup database files)',
|
'skip_media': 'Skip media backup step (only backup database files)',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@state_logger('TASK04')
|
@state_logger
|
||||||
def backup(
|
def backup(
|
||||||
c,
|
c,
|
||||||
clean: bool = False,
|
clean: bool = False,
|
||||||
@@ -838,15 +846,15 @@ def restore(
|
|||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
@state_logger()
|
@state_logger
|
||||||
def listbackups(c):
|
def listbackups(c):
|
||||||
"""List available backup files."""
|
"""List available backup files."""
|
||||||
info('Finding available backup files...')
|
info('Finding available backup files...')
|
||||||
manage(c, 'listbackups')
|
manage(c, 'listbackups')
|
||||||
|
|
||||||
|
|
||||||
@task(post=[rebuild_models, rebuild_thumbnails])
|
@task(pre=[wait], post=[rebuild_models, rebuild_thumbnails])
|
||||||
@state_logger('TASK05')
|
@state_logger
|
||||||
def migrate(c):
|
def migrate(c):
|
||||||
"""Performs database migrations.
|
"""Performs database migrations.
|
||||||
|
|
||||||
@@ -879,7 +887,7 @@ def showmigrations(c, app=''):
|
|||||||
'uv': 'Use UV (experimental package manager)',
|
'uv': 'Use UV (experimental package manager)',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@state_logger('TASK03')
|
@state_logger
|
||||||
def update(
|
def update(
|
||||||
c,
|
c,
|
||||||
skip_backup: bool = False,
|
skip_backup: bool = False,
|
||||||
@@ -953,7 +961,8 @@ def update(
|
|||||||
'include_sso': 'Include SSO token data in the output file (default = False)',
|
'include_sso': 'Include SSO token data in the output file (default = False)',
|
||||||
'include_session': 'Include user session data in the output file (default = False)',
|
'include_session': 'Include user session data in the output file (default = False)',
|
||||||
'retain_temp': 'Retain temporary files (containing permissions) at end of process (default = False)',
|
'retain_temp': 'Retain temporary files (containing permissions) at end of process (default = False)',
|
||||||
}
|
},
|
||||||
|
pre=[wait],
|
||||||
)
|
)
|
||||||
def export_records(
|
def export_records(
|
||||||
c,
|
c,
|
||||||
@@ -1050,6 +1059,7 @@ def export_records(
|
|||||||
'clear': 'Clear existing data before import',
|
'clear': 'Clear existing data before import',
|
||||||
'retain_temp': 'Retain temporary files at end of process (default = False)',
|
'retain_temp': 'Retain temporary files at end of process (default = False)',
|
||||||
},
|
},
|
||||||
|
pre=[wait],
|
||||||
post=[rebuild_models, rebuild_thumbnails],
|
post=[rebuild_models, rebuild_thumbnails],
|
||||||
)
|
)
|
||||||
def import_records(
|
def import_records(
|
||||||
@@ -1199,12 +1209,6 @@ def import_fixtures(c):
|
|||||||
|
|
||||||
|
|
||||||
# Execution tasks
|
# Execution tasks
|
||||||
@task
|
|
||||||
@state_logger('TASK10')
|
|
||||||
def wait(c):
|
|
||||||
"""Wait until the database connection is ready."""
|
|
||||||
info('Waiting for database connection...')
|
|
||||||
return manage(c, 'wait_for_db')
|
|
||||||
|
|
||||||
|
|
||||||
@task(
|
@task(
|
||||||
@@ -1506,7 +1510,7 @@ def setup_test(
|
|||||||
'no_default': 'Do not use default settings for schema (default = off/False)',
|
'no_default': 'Do not use default settings for schema (default = off/False)',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@state_logger('TASK11')
|
@state_logger
|
||||||
def schema(
|
def schema(
|
||||||
c, filename='schema.yml', overwrite=False, ignore_warnings=False, no_default=False
|
c, filename='schema.yml', overwrite=False, ignore_warnings=False, no_default=False
|
||||||
):
|
):
|
||||||
@@ -1668,7 +1672,7 @@ def frontend_check(c):
|
|||||||
|
|
||||||
|
|
||||||
@task(help={'extract': 'Extract translation strings. Default: False'})
|
@task(help={'extract': 'Extract translation strings. Default: False'})
|
||||||
@state_logger('TASK06')
|
@state_logger
|
||||||
def frontend_compile(c, extract: bool = False):
|
def frontend_compile(c, extract: bool = False):
|
||||||
"""Generate react frontend.
|
"""Generate react frontend.
|
||||||
|
|
||||||
@@ -1781,7 +1785,7 @@ def frontend_test(c, host: str = '0.0.0.0'):
|
|||||||
'clean': 'Delete old files from InvenTree/web/static/web first, default: True',
|
'clean': 'Delete old files from InvenTree/web/static/web first, default: True',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@state_logger('TASK07')
|
@state_logger
|
||||||
def frontend_download(
|
def frontend_download(
|
||||||
c,
|
c,
|
||||||
ref=None,
|
ref=None,
|
||||||
|
|||||||
Reference in New Issue
Block a user