Repository: inventree/InvenTree

Issue:
[FR] Switch to pyproject.toml
**Problem**
The new style for python projects is to use a *.toml file instead of setup.py + setup.cfg

**Suggested solution**
Switch to the newer format. Also for all built-in plugins and add support for them in plugins with a mixin that get's all its metadata from there.

**Examples of other systems**
https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/

**Do you want to develop this?**
Yes


Discussion:

Resolving patch:
diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py
--- a/InvenTree/plugin/plugin.py
+++ b/InvenTree/plugin/plugin.py
@@ -6,6 +6,7 @@
 import pathlib
 import warnings
 from datetime import datetime
+from importlib.metadata import metadata
 
 from django.conf import settings
 from django.db.utils import OperationalError, ProgrammingError
@@ -195,11 +196,26 @@ def __init__(self):
 
         self.define_package()
 
+    def _get_value(self, meta_name: str, package_name: str) -> str:
+        """Extract values from class meta or package info.
+
+        Args:
+            meta_name (str): Name of the class meta to use.
+            package_name (str): Name of the package data to use.
+
+        Returns:
+            str: Extracted value, None if nothing found.
+        """
+        val = getattr(self, meta_name, None)
+        if not val:
+            val = self.package.get(package_name, None)
+        return val
+
     # region properties
     @property
     def description(self):
         """Description of plugin."""
-        description = getattr(self, 'DESCRIPTION', None)
+        description = self._get_value('DESCRIPTION', 'description')
         if not description:
             description = self.plugin_name()
         return description
@@ -207,9 +223,7 @@ def description(self):
     @property
     def author(self):
         """Author of plugin - either from plugin settings or git."""
-        author = getattr(self, 'AUTHOR', None)
-        if not author:
-            author = self.package.get('author')
+        author = self._get_value('AUTHOR', 'author')
         if not author:
             author = _('No author found')  # pragma: no cover
         return author
@@ -229,19 +243,19 @@ def pub_date(self):
     @property
     def version(self):
         """Version of plugin."""
-        version = getattr(self, 'VERSION', None)
+        version = self._get_value('VERSION', 'version')
         return version
 
     @property
     def website(self):
         """Website of plugin - if set else None."""
-        website = getattr(self, 'WEBSITE', None)
+        website = self._get_value('WEBSITE', 'website')
         return website
 
     @property
     def license(self):
         """License of plugin."""
-        lic = getattr(self, 'LICENSE', None)
+        lic = self._get_value('LICENSE', 'license')
         return lic
     # endregion
 
@@ -273,9 +287,18 @@ def _get_package_commit(self):
         """Get last git commit for the plugin."""
         return get_git_log(self.def_path)
 
-    def _get_package_metadata(self):
+    @classmethod
+    def _get_package_metadata(cls):
         """Get package metadata for plugin."""
-        return {}  # pragma: no cover  # TODO add usage for package metadata
+        meta = metadata(cls.__name__)
+
+        return {
+            'author': meta['Author-email'],
+            'description': meta['Summary'],
+            'version': meta['Version'],
+            'website': meta['Project-URL'],
+            'license': meta['License']
+        }
 
     def define_package(self):
         """Add package info of the plugin into plugins context."""
diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py
--- a/InvenTree/plugin/registry.py
+++ b/InvenTree/plugin/registry.py
@@ -208,6 +208,7 @@ def collect_plugins(self):
                 try:
                     plugin = entry.load()
                     plugin.is_package = True
+                    plugin._get_package_metadata()
                     self.plugin_modules.append(plugin)
                 except Exception as error:
                     handle_error(error, do_raise=False, log_name='discovery')


Test patch: