diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 60b4cfb..c30e87a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +0.12.1 (2022-02-12) +------------------- + +- Add helpful link to 'failed to find module' message. + 0.12.0 (2022-02-06) ------------------- diff --git a/docs/advanced.rst b/docs/advanced.rst index 19ef321..77668f5 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -18,7 +18,7 @@ Use the following configuration: repos: - repo: https://github.com/ariebovenberg/slotscheck - rev: v0.12.0 + rev: v0.12.1 hooks: - id: slotscheck # If your Python files are not importable from the project root, diff --git a/docs/discovery.rst b/docs/discovery.rst index 348b637..3b1839e 100644 --- a/docs/discovery.rst +++ b/docs/discovery.rst @@ -1,10 +1,9 @@ Module discovery ================ -To check files, slotscheck needs to import them. -The process if importing files usually behaves as you would expect. -However, there are some complications that you may -need to be aware of. +Slotscheck needs to import files in order to check them. +This process usually behaves as you would expect. +However, there are some complications that you may need to be aware of. .. admonition:: Summary @@ -17,17 +16,18 @@ need to be aware of. Whether you run ``python -m slotscheck`` or just ``slotscheck`` has an impact on which files will be imported and checked. -This is not a choice by ``slotscheck``, but simply the way entry points work -in Python. When running ``python -m slotscheck``, the current working +This is not a choice by ``slotscheck``, but simply the way Python works. +When running ``python -m slotscheck``, the current working directory is added to ``sys.path``, so any modules in the current directory -can be imported. This is not the case when running just ``slotscheck``. +can be imported. This is not the case when running bare ``slotscheck``. + So if you run ``slotscheck foo.py``, ``foo`` will not be importable. In fact, if ``foo`` happens to be the name of an installed module, ``import foo`` will import that instead! In that case ``slotscheck`` will refuse to run, and print an informative message. An alternative way to ensure the correct files can be imported is with the -``PYTHONPATH`` environment variable. +``$PYTHONPATH`` environment variable. To illustrate all this, imagine the following file tree:: diff --git a/pyproject.toml b/pyproject.toml index 62e8936..3501694 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "slotscheck" -version = "0.12.0" +version = "0.12.1" description = "Ensure your __slots__ are working properly." authors = ["Arie Bovenberg "] license = "MIT" diff --git a/src/slotscheck/cli.py b/src/slotscheck/cli.py index 80f4462..58cacc8 100644 --- a/src/slotscheck/cli.py +++ b/src/slotscheck/cli.py @@ -9,7 +9,6 @@ from pathlib import Path from textwrap import indent from typing import ( - Any, Collection, Iterable, Iterator, @@ -114,7 +113,7 @@ @click.option( "--strict-imports/--no-strict-imports", help="Treat failed imports as errors.", - default=True, + default=None, show_default="strict", ) @click.option( @@ -134,10 +133,28 @@ def root( module: Sequence[str], verbose: bool, settings: Optional[AbsPath], - **kwargs: Any, + require_superclass: Optional[bool], + require_subclass: Optional[bool], + strict_imports: Optional[bool], + exclude_classes: Optional[config.RegexStr], + include_classes: Optional[config.RegexStr], + exclude_modules: Optional[config.RegexStr], + include_modules: Optional[config.RegexStr], ) -> None: "Check whether your __slots__ are working properly." - conf = config.collect(kwargs, Path.cwd(), settings) + conf = config.collect( + config.PartialConfig( + strict_imports=strict_imports, + require_superclass=require_superclass, + require_subclass=require_subclass, + exclude_classes=exclude_classes, + include_classes=include_classes, + exclude_modules=exclude_modules, + include_modules=include_modules, + ), + Path.cwd(), + settings, + ) if not (files or module): print("No files or modules given. Nothing to do!") exit(0) @@ -145,7 +162,11 @@ def root( try: classes, modules = _collect(files, module, conf) except ModuleNotFoundError as e: - print(_format_error(f"Module '{e.name}' not found.")) + print( + f"ERROR: Module '{e.name}' not found.\n\n" + "See slotscheck.rtfd.io/en/latest/discovery.html\n" + "for help resolving common import problems." + ) exit(1) except UnexpectedImportLocation as e: print( diff --git a/src/slotscheck/config.py b/src/slotscheck/config.py index b007e4d..ac71858 100644 --- a/src/slotscheck/config.py +++ b/src/slotscheck/config.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, fields from itertools import chain from pathlib import Path -from typing import Any, ClassVar, Collection, Mapping, Optional, Type, TypeVar +from typing import ClassVar, Collection, Mapping, Optional, Type, TypeVar import tomli @@ -111,12 +111,12 @@ def apply(self, other: PartialConfig) -> "Config": def collect( - cli_kwargs: Mapping[str, Any], cwd: Path, config: Optional[Path] + from_cli: PartialConfig, cwd: Path, config: Optional[Path] ) -> Config: "Gather and combine configuration options from the available sources" confpath = config or find_config_file(cwd) conf = PartialConfig.load(confpath) if confpath else PartialConfig.EMPTY - return Config.DEFAULT.apply(conf).apply(PartialConfig(**cli_kwargs)) + return Config.DEFAULT.apply(conf).apply(from_cli) _CONFIG_FILENAMES = ("pyproject.toml", "setup.cfg") diff --git a/tests/examples/compiled/__init__.py b/tests/examples/compiled/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/examples/compiled/bar.pyc b/tests/examples/compiled/bar.pyc new file mode 100644 index 0000000..1135ad2 Binary files /dev/null and b/tests/examples/compiled/bar.pyc differ diff --git a/tests/examples/compiled/foo.py b/tests/examples/compiled/foo.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/test_cli.py b/tests/src/test_cli.py index 1f6c2c2..c986aad 100644 --- a/tests/src/test_cli.py +++ b/tests/src/test_cli.py @@ -31,7 +31,11 @@ def test_no_inputs(runner: CliRunner): def test_module_doesnt_exist(runner: CliRunner): result = runner.invoke(cli, ["-m", "foo"]) assert result.exit_code == 1 - assert result.output == "ERROR: Module 'foo' not found.\n" + assert result.output == ( + "ERROR: Module 'foo' not found.\n\n" + "See slotscheck.rtfd.io/en/latest/discovery.html\n" + "for help resolving common import problems.\n" + ) def test_path_doesnt_exist(runner: CliRunner): diff --git a/tests/src/test_config.py b/tests/src/test_config.py index 49e3207..ff59f2c 100644 --- a/tests/src/test_config.py +++ b/tests/src/test_config.py @@ -1,4 +1,3 @@ -import dataclasses import re from pathlib import Path @@ -91,9 +90,7 @@ def test_parent_directory_cfg(self, tmpdir): def test_collect(tmpdir): (tmpdir / "setup.cfg").write_text(EXAMPLE_INI, encoding="utf-8") (tmpdir / "pyproject.toml").write_binary(EXAMPLE_TOML) - assert collect( - dataclasses.asdict(Config.EMPTY), Path(tmpdir), None - ).require_subclass + assert collect(PartialConfig.EMPTY, Path(tmpdir), None).require_subclass class TestOptionsApply: diff --git a/tests/src/test_discovery.py b/tests/src/test_discovery.py index ce4c303..cda7943 100644 --- a/tests/src/test_discovery.py +++ b/tests/src/test_discovery.py @@ -252,6 +252,11 @@ def test_unexpected_location(self): EXAMPLES_DIR / "module_misc/a/b/c.py", ) + def test_pyc_file(self): + assert module_tree("compiled", None) == make_pkg( + "compiled", Module("foo"), Module("bar") + ) + class TestFilterName: def test_module(self):