mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-21 22:23:03 +00:00
Improve request caching for plugin config objects (#12430)
* Improve request caching for plugin config objects * Cache configs dict further
This commit is contained in:
@@ -212,39 +212,89 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
return plg
|
return plg
|
||||||
|
|
||||||
def get_plugin_config(self, slug: str, name: str | None = None):
|
def get_plugin_configs(self) -> Optional[dict[str, Any]]:
|
||||||
|
"""Return a per-request cached mapping of {slug: PluginConfig} for all plugins.
|
||||||
|
|
||||||
|
This is a general-purpose cache, shared by any registry method which needs to
|
||||||
|
look up (potentially many) PluginConfig instances - e.g. get_plugin_config(),
|
||||||
|
with_mixin(), calculate_plugin_hash() - so that a single request only ever
|
||||||
|
pre-fetches the full PluginConfig table once, rather than issuing a
|
||||||
|
per-plugin database query.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, PluginConfig]: Mapping of plugin slug to PluginConfig instance.
|
||||||
|
None if the database is not ready to be queried.
|
||||||
|
"""
|
||||||
|
# As we have already checked the registry hash, this is a valid cache key
|
||||||
|
cache_key = f'plugin_configs:{self.registry_hash}'
|
||||||
|
|
||||||
|
configs = InvenTree.cache.get_session_cache(cache_key)
|
||||||
|
|
||||||
|
if configs is None:
|
||||||
|
try:
|
||||||
|
# Pre-fetch the PluginConfig objects to avoid multiple database queries
|
||||||
|
from plugin.models import PluginConfig
|
||||||
|
|
||||||
|
configs = {config.key: config for config in PluginConfig.objects.all()}
|
||||||
|
InvenTree.cache.set_session_cache(cache_key, configs)
|
||||||
|
except (ProgrammingError, OperationalError):
|
||||||
|
# The database is not ready yet
|
||||||
|
logger.warning('plugin.registry.get_plugin_configs: Database not ready')
|
||||||
|
return None
|
||||||
|
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def get_plugin_config(
|
||||||
|
self, slug: str, name: str | None = None, configs: dict | None = None
|
||||||
|
):
|
||||||
"""Return the matching PluginConfig instance for a given plugin.
|
"""Return the matching PluginConfig instance for a given plugin.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
slug: The plugin slug
|
slug: The plugin slug
|
||||||
name: The plugin name (optional)
|
name: The plugin name (optional)
|
||||||
|
configs: A pre-fetched mapping of {slug: PluginConfig} to avoid multiple database queries (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PluginConfig | None: The matching PluginConfig instance, or None if not found.
|
||||||
"""
|
"""
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
from plugin.models import PluginConfig
|
from plugin.models import PluginConfig
|
||||||
|
|
||||||
# Under certain circumstances, we want to avoid creating new PluginConfig instances in the database
|
if configs is None:
|
||||||
can_create = (
|
configs = self.get_plugin_configs()
|
||||||
InvenTree.ready.canAppAccessDatabase(
|
|
||||||
allow_plugins=False, allow_shell=True, allow_test=True
|
|
||||||
)
|
|
||||||
and not InvenTree.ready.isReadOnlyCommand()
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
cfg = configs.get(slug) if configs is not None else None
|
||||||
cfg = PluginConfig.objects.filter(key=slug).first()
|
|
||||||
|
|
||||||
if not cfg and can_create:
|
if not cfg:
|
||||||
logger.debug(
|
# Under certain circumstances, we want to avoid creating new PluginConfig instances in the database
|
||||||
"get_plugin_config: Creating new PluginConfig for '%s'", slug
|
can_create = (
|
||||||
|
InvenTree.ready.canAppAccessDatabase(
|
||||||
|
allow_plugins=False, allow_shell=True, allow_test=True
|
||||||
)
|
)
|
||||||
cfg = PluginConfig.objects.create(key=slug)
|
and not InvenTree.ready.isReadOnlyCommand()
|
||||||
|
)
|
||||||
|
|
||||||
except PluginConfig.DoesNotExist: # pragma: no cover
|
try:
|
||||||
return None
|
cfg = PluginConfig.objects.filter(key=slug).first()
|
||||||
except (IntegrityError, OperationalError, ProgrammingError): # pragma: no cover
|
|
||||||
return None
|
|
||||||
|
|
||||||
if name and cfg.name != name:
|
if not cfg and can_create:
|
||||||
|
logger.debug(
|
||||||
|
"get_plugin_config: Creating new PluginConfig for '%s'", slug
|
||||||
|
)
|
||||||
|
cfg = PluginConfig.objects.create(key=slug)
|
||||||
|
|
||||||
|
except (
|
||||||
|
IntegrityError,
|
||||||
|
OperationalError,
|
||||||
|
ProgrammingError,
|
||||||
|
): # pragma: no cover
|
||||||
|
return None
|
||||||
|
|
||||||
|
if cfg is not None and configs is not None:
|
||||||
|
# Backfill the cache, so repeated lookups this request are free
|
||||||
|
configs[slug] = cfg
|
||||||
|
|
||||||
|
if cfg and name and cfg.name != name:
|
||||||
# Update the name if it has changed
|
# Update the name if it has changed
|
||||||
try:
|
try:
|
||||||
cfg.name = name
|
cfg.name = name
|
||||||
@@ -313,25 +363,15 @@ class PluginsRegistry:
|
|||||||
active (bool, optional): Filter by 'active' status of plugin. Defaults to True.
|
active (bool, optional): Filter by 'active' status of plugin. Defaults to True.
|
||||||
builtin (bool, optional): Filter by 'builtin' status of plugin. Defaults to None.
|
builtin (bool, optional): Filter by 'builtin' status of plugin. Defaults to None.
|
||||||
"""
|
"""
|
||||||
# We can store the PluginConfig objects against the session cache,
|
# Pre-fetch (and cache) the PluginConfig objects, to avoid hitting the
|
||||||
# which allows us to avoid hitting the database multiple times (per session)
|
# database multiple times (per plugin) below
|
||||||
# As we have already checked the registry hash, this is a valid cache key
|
|
||||||
cache_key = f'plugin_configs:{self.registry_hash}'
|
|
||||||
|
|
||||||
configs = InvenTree.cache.get_session_cache(cache_key)
|
configs = self.get_plugin_configs()
|
||||||
|
|
||||||
if not configs:
|
if configs is None:
|
||||||
try:
|
# The database is not ready yet
|
||||||
# Pre-fetch the PluginConfig objects to avoid multiple database queries
|
logger.warning('plugin.registry.with_mixin: Database not ready')
|
||||||
from plugin.models import PluginConfig
|
return []
|
||||||
|
|
||||||
plugin_configs = PluginConfig.objects.all()
|
|
||||||
configs = {config.key: config for config in plugin_configs}
|
|
||||||
InvenTree.cache.set_session_cache(cache_key, configs)
|
|
||||||
except (ProgrammingError, OperationalError):
|
|
||||||
# The database is not ready yet
|
|
||||||
logger.warning('plugin.registry.with_mixin: Database not ready')
|
|
||||||
return []
|
|
||||||
|
|
||||||
mixin = str(mixin).lower().strip()
|
mixin = str(mixin).lower().strip()
|
||||||
|
|
||||||
@@ -344,7 +384,7 @@ class PluginsRegistry:
|
|||||||
except MixinNotImplementedError:
|
except MixinNotImplementedError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
config = configs.get(plugin.slug) or plugin.plugin_config()
|
config = self.get_plugin_config(plugin.slug, configs=configs)
|
||||||
|
|
||||||
# No config - cannot use this plugin
|
# No config - cannot use this plugin
|
||||||
if not config:
|
if not config:
|
||||||
@@ -1025,6 +1065,10 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
data = md5()
|
data = md5()
|
||||||
|
|
||||||
|
# Pre-fetch (and cache) the PluginConfig objects, to avoid hitting the
|
||||||
|
# database once per plugin below
|
||||||
|
configs = self.get_plugin_configs() or {}
|
||||||
|
|
||||||
# Hash for all loaded plugins
|
# Hash for all loaded plugins
|
||||||
# Note: Sort by slug, so the hash is independent of discovery order.
|
# Note: Sort by slug, so the hash is independent of discovery order.
|
||||||
# Different processes can discover the same plugins in a different
|
# Different processes can discover the same plugins in a different
|
||||||
@@ -1034,7 +1078,10 @@ class PluginsRegistry:
|
|||||||
data.update(str(slug).encode())
|
data.update(str(slug).encode())
|
||||||
data.update(str(plug.name).encode())
|
data.update(str(plug.name).encode())
|
||||||
data.update(str(plug.version).encode())
|
data.update(str(plug.version).encode())
|
||||||
data.update(str(plug.is_active()).encode())
|
|
||||||
|
config = configs.get(slug) or self.get_plugin_config(slug)
|
||||||
|
active = config.is_active() if config else False
|
||||||
|
data.update(str(active).encode())
|
||||||
|
|
||||||
for k in self.plugin_settings_keys():
|
for k in self.plugin_settings_keys():
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user