public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/python-django-configurations] rawhide: Fix Python 3.15 compatibility: move to PEP-451 style loader
@ 2026-06-06 10:53
0 siblings, 0 replies; only message in thread
From: @ 2026-06-06 10:53 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/python-django-configurations
Branch : rawhide
Commit : 3ae4328864864183022b6fc0b8bcb6c511e1a378
Author : Tomáš Hrnčiar <thrnciar@redhat.com>
Date : 2026-06-06T10:33:59+00:00
Stats : +281/-0 in 2 file(s)
URL : https://src.fedoraproject.org/rpms/python-django-configurations/c/3ae4328864864183022b6fc0b8bcb6c511e1a378?branch=rawhide
Log:
Fix Python 3.15 compatibility: move to PEP-451 style loader
Backport upstream PR#386 to replace the deprecated load_module() API
with exec_module(), which Python 3.15 requires.
https: //github.com/jazzband/django-configurations/pull/386
Co-authored-by: Cursor <cursoragent@cursor.com>
---
diff --git a/fix-pep451-exec-module.patch b/fix-pep451-exec-module.patch
new file mode 100644
index 0000000..db4b284
--- /dev/null
+++ b/fix-pep451-exec-module.patch
@@ -0,0 +1,278 @@
+diff --git a/configurations/importer.py b/configurations/importer.py
+index 499eab6..01469c6 100644
+--- a/configurations/importer.py
++++ b/configurations/importer.py
+@@ -1,4 +1,3 @@
+-import importlib.util
+ from importlib.machinery import PathFinder
+ import logging
+ import os
+@@ -47,12 +46,12 @@ def install(check_options=False):
+ return parser
+
+ base.BaseCommand.create_parser = create_parser
+- importer = ConfigurationImporter(check_options=check_options)
++ importer = ConfigurationFinder(check_options=check_options)
+ sys.meta_path.insert(0, importer)
+ installed = True
+
+
+-class ConfigurationImporter:
++class ConfigurationFinder(PathFinder):
+ modvar = SETTINGS_ENVIRONMENT_VARIABLE
+ namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
+ error_msg = ("Configuration cannot be imported, "
+@@ -71,7 +70,7 @@ class ConfigurationImporter:
+ self.announce()
+
+ def __repr__(self):
+- return "<ConfigurationImporter for '{0}.{1}'>".format(self.module,
++ return "<ConfigurationFinder for '{0}.{1}'>".format(self.module,
+ self.name)
+
+ @property
+@@ -129,56 +128,51 @@ class ConfigurationImporter:
+
+ def find_spec(self, fullname, path=None, target=None):
+ if fullname is not None and fullname == self.module:
+- spec = PathFinder.find_spec(fullname, path)
++ spec = super().find_spec(fullname, path, target)
+ if spec is not None:
+- return importlib.machinery.ModuleSpec(spec.name,
+- ConfigurationLoader(self.name, spec),
+- origin=spec.origin)
+- return None
+-
+-
+-class ConfigurationLoader:
+-
+- def __init__(self, name, spec):
+- self.name = name
+- self.spec = spec
+-
+- def load_module(self, fullname):
+- if fullname in sys.modules:
+- mod = sys.modules[fullname] # pragma: no cover
++ wrap_loader(spec.loader, self.name)
++ return spec
+ else:
+- mod = importlib.util.module_from_spec(self.spec)
+- sys.modules[fullname] = mod
+- self.spec.loader.exec_module(mod)
+-
+- cls_path = '{0}.{1}'.format(mod.__name__, self.name)
+-
+- try:
+- cls = getattr(mod, self.name)
+- except AttributeError as err: # pragma: no cover
+- reraise(err, "Couldn't find configuration '{0}' "
+- "in module '{1}'".format(self.name,
+- mod.__package__))
+- try:
+- cls.pre_setup()
+- cls.setup()
+- obj = cls()
+- attributes = uppercase_attributes(obj).items()
+- for name, value in attributes:
+- if callable(value) and not getattr(value, 'pristine', False):
+- value = value()
+- # in case a method returns a Value instance we have
+- # to do the same as the Configuration.setup method
+- if isinstance(value, Value):
+- setup_value(mod, name, value)
+- continue
+- setattr(mod, name, value)
+-
+- setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname,
+- self.name))
+- cls.post_setup()
+-
+- except Exception as err:
+- reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
+-
+- return mod
++ return None
++
++
++def wrap_loader(loader, class_name):
++ class ConfigurationLoader(loader.__class__):
++ def exec_module(self, module):
++ super().exec_module(module)
++
++ mod = module
++
++ cls_path = '{0}.{1}'.format(mod.__name__, class_name)
++
++ try:
++ cls = getattr(mod, class_name)
++ except AttributeError as err: # pragma: no cover
++ reraise(
++ err,
++ "Couldn't find configuration '{0}' in "
++ "module '{1}'".format(class_name, mod.__package__),
++ )
++ try:
++ cls.pre_setup()
++ cls.setup()
++ obj = cls()
++ attributes = uppercase_attributes(obj).items()
++ for name, value in attributes:
++ if callable(value) and not getattr(value, 'pristine', False):
++ value = value()
++ # in case a method returns a Value instance we have
++ # to do the same as the Configuration.setup method
++ if isinstance(value, Value):
++ setup_value(mod, name, value)
++ continue
++ setattr(mod, name, value)
++
++ setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(module.__name__,
++ class_name))
++ cls.post_setup()
++
++ except Exception as err:
++ reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
++
++ loader.__class__ = ConfigurationLoader
+diff --git a/tests/settings/dot_env.py b/tests/settings/dot_env.py
+index eab4237..7c9d3e6 100644
+--- a/tests/settings/dot_env.py
++++ b/tests/settings/dot_env.py
+@@ -6,3 +6,6 @@ class DotEnvConfiguration(Configuration):
+ DOTENV = 'test_project/.env'
+
+ DOTENV_VALUE = values.Value()
++
++ def DOTENV_VALUE_METHOD(self):
++ return values.Value(environ_name="DOTENV_VALUE")
+diff --git a/tests/settings/error.py b/tests/settings/error.py
+new file mode 100644
+index 0000000..a356910
+--- /dev/null
++++ b/tests/settings/error.py
+@@ -0,0 +1,8 @@
++from configurations import Configuration
++
++
++class ErrorConfiguration(Configuration):
++
++ @classmethod
++ def pre_setup(cls):
++ raise ValueError("Error in pre_setup")
+diff --git a/tests/test_env.py b/tests/test_env.py
+index 8066eea..50b7f66 100644
+--- a/tests/test_env.py
++++ b/tests/test_env.py
+@@ -11,4 +11,5 @@ class DotEnvLoadingTests(TestCase):
+ def test_env_loaded(self):
+ from tests.settings import dot_env
+ self.assertEqual(dot_env.DOTENV_VALUE, 'is set')
++ self.assertEqual(dot_env.DOTENV_VALUE_METHOD, 'is set')
+ self.assertEqual(dot_env.DOTENV_LOADED, dot_env.DOTENV)
+diff --git a/tests/test_error.py b/tests/test_error.py
+new file mode 100644
+index 0000000..85e87da
+--- /dev/null
++++ b/tests/test_error.py
+@@ -0,0 +1,22 @@
++import os
++from django.test import TestCase
++from unittest.mock import patch
++
++
++class ErrorTests(TestCase):
++
++ @patch.dict(os.environ, clear=True,
++ DJANGO_CONFIGURATION='ErrorConfiguration',
++ DJANGO_SETTINGS_MODULE='tests.settings.error')
++ def test_env_loaded(self):
++ with self.assertRaises(ValueError) as cm:
++ from tests.settings import error # noqa: F401
++
++ self.assertIsInstance(cm.exception, ValueError)
++ self.assertEqual(
++ cm.exception.args,
++ (
++ "Couldn't setup configuration "
++ "'tests.settings.error.ErrorConfiguration': Error in pre_setup ",
++ )
++ )
+diff --git a/tests/test_main.py b/tests/test_main.py
+index ff9ad54..47a9eb6 100644
+--- a/tests/test_main.py
++++ b/tests/test_main.py
+@@ -7,7 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
+
+ from unittest.mock import patch
+
+-from configurations.importer import ConfigurationImporter
++from configurations.importer import ConfigurationFinder
+
+ ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
+ TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
+@@ -42,12 +42,14 @@ class MainTests(TestCase):
+
+ @patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
+ def test_empty_module_var(self):
+- self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
++ with self.assertRaises(ImproperlyConfigured):
++ ConfigurationFinder()
+
+ @patch.dict(os.environ, clear=True,
+ DJANGO_SETTINGS_MODULE='tests.settings.main')
+ def test_empty_class_var(self):
+- self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
++ with self.assertRaises(ImproperlyConfigured):
++ ConfigurationFinder()
+
+ def test_global_settings(self):
+ from configurations.base import Configuration
+@@ -70,21 +72,21 @@ class MainTests(TestCase):
+ DJANGO_SETTINGS_MODULE='tests.settings.main',
+ DJANGO_CONFIGURATION='Test')
+ def test_initialization(self):
+- importer = ConfigurationImporter()
+- self.assertEqual(importer.module, 'tests.settings.main')
+- self.assertEqual(importer.name, 'Test')
++ finder = ConfigurationFinder()
++ self.assertEqual(finder.module, 'tests.settings.main')
++ self.assertEqual(finder.name, 'Test')
+ self.assertEqual(
+- repr(importer),
+- "<ConfigurationImporter for 'tests.settings.main.Test'>")
++ repr(finder),
++ "<ConfigurationFinder for 'tests.settings.main.Test'>")
+
+ @patch.dict(os.environ, clear=True,
+ DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
+ DJANGO_CONFIGURATION='Inheritance')
+ def test_initialization_inheritance(self):
+- importer = ConfigurationImporter()
+- self.assertEqual(importer.module,
++ finder = ConfigurationFinder()
++ self.assertEqual(finder.module,
+ 'tests.settings.inheritance')
+- self.assertEqual(importer.name, 'Inheritance')
++ self.assertEqual(finder.name, 'Inheritance')
+
+ @patch.dict(os.environ, clear=True,
+ DJANGO_SETTINGS_MODULE='tests.settings.main',
+@@ -93,12 +95,12 @@ class MainTests(TestCase):
+ '--settings=tests.settings.main',
+ '--configuration=Test'])
+ def test_configuration_option(self):
+- importer = ConfigurationImporter(check_options=False)
+- self.assertEqual(importer.module, 'tests.settings.main')
+- self.assertEqual(importer.name, 'NonExisting')
+- importer = ConfigurationImporter(check_options=True)
+- self.assertEqual(importer.module, 'tests.settings.main')
+- self.assertEqual(importer.name, 'Test')
++ finder = ConfigurationFinder(check_options=False)
++ self.assertEqual(finder.module, 'tests.settings.main')
++ self.assertEqual(finder.name, 'NonExisting')
++ finder = ConfigurationFinder(check_options=True)
++ self.assertEqual(finder.module, 'tests.settings.main')
++ self.assertEqual(finder.name, 'Test')
+
+ def test_configuration_argument_in_cli(self):
+ """
diff --git a/python-django-configurations.spec b/python-django-configurations.spec
index 3fbfb07..f66d7f8 100644
--- a/python-django-configurations.spec
+++ b/python-django-configurations.spec
@@ -17,6 +17,9 @@ URL: https://django-configurations.readthedocs.io/
Source: %{pypi_source}
Patch: %{pypi_name}-adjust_test_cases.diff
Patch: django-redis.diff
+# Fix Python 3.15 compatibility: move to PEP-451 style loader (exec_module)
+# https://github.com/jazzband/django-configurations/pull/386
+Patch: fix-pep451-exec-module.patch
BuildArch: noarch
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-06-06 10:53 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-06 10:53 [rpms/python-django-configurations] rawhide: Fix Python 3.15 compatibility: move to PEP-451 style loader
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox