mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
Fixes to make compatible with Python 3.12 (#7112)
* 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`. * Fix usage of from importlib.metadata.entry_points to work with newer importlib & Python 3.12 * Update registry.py Fix order of imports * Use importlib.util.module_from_spec() instead of deprecated load_module() * auto-fixed import style (isort) * enable py 12 * run coverage for lower and upper bound * fix style error * make import conditional * fix? * fix env * style fix * only use new loader on 3.12 * fix order * fix module loading * reimplement assertDictContainsSubset * remove old testing alias --------- Co-authored-by: Thea Flowers <thea@winterbloom.com> Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
parent
477da1fa65
commit
a205fd5967
6
.github/workflows/qc_checks.yaml
vendored
6
.github/workflows/qc_checks.yaml
vendored
@ -261,18 +261,22 @@ jobs:
|
|||||||
coverage run -m unittest discover -s test/
|
coverage run -m unittest discover -s test/
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
name: Tests - DB [SQLite] + Coverage
|
name: Tests - DB [SQLite] + Coverage ${{ matrix.python_version }}
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
needs: ["pre-commit", "paths-filter"]
|
needs: ["pre-commit", "paths-filter"]
|
||||||
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
|
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
|
continue-on-error: true # continue if a step fails so that coverage gets pushed
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python_version: [3.9, 3.12]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
INVENTREE_DB_NAME: ./inventree.sqlite
|
INVENTREE_DB_NAME: ./inventree.sqlite
|
||||||
INVENTREE_DB_ENGINE: sqlite3
|
INVENTREE_DB_ENGINE: sqlite3
|
||||||
INVENTREE_PLUGINS_ENABLED: true
|
INVENTREE_PLUGINS_ENABLED: true
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
python_version: ${{ matrix.python_version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
|
@ -435,3 +435,7 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
|
|||||||
data.append(entry)
|
data.append(entry)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def assertDictContainsSubset(self, a, b):
|
||||||
|
"""Assert that dictionary 'a' is a subset of dictionary 'b'."""
|
||||||
|
self.assertEqual(b, b | a)
|
||||||
|
@ -5,14 +5,16 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import traceback
|
import traceback
|
||||||
from importlib.metadata import entry_points
|
from importlib.metadata import entry_points
|
||||||
|
from importlib.util import module_from_spec
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
from django.db.utils import IntegrityError
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
@ -110,7 +112,10 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st
|
|||||||
|
|
||||||
def get_entrypoints():
|
def get_entrypoints():
|
||||||
"""Returns list for entrypoints for InvenTree plugins."""
|
"""Returns list for entrypoints for InvenTree plugins."""
|
||||||
|
# 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().get('inventree_plugins', [])
|
||||||
|
return entry_points(group='inventree_plugins')
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@ -121,7 +126,8 @@ def get_git_log(path):
|
|||||||
"""Get dict with info of the last commit to file named in path."""
|
"""Get dict with info of the last commit to file named in path."""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from dulwich.repo import NotGitRepository, Repo
|
from dulwich.errors import NotGitRepository
|
||||||
|
from dulwich.repo import Repo
|
||||||
|
|
||||||
from InvenTree.ready import isInTestMode
|
from InvenTree.ready import isInTestMode
|
||||||
|
|
||||||
@ -175,9 +181,15 @@ def get_modules(pkg, path=None):
|
|||||||
elif type(path) is not list:
|
elif type(path) is not list:
|
||||||
path = [path]
|
path = [path]
|
||||||
|
|
||||||
for loader, name, _ in pkgutil.walk_packages(path):
|
for finder, name, _ in pkgutil.walk_packages(path):
|
||||||
try:
|
try:
|
||||||
module = loader.find_module(name).load_module(name)
|
if sys.version_info < (3, 12):
|
||||||
|
module = finder.find_module(name).load_module(name)
|
||||||
|
else:
|
||||||
|
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)
|
pkg_names = getattr(module, '__all__', None)
|
||||||
for k, v in vars(module).items():
|
for k, v in vars(module).items():
|
||||||
if not k.startswith('_') and (pkg_names is None or k in pkg_names):
|
if not k.startswith('_') and (pkg_names is None or k in pkg_names):
|
||||||
|
@ -4,12 +4,15 @@
|
|||||||
- Manages setup and teardown of plugin class instances
|
- Manages setup and teardown of plugin class instances
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import imp
|
|
||||||
import importlib
|
import importlib
|
||||||
|
import importlib.machinery
|
||||||
|
import importlib.util
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -411,9 +414,15 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
# Gather Modules
|
# Gather Modules
|
||||||
if parent_path:
|
if parent_path:
|
||||||
raw_module = imp.load_source(
|
# On python 3.12 use new loader method
|
||||||
|
if sys.version_info < (3, 12):
|
||||||
|
raw_module = _load_source(
|
||||||
plugin, str(parent_obj.joinpath('__init__.py'))
|
plugin, str(parent_obj.joinpath('__init__.py'))
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
raw_module = SourceFileLoader(
|
||||||
|
plugin, str(parent_obj.joinpath('__init__.py'))
|
||||||
|
).load_module()
|
||||||
else:
|
else:
|
||||||
raw_module = importlib.import_module(plugin)
|
raw_module = importlib.import_module(plugin)
|
||||||
modules = get_plugins(raw_module, InvenTreePlugin, path=parent_path)
|
modules = get_plugins(raw_module, InvenTreePlugin, path=parent_path)
|
||||||
@ -830,3 +839,18 @@ registry: PluginsRegistry = PluginsRegistry()
|
|||||||
def call_function(plugin_name, function_name, *args, **kwargs):
|
def call_function(plugin_name, function_name, *args, **kwargs):
|
||||||
"""Global helper function to call a specific member function of a plugin."""
|
"""Global helper function to call a specific member function of a plugin."""
|
||||||
return registry.call_plugin_function(plugin_name, function_name, *args, **kwargs)
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user