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
This commit is contained in:
Matthias Mair 2022-09-05 05:03:26 +02:00 committed by GitHub
parent 2601cf0279
commit abf133384b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 25 additions and 59 deletions

View File

@ -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

View File

@ -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'],

View File

@ -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 <em>sample/wrongsample.html</em>' 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')