From abf133384b800b47a0f242d5ff7c7e7b6990f59f Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 5 Sep 2022 05:03:26 +0200 Subject: [PATCH] Optimisations for editable installs of plugins (#3634) * out-of-scope: add function to check if a package is editable * out-of-scope: move to included meta toolset for metadata discovery * out-of-scope: make lookup safe for editable installs --- InvenTree/plugin/helpers.py | 58 ++++++++------------------------ InvenTree/plugin/plugin.py | 12 +++++-- InvenTree/plugin/test_helpers.py | 14 +------- 3 files changed, 25 insertions(+), 59 deletions(-) diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index baeb1847ce..7a873f0a90 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -7,9 +7,7 @@ import pkgutil import subprocess import sysconfig import traceback -from importlib.metadata import (PackageNotFoundError, distributions, - entry_points) -from importlib.util import find_spec +from importlib.metadata import entry_points from django import template from django.conf import settings @@ -71,18 +69,21 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st package_name = pathlib.Path(package_path).relative_to(install_path).parts[0] except ValueError: # is file - loaded -> form a name for that - path_obj = pathlib.Path(package_path).relative_to(settings.BASE_DIR) - path_parts = [*path_obj.parts] - path_parts[-1] = path_parts[-1].replace(path_obj.suffix, '') # remove suffix + try: + path_obj = pathlib.Path(package_path).relative_to(settings.BASE_DIR) + path_parts = [*path_obj.parts] + path_parts[-1] = path_parts[-1].replace(path_obj.suffix, '') # remove suffix - # remove path prefixes - if path_parts[0] == 'plugin': - path_parts.remove('plugin') - path_parts.pop(0) - else: - path_parts.remove('plugins') # pragma: no cover + # remove path prefixes + if path_parts[0] == 'plugin': + path_parts.remove('plugin') + path_parts.pop(0) + else: + path_parts.remove('plugins') # pragma: no cover - package_name = '.'.join(path_parts) + package_name = '.'.join(path_parts) + except Exception: + package_name = package_path if do_log: log_kwargs = {} @@ -231,37 +232,6 @@ def get_plugins(pkg, baseclass, path=None): plugins.append(plugin) return plugins - - -def get_module_meta(mdl_name): - """Return distribution for module. - - Modified form source: https://stackoverflow.com/a/60975978/17860466 - """ - # Get spec for module - spec = find_spec(mdl_name) - - if not spec: # pragma: no cover - raise PackageNotFoundError(mdl_name) - - # Try to get specific package for the module - result = None - for dist in distributions(): - try: - relative = pathlib.Path(spec.origin).relative_to(dist.locate_file('')) - except ValueError: # pragma: no cover - pass - else: - if relative in dist.files: - result = dist - - # Check if a distribution was found - # A no should not be possible here as a call can only be made on a discovered module but better save then sorry - if not result: # pragma: no cover - raise PackageNotFoundError(mdl_name) - - # Return metadata - return result.metadata # endregion diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index f23e34107f..d39ab2187c 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -4,6 +4,7 @@ import inspect import logging import warnings from datetime import datetime +from distutils.sysconfig import get_python_lib from importlib.metadata import PackageNotFoundError, metadata from pathlib import Path @@ -13,7 +14,7 @@ from django.urls.base import reverse from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ -from plugin.helpers import GitStatus, get_git_log, get_module_meta +from plugin.helpers import GitStatus, get_git_log logger = logging.getLogger("inventree") @@ -325,6 +326,13 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase): """Get last git commit for the plugin.""" return get_git_log(str(self.file())) + @classmethod + def is_editable(cls): + """Returns if the current part is editable.""" + pkg_name = cls.__name__.split('.')[0] + dist_info = list(Path(get_python_lib()).glob(f'{pkg_name}-*.dist-info')) + return bool(len(dist_info) == 1) + @classmethod def _get_package_metadata(cls): """Get package metadata for plugin.""" @@ -334,7 +342,7 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase): meta = metadata(cls.__name__) # Simpel lookup did not work - get data from module except PackageNotFoundError: - meta = get_module_meta(cls.__module__) + meta = metadata(cls.__module__.split('.')[0]) return { 'author': meta['Author-email'], diff --git a/InvenTree/plugin/test_helpers.py b/InvenTree/plugin/test_helpers.py index 4b2ac8538c..53b2622592 100644 --- a/InvenTree/plugin/test_helpers.py +++ b/InvenTree/plugin/test_helpers.py @@ -2,7 +2,7 @@ from django.test import TestCase -from .helpers import get_module_meta, render_template +from .helpers import render_template class HelperTests(TestCase): @@ -21,15 +21,3 @@ class HelperTests(TestCase): response = render_template(ErrorSource(), 'sample/wrongsample.html', {'abc': 123}) self.assertTrue('lert alert-block alert-danger' in response) self.assertTrue('Template file sample/wrongsample.html' in response) - - def test_get_module_meta(self): - """Test for get_module_meta.""" - - # We need a stable, known good that will be in enviroment for sure - # and it can't be stdlib because does might differ depending on the abstraction layer - # and version - meta = get_module_meta('django') - - # Lets just hope they do not change the name or author - self.assertEqual(meta['Name'], 'Django') - self.assertEqual(meta['Author'], 'Django Software Foundation')