From 61b88decf5d813386685cbffcc5b66d510e0a3f7 Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Thu, 31 Oct 2024 08:16:44 +0100 Subject: [PATCH 1/6] Fix bug when user load config from a state with no config. --- src/evaluation_system/settings/database.py | 41 +++++++--- src/freva/_history.py | 13 +++- src/freva/utils.py | 87 ++++++++++++++++++---- 3 files changed, 114 insertions(+), 27 deletions(-) diff --git a/src/evaluation_system/settings/database.py b/src/evaluation_system/settings/database.py index d7442f95..7223cfc6 100755 --- a/src/evaluation_system/settings/database.py +++ b/src/evaluation_system/settings/database.py @@ -1,10 +1,11 @@ -import json -import time +from copy import deepcopy +from pathlib import Path +from typing import Any, Dict, Optional import django from django.conf import settings - from evaluation_system.misc import config +from evaluation_system.misc.config import reloadConfiguration SETTINGS = dict( TIME_ZONE="UTC", @@ -25,17 +26,39 @@ SETTINGS["DATABASES"] = { "default": { "ENGINE": "django.db.backends.mysql", - "NAME": config.get(config.DB_DB), - "USER": config.get(config.DB_USER), - "PASSWORD": config.get(config.DB_PASSWD), - "HOST": config.get( - config.DB_HOST - ), # Or an IP Address that your DB is hosted on + "NAME": config.get(config.DB_DB) or "", + "USER": config.get(config.DB_USER) or "", + "PASSWORD": config.get(config.DB_PASSWD) or "", + "HOST": config.get(config.DB_HOST) + or "", # Or an IP Address that your DB is hosted on "PORT": config.get(str(config.DB_PORT), "3306"), } } + try: settings.configure(**SETTINGS) django.setup() except RuntimeError: pass + + +def reconfigure_django(config_file: Optional[Path] = None) -> None: + """Reconfigure the django settings.""" + django_settings: Dict[str, Any] = deepcopy(SETTINGS) + reloadConfiguration(config_file) + django_settings["DATABASES"] = { + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": config.get(config.DB_DB) or "", + "USER": config.get(config.DB_USER) or "", + "PASSWORD": config.get(config.DB_PASSWD) or "", + "HOST": config.get(config.DB_HOST) or "", + "PORT": config.get(str(config.DB_PORT), "3306"), + } + } + try: + settings.configure(**django_settings) + django.setup() + except RuntimeError: + for key, value in django_settings["DATABASES"]["default"].items(): + settings.DATABASES["default"][key] = value diff --git a/src/freva/_history.py b/src/freva/_history.py index c251eb6a..9bca39ad 100644 --- a/src/freva/_history.py +++ b/src/freva/_history.py @@ -6,11 +6,20 @@ from typing import Any, Optional, Union import lazy_import - +from django.core.exceptions import ImproperlyConfigured from evaluation_system.misc import logger +from evaluation_system.misc.config import reloadConfiguration +db_settings = lazy_import.lazy_module("evaluation_system.settings.database") pm = lazy_import.lazy_module("evaluation_system.api.plugin_manager") -from evaluation_system.model.user import User +from .utils import config + +try: + from evaluation_system.model.user import User +except ImproperlyConfigured: + reloadConfiguration() + db_settings.reconfigure_django() + __all__ = ["history"] diff --git a/src/freva/utils.py b/src/freva/utils.py index e8871a2b..bf42a127 100644 --- a/src/freva/utils.py +++ b/src/freva/utils.py @@ -31,19 +31,22 @@ import lazy_import from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured +from django.db.utils import OperationalError +from evaluation_system.misc import logger +from evaluation_system.misc.exceptions import ConfigurationException +from evaluation_system.misc.utils import metadict as meta_type from rich.console import Console from rich.live import Live from rich.spinner import Spinner import freva -from evaluation_system.misc import logger -from evaluation_system.misc.utils import metadict as meta_type pm = lazy_import.lazy_module("evaluation_system.api.plugin_manager") cancel_command = lazy_import.lazy_callable( "evaluation_system.api.workload_manager.cancel_command" ) cfg = lazy_import.lazy_module("evaluation_system.misc.config") +db_settings = lazy_import.lazy_module("evaluation_system.settings.database") def is_jupyter() -> bool: @@ -65,8 +68,24 @@ def handled_exception(func: Callable[..., Any]) -> Callable[..., Any]: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: """Wrapper function that handles the exception.""" + wrong_config_msg = { + False: ( + "consult the `freva.config` class to see how to pass a " + "valid configuration." + ), + True: ( + "export the EVALUATION_SYSTEM_CONFIG_FILE " + "environment variable to set a valid configuration file." + ), + } try: return func(*args, **kwargs) + except (ImproperlyConfigured, OperationalError): # prgama: no cover + # Wrap django error in a more informative exception + msg = "Your freva instance doesn't seem to be properly configured: " + raise ConfigurationException( + msg + wrong_config_msg[logger.is_cli] + ) from None except BaseException as error: exception_handler(error) @@ -78,7 +97,7 @@ def exception_handler(exception: BaseException, cli: bool = False) -> None: trace_back = exception.__traceback__ appendix = { - False: " - decrease log level via `freva.logger.setLevel`", + False: " - decrease log level via `freva.logger.setLevel(10)`", True: " - increase verbosity flags (-v)", }[cli] append_msg = "" @@ -352,12 +371,16 @@ class config: With the help of this class you can not only (temporarily) override the default configuration file and use a configuration from another project, but you can also set a path to a configuration file if no - configuration file has been set. + configuration file has been set. Additionally you can set the any + plugin paths that are not part of the configuration file. Parameters ---------- - config_file: str | pathlib.Path + config_file: str | pathlib.Path, default: None Path to the (new) configuration file. + plugin_path: str | List[str], default: None + New plugins that should be used, use a list of paths if you want + export multiple plugins. Examples -------- @@ -382,24 +405,51 @@ class config: freva.config("/work/freva/evaluation_system.conf") files = sorted(freva.databrowser(project="user-1234", experiment="extremes")) + Import a new user defined plugin, for example if you have created a plugin + called ``MyPlugin`` that is located in ``~/freva/myplugin/plugin.py`` + you would set to ``plugin_path='~/freva/my_plugin,plugin_module'``. + + :: + + import freva + freva.config(plugin_path="~/freva/my_plugin,plugin_module") + freva.run_plugin('MyPlugin", variable1=1, variable2="a") + + In the same fashion you can set multiple plugin paths: + + :: + import freva + freva.config(plugin_path=["~/freva/my_plugin1,plugin_module_b"], + "~/ freva/my_plugin2,plugin_module_b"]) + """ _original_config_env = os.environ.get( "EVALUATION_SYSTEM_CONFIG_FILE", - cfg.CONFIG_FILE, ) + _original_plugin_env = os.environ.get("EVALUATION_SYSTEM_PLUGINS") db_reloaded: List[bool] = [False] - def __init__(self, config_file: Union[str, Path]) -> None: - self._config_file = Path(config_file).expanduser().absolute() - os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = str(self._config_file) - cfg.reloadConfiguration(self._config_file) - pm.reload_plugins() + def __init__( + self, + config_file: Optional[Union[str, Path]] = None, + plugin_path: Optional[Union[str, List[str]]] = None, + ) -> None: + plugin_path = plugin_path or [] + if isinstance(plugin_path, str): + plugin_path = [plugin_path] + if config_file: + config_file = Path(config_file).expanduser().absolute() + os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = str(config_file) try: if django_settings.DATABASES: self.db_reloaded[0] = True - except (ImproperlyConfigured, AttributeError): - pass + db_settings.reconfigure_django(config_file) + except (ImproperlyConfigured, AttributeError): # prgama: no cover + pass # prgama: no cover + assert isinstance(db_settings.SETTINGS, dict) + if plugin_path or config_file: + pm.reload_plugins(plugin_path=plugin_path) def __enter__(self) -> "config": return self @@ -410,7 +460,12 @@ def __exit__( exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> None: - os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = self._original_config_env - cfg.reloadConfiguration(self._original_config_env) - pm.reload_plugins() + os.environ.pop("EVALUATION_SYSTEM_CONFIG_FILE") + if self._original_config_env: + os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = self._original_config_env + db_settings.reconfigure_django(self._original_config_env) + try: + pm.reload_plugins() + except Exception: + pass self.db_reloaded[0] = False From 10bc4507b8c4a5b63922e64f8001ee7d6e6b9d26 Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Thu, 31 Oct 2024 08:17:16 +0100 Subject: [PATCH 2/6] Allow users to load user plugins without setting the env variable --- src/evaluation_system/api/plugin_manager.py | 73 +++++++++++++-------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/evaluation_system/api/plugin_manager.py b/src/evaluation_system/api/plugin_manager.py index ec5aaebf..4d998db3 100755 --- a/src/evaluation_system/api/plugin_manager.py +++ b/src/evaluation_system/api/plugin_manager.py @@ -40,13 +40,20 @@ from multiprocessing import Pool from pathlib import Path from types import ModuleType -from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, TypeVar, Union, cast +from typing import ( + Any, + Dict, + List, + Iterator, + Optional, + Sequence, + Tuple, + TypeVar, + Union, + cast, +) from django.db.models.query import QuerySet -from PIL import Image, ImageSequence -from rich import print as pprint -from typing_extensions import TypedDict - from evaluation_system import __version__ as version_api from evaluation_system.misc import config from evaluation_system.misc import logger as log @@ -65,6 +72,9 @@ ) from evaluation_system.model.plugins.models import Parameter from evaluation_system.model.user import User +from PIL import Image, ImageSequence +from rich import print as pprint +from typing_extensions import TypedDict from .plugin import PluginAbstract @@ -183,7 +193,9 @@ def munge(seq: Sequence[T]) -> Iterator[T]: yield item -def reload_plugins(user_name: Optional[str] = None) -> None: +def reload_plugins( + user_name: Optional[str] = None, plugin_path: Optional[List[str]] = None +) -> None: """Reload all plug-ins. Plug-ins are then loaded first from the :class:`PLUGIN_ENV` @@ -193,33 +205,36 @@ def reload_plugins(user_name: Optional[str] = None) -> None: Parameters ---------- - user_name - freva user to load the plugins for, if none, it will load the current user + user_name: str, default: None + freva user to load the plugins for, if none, it will load the current + user + plugin_path: List[str], default: None + A list of additional plugin paths that should be loaded. """ user_name = user_name or User().getName() - + plugin_path = plugin_path or [] + user_plugins_from_env = [os.getenv(PLUGIN_ENV) or ""] + user_plugins = os.pathsep.join(plugin_path + user_plugins_from_env) __plugin_modules__: dict[str, str] = {} __plugins__ = {} __plugins_meta: dict[str, PluginMetadata] = {} __plugin_modules_user__[user_name] = {} __plugins_meta_user[user_name] = {} extra_plugins = [] - if os.environ.get(PLUGIN_ENV): - # now get all modules loaded from the environment - for path, module_name in plugin_env_iter(os.environ[PLUGIN_ENV]): - # extend path to be exact by resolving all - # "user shortcuts" (e.g. '~' or '$HOME') - path = os.path.abspath(os.path.expandvars(os.path.expanduser(path))) - if os.path.isdir(path): - # we have a plugin_imp with defined api - sys.path.append(path) - # TODO this is not working like in the previous loop. Though we might - # just want to remove it, as there seem to be no use for this info... - __plugin_modules__[module_name] = os.path.join(path, module_name) - extra_plugins.append(module_name) - else: - log.warning("Cannot load %s, directory missing: %s", module_name, path) + for path, module_name in plugin_env_iter(user_plugins): + # extend path to be exact by resolving all + # "user shortcuts" (e.g. '~' or '$HOME') + path = os.path.abspath(os.path.expandvars(os.path.expanduser(path))) + if os.path.isdir(path): + # we have a plugin_imp with defined api + sys.path.append(path) + # TODO this is not working like in the previous loop. Though we might + # just want to remove it, as there seem to be no use for this info... + __plugin_modules__[module_name] = os.path.join(path, module_name) + extra_plugins.append(module_name) + else: + log.warning("Cannot load %s, directory missing: %s", module_name, path) # the same for user specific env variable if user_name: # is it possible for User().getName() to be None? if PLUGIN_ENV + "_" + user_name in os.environ: @@ -1464,7 +1479,7 @@ def dict2conf( return conf -def plugin_env_iter(envvar: str) -> Iterator[tuple[str, ...]]: +def plugin_env_iter(plugin_paths: str) -> Iterator[tuple[str, ...]]: """Splits the elements of a plugin env string. Returns an iterator over all elements in a plugin environment @@ -1472,7 +1487,7 @@ def plugin_env_iter(envvar: str) -> Iterator[tuple[str, ...]]: Parameters ---------- - envvar + plugin_paths: string to split Returns @@ -1482,7 +1497,11 @@ def plugin_env_iter(envvar: str) -> Iterator[tuple[str, ...]]: The type is variable length but this will only ever return a 2 element tuple when given a well formed string. """ - return (i.strip().partition(",")[::2] for i in envvar.split(":") if i.strip()) + for plugin in plugin_paths.split(os.pathsep): + p = plugin.strip() + if p: + path, _, module = p.partition(",") + yield path, module def find_plugin_class(mod: ModuleType) -> type[PluginAbstract]: From 59de5a711f7f6a0e07d79112ac17371a88c65d8f Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Thu, 31 Oct 2024 08:17:58 +0100 Subject: [PATCH 3/6] Update docs and converage config. --- .coveragerc | 5 ++- docs/source/FAQ.ipynb | 34 +++++++++++++++++-- .../tests/A_Notebook_Smoke_Test.ipynb | 6 ++-- src/evaluation_system/tests/mocks/__init__.py | 4 ++- .../tests/user_config_test.py | 8 +++++ 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/.coveragerc b/.coveragerc index d3762d5a..bc273fec 100644 --- a/.coveragerc +++ b/.coveragerc @@ -19,6 +19,9 @@ exclude_lines = # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: -omit = ./src/evaluation_system/fuse/*,./src/evaluation_system/external/*,./src/freva/cli/__main__.py + except ImportError: + except (ImproperlyConfigured, OperationalError): + except (ImproperlyConfigured, AttributeError): +omit = src/evaluation_system/tests/*,./src/evaluation_system/fuse/*,./src/evaluation_system/external/*,./src/freva/cli/__main__.py ignore_errors = False diff --git a/docs/source/FAQ.ipynb b/docs/source/FAQ.ipynb index 5e1b8895..b43a2a5d 100644 --- a/docs/source/FAQ.ipynb +++ b/docs/source/FAQ.ipynb @@ -78,13 +78,43 @@ "```python\n", "import freva\n", "# In order to use freva we have to tell where to get the configuration\n", - "freva.config(\"/path/to/the/freva/config/file.conf\")\n", + "freva.config(config_file=\"/path/to/the/freva/config/file.conf\")\n", "# Talk to your admins to get the location of the config file.\n", "```\n", "\n", "Please talk to your admins on where the central freva configuration is located." ] }, + { + "cell_type": "markdown", + "id": "9826e767-b45c-4199-bc42-efeb747a2e82", + "metadata": {}, + "source": [ + "### How can I add my own plugins?\n", + "\n", + "If you are working with python and want to load your own plugin definitions you can also the `freva.config` to load you own plugin definitons. Assuming you have\n", + "your plugin definition saved in `~/freva/mypluing/the_plugin.py` you can load your own plugin via:\n", + "\n", + "```python\n", + "import freva\n", + "freva.config(plugin_path=\"~/freva/mypluing,the_plugin\")\n", + "```\n", + "\n", + "You can load multiple plugins by using a list.\n", + "\n", + "```python\n", + "import freva\n", + "freva.config(plugin_path=[\"~/freva/mypluing,the_plugin_1\", \"~/freva/mypluing,the_plugin_2\"])\n", + "```\n", + "\n", + "Loading plugins can of course be combined with loading a different freva config:\n", + "\n", + "```python\n", + "freva.config(config_file=\"/path/to/the/freva/config/file.conf\",\n", + " plugin_path=\"~/freva/mypluing,the_plugin\")\n", + "```" + ] + }, { "cell_type": "markdown", "id": "b68392b5-5375-40b8-84d5-ac3daeaeb9a3", @@ -266,7 +296,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/src/evaluation_system/tests/A_Notebook_Smoke_Test.ipynb b/src/evaluation_system/tests/A_Notebook_Smoke_Test.ipynb index c4df5a4b..e0b0cf95 100644 --- a/src/evaluation_system/tests/A_Notebook_Smoke_Test.ipynb +++ b/src/evaluation_system/tests/A_Notebook_Smoke_Test.ipynb @@ -4,9 +4,7 @@ "cell_type": "code", "execution_count": 1, "id": "63a857b5", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -102,7 +100,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/src/evaluation_system/tests/mocks/__init__.py b/src/evaluation_system/tests/mocks/__init__.py index eb33e64a..8ed565aa 100644 --- a/src/evaluation_system/tests/mocks/__init__.py +++ b/src/evaluation_system/tests/mocks/__init__.py @@ -1,5 +1,7 @@ from pathlib import Path +DUMMY_PATH = str(Path(__file__).parent) + TEST_EVAL = """[evaluation_system] base_dir=evaluation_system project_name=freva-ces @@ -26,7 +28,7 @@ [plugin:DummyPlugin] python_path={dummy_path} module=dummy""".format( - dummy_path=Path(__file__).parent + dummy_path=DUMMY_PATH ) TEST_DRS = """ diff --git a/src/evaluation_system/tests/user_config_test.py b/src/evaluation_system/tests/user_config_test.py index 52ffb539..4cdb5e74 100644 --- a/src/evaluation_system/tests/user_config_test.py +++ b/src/evaluation_system/tests/user_config_test.py @@ -8,6 +8,7 @@ import freva from .conftest import get_config +from .mocks import DUMMY_PATH def test_load_config(dummy_config) -> None: @@ -27,3 +28,10 @@ def test_load_config(dummy_config) -> None: freva.run_plugin("dummyplugin", the_number=1, batchmode=True) with pytest.warns(UserWarning): freva.run_plugin("dummyplugin", the_number=1) + + +def test_load_user_plugin(dummy_config) -> None: + """Test loading a user defined plugin.""" + with freva.config(plugin_path=f"{DUMMY_PATH},dummy"): + assert "dummyplugin" in freva.list_plugins() + assert "dummyplugin" not in freva.list_plugins() From 5d104d69d36eff21ad292860e82294f049e3faf8 Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Mon, 4 Nov 2024 08:04:30 +0100 Subject: [PATCH 4/6] Fix typos. --- docs/source/FAQ.ipynb | 6 +++--- src/freva/utils.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/source/FAQ.ipynb b/docs/source/FAQ.ipynb index b43a2a5d..0a5b32a8 100644 --- a/docs/source/FAQ.ipynb +++ b/docs/source/FAQ.ipynb @@ -92,8 +92,8 @@ "source": [ "### How can I add my own plugins?\n", "\n", - "If you are working with python and want to load your own plugin definitions you can also the `freva.config` to load you own plugin definitons. Assuming you have\n", - "your plugin definition saved in `~/freva/mypluing/the_plugin.py` you can load your own plugin via:\n", + "If you are working with python and want to load your own plugin definitions you can also use `freva.config` to load you own plugin definitons. Assuming you have\n", + "your plugin definition saved in `~/freva/mypluging/the_plugin.py` you can load your own plugin via:\n", "\n", "```python\n", "import freva\n", @@ -104,7 +104,7 @@ "\n", "```python\n", "import freva\n", - "freva.config(plugin_path=[\"~/freva/mypluing,the_plugin_1\", \"~/freva/mypluing,the_plugin_2\"])\n", + "freva.config(plugin_path=[\"~/freva/mypluging,the_plugin_1\", \"~/freva/mypluging,the_plugin_2\"])\n", "```\n", "\n", "Loading plugins can of course be combined with loading a different freva config:\n", diff --git a/src/freva/utils.py b/src/freva/utils.py index bf42a127..725b471f 100644 --- a/src/freva/utils.py +++ b/src/freva/utils.py @@ -82,7 +82,9 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return func(*args, **kwargs) except (ImproperlyConfigured, OperationalError): # prgama: no cover # Wrap django error in a more informative exception - msg = "Your freva instance doesn't seem to be properly configured: " + msg = ( + "Your freva instance doesn't seem to be properly configured: " + ) raise ConfigurationException( msg + wrong_config_msg[logger.is_cli] ) from None @@ -371,8 +373,8 @@ class config: With the help of this class you can not only (temporarily) override the default configuration file and use a configuration from another project, but you can also set a path to a configuration file if no - configuration file has been set. Additionally you can set the any - plugin paths that are not part of the configuration file. + configuration file has been set. Additionally you can set any plugin + paths that are not part of the configuration file. Parameters ---------- @@ -462,7 +464,9 @@ def __exit__( ) -> None: os.environ.pop("EVALUATION_SYSTEM_CONFIG_FILE") if self._original_config_env: - os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = self._original_config_env + os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = ( + self._original_config_env + ) db_settings.reconfigure_django(self._original_config_env) try: pm.reload_plugins() From fa5569d0ab7452c52dee1dad45303a4815d69372 Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Mon, 4 Nov 2024 08:06:07 +0100 Subject: [PATCH 5/6] Fix more typos. --- docs/source/FAQ.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/FAQ.ipynb b/docs/source/FAQ.ipynb index 0a5b32a8..3814c6a2 100644 --- a/docs/source/FAQ.ipynb +++ b/docs/source/FAQ.ipynb @@ -97,7 +97,7 @@ "\n", "```python\n", "import freva\n", - "freva.config(plugin_path=\"~/freva/mypluing,the_plugin\")\n", + "freva.config(plugin_path=\"~/freva/mypluging,the_plugin\")\n", "```\n", "\n", "You can load multiple plugins by using a list.\n", From 37ed566204119e617378b54571ac3a4d8da7c87e Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Mon, 4 Nov 2024 09:14:28 +0100 Subject: [PATCH 6/6] Fix linting issues. --- src/freva/utils.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/freva/utils.py b/src/freva/utils.py index 725b471f..20f2726a 100644 --- a/src/freva/utils.py +++ b/src/freva/utils.py @@ -82,9 +82,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return func(*args, **kwargs) except (ImproperlyConfigured, OperationalError): # prgama: no cover # Wrap django error in a more informative exception - msg = ( - "Your freva instance doesn't seem to be properly configured: " - ) + msg = "Your freva instance doesn't seem to be properly configured: " raise ConfigurationException( msg + wrong_config_msg[logger.is_cli] ) from None @@ -464,9 +462,7 @@ def __exit__( ) -> None: os.environ.pop("EVALUATION_SYSTEM_CONFIG_FILE") if self._original_config_env: - os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = ( - self._original_config_env - ) + os.environ["EVALUATION_SYSTEM_CONFIG_FILE"] = self._original_config_env db_settings.reconfigure_django(self._original_config_env) try: pm.reload_plugins()