From 001cff13d34e1c11a8bd4f28f16cf0b2ab891cf3 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Mon, 23 Oct 2023 20:32:26 -0400 Subject: [PATCH 01/17] Remove use of deprecated imp.load_source The entire `imp` module has been removed from Python 3.12. This patch applies the recommended replacement using `importlib`. --- InvenTree/plugin/registry.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index e5c4e143926..38915aa79e2 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -4,8 +4,9 @@ - Manages setup and teardown of plugin class instances """ -import imp import importlib +import importlib.util +import importlib.machinery import logging import os import time @@ -354,7 +355,7 @@ def collect_plugins(self): # Gather Modules if parent_path: - raw_module = imp.load_source(plugin, str(parent_obj.joinpath('__init__.py'))) + raw_module = _load_source(plugin, str(parent_obj.joinpath('__init__.py'))) else: raw_module = importlib.import_module(plugin) modules = get_plugins(raw_module, InvenTreePlugin, path=parent_path) @@ -724,3 +725,18 @@ def check_reload(self): def call_function(plugin_name, function_name, *args, **kwargs): """Global helper function to call a specific member function of a plugin.""" return registry.call_plugin_function(plugin_name, function_name, *args, **kwargs) + + +def _load_source(modname, filename): + """Helper function to replace deprecated & removed imp.load_source. + + See https://docs.python.org/3/whatsnew/3.12.html#imp + """ + loader = importlib.machinery.SourceFileLoader(modname, filename) + spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + module = importlib.util.module_from_spec(spec) + # The module is always executed and not cached in sys.modules. + # Uncomment the following line to cache the module. + # sys.modules[module.__name__] = module + loader.exec_module(module) + return module From b0114d9823dc6bb29b15ec5af7a880e404e95cbc Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Mon, 23 Oct 2023 20:43:57 -0400 Subject: [PATCH 02/17] Fix usage of from importlib.metadata.entry_points to work with newer importlib & Python 3.12 --- InvenTree/plugin/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index d896ce10c27..e3e10406d73 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -102,7 +102,7 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st def get_entrypoints(): """Returns list for entrypoints for InvenTree plugins.""" - return entry_points().get('inventree_plugins', []) + return entry_points(group='inventree_plugins') # endregion From 8cfd7fa42fd66a6c9ed22a8eb95583fe4224e556 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 24 Oct 2023 13:37:59 +1100 Subject: [PATCH 03/17] Update registry.py Fix order of imports --- InvenTree/plugin/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 38915aa79e2..92d21405ce4 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -5,8 +5,8 @@ """ import importlib -import importlib.util import importlib.machinery +import importlib.util import logging import os import time From 4ba2fc5a4626e9ac734db8515e3ce4d6afd68580 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Tue, 24 Oct 2023 12:52:28 -0400 Subject: [PATCH 04/17] Use importlib.util.module_from_spec() instead of deprecated load_module() --- InvenTree/plugin/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index e3e10406d73..5aedc94eda6 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -7,6 +7,7 @@ import pkgutil import sysconfig import traceback +from importlib.util import module_from_spec from importlib.metadata import entry_points from django import template @@ -156,9 +157,9 @@ def get_modules(pkg, path=None): elif type(path) is not list: path = [path] - for loader, name, _ in pkgutil.walk_packages(path): + for finder, name, _ in pkgutil.walk_packages(path): try: - module = loader.find_module(name).load_module(name) + module = module_from_spec(finder.find_spec(name)) pkg_names = getattr(module, '__all__', None) for k, v in vars(module).items(): if not k.startswith('_') and (pkg_names is None or k in pkg_names): From 9eb35f8d18adde95994e5d0c9f7596afb184ce29 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 24 Oct 2023 19:17:50 +0200 Subject: [PATCH 05/17] auto-fixed import style (isort) --- InvenTree/plugin/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index 5aedc94eda6..c3b9e492e59 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -7,8 +7,8 @@ import pkgutil import sysconfig import traceback -from importlib.util import module_from_spec from importlib.metadata import entry_points +from importlib.util import module_from_spec from django import template from django.conf import settings From 8d58e27b1737ad1052caac8d087796ec0ed9d435 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 2 Apr 2024 18:55:37 +0200 Subject: [PATCH 06/17] enable py 12 --- InvenTree/plugin/helpers.py | 5 +++-- InvenTree/plugin/registry.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index f9c1321d8d4..8cf09c40551 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -110,7 +110,7 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st def get_entrypoints(): """Returns list for entrypoints for InvenTree plugins.""" - return entry_points().get('inventree_plugins', []) + return entry_points(group='inventree_plugins') # endregion @@ -121,7 +121,8 @@ def get_git_log(path): """Get dict with info of the last commit to file named in path.""" import datetime - from dulwich.repo import NotGitRepository, Repo + from dulwich.errors import NotGitRepository + from dulwich.repo import Repo from InvenTree.ready import isInTestMode diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index ee68b0d04f0..0695f318c8b 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -4,12 +4,12 @@ - Manages setup and teardown of plugin class instances """ -import imp import importlib import logging import os import time from collections import OrderedDict +from importlib.machinery import SourceFileLoader from pathlib import Path from threading import Lock from typing import Any @@ -412,9 +412,9 @@ def collect_plugins(self): # Gather Modules if parent_path: - raw_module = imp.load_source( + raw_module = SourceFileLoader( plugin, str(parent_obj.joinpath('__init__.py')) - ) + ).load_module() else: raw_module = importlib.import_module(plugin) modules = get_plugins(raw_module, InvenTreePlugin, path=parent_path) From f66d3830747160595494ff44c00c36a0a9237b66 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 23 Apr 2024 19:43:31 +0200 Subject: [PATCH 07/17] run coverage for lower and upper bound --- .github/workflows/qc_checks.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 1b778950bbf..f09b260bb2b 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -261,18 +261,21 @@ jobs: coverage run -m unittest discover -s test/ coverage: - name: Tests - DB [SQLite] + Coverage + name: Tests - DB [SQLite] + Coverage ${{ matrix.python-version }} runs-on: ubuntu-20.04 needs: ["pre-commit", "paths-filter"] if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' continue-on-error: true # continue if a step fails so that coverage gets pushed + matrix: + python-version: [3.9, 3.12] env: INVENTREE_DB_NAME: ./inventree.sqlite INVENTREE_DB_ENGINE: sqlite3 INVENTREE_PLUGINS_ENABLED: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + python-version: ${{ matrix.python-version }} steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 From bb2fe57e67a297958001f332092a0b37d58af8b9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Apr 2024 19:54:42 +0200 Subject: [PATCH 08/17] fix style error --- .github/workflows/qc_checks.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index f09b260bb2b..c523a16c0e1 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -267,8 +267,9 @@ jobs: needs: ["pre-commit", "paths-filter"] if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true' continue-on-error: true # continue if a step fails so that coverage gets pushed - matrix: - python-version: [3.9, 3.12] + strategy: + matrix: + python-version: [3.9, 3.12] env: INVENTREE_DB_NAME: ./inventree.sqlite From a1bdf618c1bedf94258319854b92be15907c79b4 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Apr 2024 20:49:19 +0200 Subject: [PATCH 09/17] make import conditional --- src/backend/InvenTree/plugin/helpers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index 44a896f90d2..5447bfade35 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -5,15 +5,20 @@ import os import pathlib import pkgutil +import sys import sysconfig import traceback -from importlib.metadata import entry_points from django import template from django.conf import settings from django.core.exceptions import AppRegistryNotReady from django.db.utils import IntegrityError, OperationalError, ProgrammingError +if sys.version_info < (3, 12): + from importlib_metadata import entry_points +else: + from importlib.metadata import entry_points + logger = logging.getLogger('inventree') @@ -110,6 +115,9 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st def get_entrypoints(): """Returns list for entrypoints for InvenTree plugins.""" + # on python before 3.8, we need to use importlib_metadata + if sys.version_info < (3, 12): + return entry_points().get('inventree_plugins', []) return entry_points(group='inventree_plugins') From 7a130b09a6dae9ef298b8fad466a33c22b824f20 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Apr 2024 20:54:09 +0200 Subject: [PATCH 10/17] fix? --- src/backend/InvenTree/plugin/helpers.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index 5447bfade35..e6e9a1bdf5f 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -8,17 +8,13 @@ import sys import sysconfig import traceback +from importlib.metadata import entry_points from django import template from django.conf import settings from django.core.exceptions import AppRegistryNotReady from django.db.utils import IntegrityError, OperationalError, ProgrammingError -if sys.version_info < (3, 12): - from importlib_metadata import entry_points -else: - from importlib.metadata import entry_points - logger = logging.getLogger('inventree') From 9f0cf39076f8d83d9278dcd6581b065bf6e3c7a1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Apr 2024 21:39:07 +0200 Subject: [PATCH 11/17] fix env --- .github/workflows/qc_checks.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index c523a16c0e1..bab62d74275 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -261,7 +261,7 @@ jobs: coverage run -m unittest discover -s test/ coverage: - name: Tests - DB [SQLite] + Coverage ${{ matrix.python-version }} + name: Tests - DB [SQLite] + Coverage ${{ matrix.python_version }} runs-on: ubuntu-20.04 needs: ["pre-commit", "paths-filter"] @@ -269,14 +269,14 @@ jobs: continue-on-error: true # continue if a step fails so that coverage gets pushed strategy: matrix: - python-version: [3.9, 3.12] + python_version: [3.9, 3.12] env: INVENTREE_DB_NAME: ./inventree.sqlite INVENTREE_DB_ENGINE: sqlite3 INVENTREE_PLUGINS_ENABLED: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - python-version: ${{ matrix.python-version }} + python_version: ${{ matrix.python_version }} steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 From bfd6e5b3d6ffa4985abfabfe9a834894fff0fcf6 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 24 Apr 2024 23:28:04 +0200 Subject: [PATCH 12/17] style fix --- src/backend/InvenTree/plugin/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index e6e9a1bdf5f..99615ed7df0 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -13,7 +13,7 @@ from django import template from django.conf import settings from django.core.exceptions import AppRegistryNotReady -from django.db.utils import IntegrityError, OperationalError, ProgrammingError +from django.db.utils import IntegrityError logger = logging.getLogger('inventree') @@ -111,7 +111,7 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st def get_entrypoints(): """Returns list for entrypoints for InvenTree plugins.""" - # on python before 3.8, we need to use importlib_metadata + # on python before 3.12, we need to use importlib_metadata if sys.version_info < (3, 12): return entry_points().get('inventree_plugins', []) return entry_points(group='inventree_plugins') From 2fa0510d4b23b52018d4c54aaf3107bdf0b4254c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 25 Apr 2024 00:01:21 +0200 Subject: [PATCH 13/17] only use new loader on 3.12 --- src/backend/InvenTree/plugin/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index 5e988d3df36..98e03aa8226 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -183,7 +183,10 @@ def get_modules(pkg, path=None): for finder, name, _ in pkgutil.walk_packages(path): try: - module = module_from_spec(finder.find_spec(name)) + if sys.version_info < (3, 12): + module = module_from_spec(finder.find_spec(name)) + else: + module = finder.find_module(name).load_module(name) pkg_names = getattr(module, '__all__', None) for k, v in vars(module).items(): if not k.startswith('_') and (pkg_names is None or k in pkg_names): From c425fe27aafaeb33c1241985a8469849607d5c5c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 25 Apr 2024 00:05:08 +0200 Subject: [PATCH 14/17] fix order --- src/backend/InvenTree/plugin/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index 98e03aa8226..abeba6a139a 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -184,9 +184,9 @@ def get_modules(pkg, path=None): for finder, name, _ in pkgutil.walk_packages(path): try: if sys.version_info < (3, 12): - module = module_from_spec(finder.find_spec(name)) - else: module = finder.find_module(name).load_module(name) + else: + module = module_from_spec(finder.find_spec(name)) pkg_names = getattr(module, '__all__', None) for k, v in vars(module).items(): if not k.startswith('_') and (pkg_names is None or k in pkg_names): From 065c8b94f183807cda8a8c51692176700202f8aa Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 28 Apr 2024 13:28:38 +0200 Subject: [PATCH 15/17] fix module loading --- src/backend/InvenTree/plugin/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/plugin/helpers.py b/src/backend/InvenTree/plugin/helpers.py index abeba6a139a..6e8425ad5ad 100644 --- a/src/backend/InvenTree/plugin/helpers.py +++ b/src/backend/InvenTree/plugin/helpers.py @@ -186,7 +186,10 @@ def get_modules(pkg, path=None): if sys.version_info < (3, 12): module = finder.find_module(name).load_module(name) else: - module = module_from_spec(finder.find_spec(name)) + spec = finder.find_spec(name) + module = module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) pkg_names = getattr(module, '__all__', None) for k, v in vars(module).items(): if not k.startswith('_') and (pkg_names is None or k in pkg_names): From 38485dc59d1d1f62a1833eeb7a1317c00664c6f2 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 28 Apr 2024 15:16:24 +0200 Subject: [PATCH 16/17] reimplement assertDictContainsSubset --- src/backend/InvenTree/InvenTree/unit_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/unit_test.py b/src/backend/InvenTree/InvenTree/unit_test.py index 4ad2d68b7b4..794649b3652 100644 --- a/src/backend/InvenTree/InvenTree/unit_test.py +++ b/src/backend/InvenTree/InvenTree/unit_test.py @@ -435,3 +435,7 @@ def process_csv( data.append(entry) return data + + def assertDictContainsSubset(self, a, b): + """Assert that dictionary 'a' is a subset of dictionary 'b'.""" + self.assertEqual(b, b | a) From 2b3b584d5b3352f889895c7da591b0b66257a84d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 28 Apr 2024 18:32:35 +0200 Subject: [PATCH 17/17] remove old testing alias --- src/backend/InvenTree/order/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py index d9f63d67c07..07ada62699a 100644 --- a/src/backend/InvenTree/order/test_api.py +++ b/src/backend/InvenTree/order/test_api.py @@ -1772,7 +1772,7 @@ def check_template(line_item): # At least one item should be allocated, and all should be variants self.assertGreater(self.order.stock_allocations.count(), 0) for allocation in self.order.stock_allocations.all(): - self.assertNotEquals(allocation.item.part.pk, allocation.line.part.pk) + self.assertNotEqual(allocation.item.part.pk, allocation.line.part.pk) def test_shipment_complete(self): """Test that we can complete a shipment via the API."""