Skip to content

Commit

Permalink
Replace deprecated load_module with exec_module (#498)
Browse files Browse the repository at this point in the history
* Replace to be deprecated load_module

* Satisfy mypy (sort of)

* Add tests to cover new statements

* Factor out the loader function and test seperately
  • Loading branch information
FollowTheProcess authored Oct 8, 2021
1 parent 39b8dd4 commit e9f7f03
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 10 deletions.
44 changes: 40 additions & 4 deletions nox/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
# limitations under the License.

import ast
import importlib.machinery
import importlib.util
import io
import json
import os
import sys
import types
from argparse import Namespace
from typing import List, Union
Expand All @@ -31,6 +32,42 @@
from nox.sessions import Result


def _load_and_exec_nox_module(global_config: Namespace) -> types.ModuleType:
"""
Loads, executes, then returns the global_config nox module.
Args:
global_config (Namespace): The global config.
Raises:
IOError: If the nox module cannot be loaded. This
exception is chosen such that it will be caught
by load_nox_module and logged appropriately.
Returns:
types.ModuleType: The initialised nox module.
"""
spec = importlib.util.spec_from_file_location(
"user_nox_module", global_config.noxfile
)
if not spec:
raise IOError(f"Could not get module spec from {global_config.noxfile}")

module = importlib.util.module_from_spec(spec)
if not module:
raise IOError(f"Noxfile {global_config.noxfile} is not a valid python module.")

sys.modules["user_nox_module"] = module

loader = spec.loader
if not loader: # pragma: no cover
raise IOError(f"Could not get module loader for {global_config.noxfile}")
# See https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
# unsure why mypy doesn't like this
loader.exec_module(module) # type: ignore
return module


def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]:
"""Load the user's noxfile and return the module object for it.
Expand Down Expand Up @@ -64,9 +101,8 @@ def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]:
# guess. The original working directory (the directory that Nox was
# invoked from) gets stored by the .invoke_from "option" in _options.
os.chdir(noxfile_parent_dir)
return importlib.machinery.SourceFileLoader(
"user_nox_module", global_config.noxfile
).load_module()

return _load_and_exec_nox_module(global_config)

except (VersionCheckFailed, InvalidVersionSpecifier) as error:
logger.error(str(error))
Expand Down
30 changes: 24 additions & 6 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ def test_load_nox_module_IOError(caplog):
our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py")
config = _options.options.namespace(noxfile=str(our_noxfile))

with mock.patch(
"nox.tasks.importlib.machinery.SourceFileLoader.load_module"
) as mock_load:
with mock.patch("nox.tasks.importlib.util.module_from_spec") as mock_load:
mock_load.side_effect = IOError

assert tasks.load_nox_module(config) == 2
Expand All @@ -112,15 +110,35 @@ def test_load_nox_module_OSError(caplog):
our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py")
config = _options.options.namespace(noxfile=str(our_noxfile))

with mock.patch(
"nox.tasks.importlib.machinery.SourceFileLoader.load_module"
) as mock_load:
with mock.patch("nox.tasks.importlib.util.module_from_spec") as mock_load:
mock_load.side_effect = OSError

assert tasks.load_nox_module(config) == 2
assert "Failed to load Noxfile" in caplog.text


def test_load_nox_module_invalid_spec():
our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py")
config = _options.options.namespace(noxfile=str(our_noxfile))

with mock.patch("nox.tasks.importlib.util.spec_from_file_location") as mock_spec:
mock_spec.return_value = None

with pytest.raises(IOError):
tasks._load_and_exec_nox_module(config)


def test_load_nox_module_invalid_module():
our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py")
config = _options.options.namespace(noxfile=str(our_noxfile))

with mock.patch("nox.tasks.importlib.util.module_from_spec") as mock_spec:
mock_spec.return_value = None

with pytest.raises(IOError):
tasks._load_and_exec_nox_module(config)


@pytest.fixture
def reset_needs_version():
"""Do not leak ``nox.needs_version`` between tests."""
Expand Down

0 comments on commit e9f7f03

Please sign in to comment.