diff --git a/src/reuse/__init__.py b/src/reuse/__init__.py index 495f35ed..416f7d94 100644 --- a/src/reuse/__init__.py +++ b/src/reuse/__init__.py @@ -182,11 +182,3 @@ def __bool__(self) -> bool: def __or__(self, value: "ReuseInfo") -> "ReuseInfo": return self.union(value) - - -class ReuseException(Exception): - """Base exception.""" - - -class IdentifierNotFound(ReuseException): - """Could not find SPDX identifier for license file.""" diff --git a/src/reuse/_annotate.py b/src/reuse/_annotate.py index e914f5c2..564c0341 100644 --- a/src/reuse/_annotate.py +++ b/src/reuse/_annotate.py @@ -30,12 +30,12 @@ ) from .comment import ( NAME_STYLE_MAP, - CommentCreateError, CommentStyle, EmptyCommentStyle, get_comment_style, ) -from .header import MissingReuseInfo, add_new_header, find_and_replace_header +from .exceptions import CommentCreateError, MissingReuseInfoError +from .header import add_new_header, find_and_replace_header from .i18n import _ from .project import Project from .types import StrPath @@ -152,7 +152,7 @@ def add_header_to_file( ) out.write("\n") result = 1 - except MissingReuseInfo: + except MissingReuseInfoError: out.write( _( "Error: Generated comment header for '{path}' is missing" diff --git a/src/reuse/cli/common.py b/src/reuse/cli/common.py index 1f943fba..c2ce2213 100644 --- a/src/reuse/cli/common.py +++ b/src/reuse/cli/common.py @@ -13,9 +13,9 @@ from license_expression import ExpressionError from .._util import _LICENSING -from ..global_licensing import GlobalLicensingParseError +from ..exceptions import GlobalLicensingConflictError, GlobalLicensingParseError from ..i18n import _ -from ..project import GlobalLicensingConflict, Project +from ..project import Project from ..vcs import find_root @@ -60,7 +60,7 @@ def project(self) -> Project: ).format(path=error.source, message=str(error)) ) from error - except (GlobalLicensingConflict, OSError) as error: + except (GlobalLicensingConflictError, OSError) as error: raise click.UsageError(str(error)) from error self._project = project diff --git a/src/reuse/comment.py b/src/reuse/comment.py index c8786933..d48e3bc4 100644 --- a/src/reuse/comment.py +++ b/src/reuse/comment.py @@ -32,19 +32,12 @@ from textwrap import dedent from typing import NamedTuple, Optional, Type, cast +from .exceptions import CommentCreateError, CommentParseError from .types import StrPath _LOGGER = logging.getLogger(__name__) -class CommentParseError(Exception): - """An error occurred during the parsing of a comment.""" - - -class CommentCreateError(Exception): - """An error occurred during the creation of a comment.""" - - class MultiLineSegments(NamedTuple): """Components that make up a multi-line comment style, e.g. '/*', '*', and '*/'. diff --git a/src/reuse/exceptions.py b/src/reuse/exceptions.py new file mode 100644 index 00000000..94d03d87 --- /dev/null +++ b/src/reuse/exceptions.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +"""All exceptions owned by :mod:`reuse`. These exceptions all inherit +:class:`ReuseError`. +""" + +from typing import Any, Optional + + +class ReuseError(Exception): + """Base exception.""" + + +class SpdxIdentifierNotFoundError(ReuseError): + """Could not find SPDX identifier for license file.""" + + +class GlobalLicensingParseError(ReuseError): + """An exception representing any kind of error that occurs when trying to + parse a :class:`reuse.global_licensing.GlobalLicensing` file. + """ + + def __init__(self, *args: Any, source: Optional[str] = None): + super().__init__(*args) + self.source = source + + +class GlobalLicensingParseTypeError(GlobalLicensingParseError, TypeError): + """An exception representing a type error while trying to parse a + :class:`reuse.global_licensing.GlobalLicensing` file. + """ + + +class GlobalLicensingParseValueError(GlobalLicensingParseError, ValueError): + """An exception representing a value error while trying to parse a + :class:`reuse.global_licensing.GlobalLicensing` file. + """ + + +class GlobalLicensingConflictError(ReuseError): + """There are two global licensing files in the project that are not + compatible. + """ + + +class MissingReuseInfoError(ReuseError): + """Some REUSE information is missing from the result.""" + + +class CommentError(ReuseError): + """An error occurred during an interaction with a comment.""" + + +class CommentCreateError(Exception): + """An error occurred during the creation of a comment.""" + + +class CommentParseError(Exception): + """An error occurred during the parsing of a comment.""" diff --git a/src/reuse/global_licensing.py b/src/reuse/global_licensing.py index 3a041529..bacc8f92 100644 --- a/src/reuse/global_licensing.py +++ b/src/reuse/global_licensing.py @@ -33,9 +33,14 @@ from debian.copyright import Error as DebianError from license_expression import ExpressionError -from . import ReuseException, ReuseInfo, SourceType +from . import ReuseInfo, SourceType from ._util import _LICENSING from .covered_files import iter_files +from .exceptions import ( + GlobalLicensingParseError, + GlobalLicensingParseTypeError, + GlobalLicensingParseValueError, +) from .i18n import _ from .types import StrPath from .vcs import VCSStrategy @@ -72,28 +77,6 @@ class PrecedenceType(Enum): OVERRIDE = "override" -class GlobalLicensingParseError(ReuseException): - """An exception representing any kind of error that occurs when trying to - parse a :class:`GlobalLicensing` file. - """ - - def __init__(self, *args: Any, source: Optional[str] = None): - super().__init__(*args) - self.source = source - - -class GlobalLicensingParseTypeError(GlobalLicensingParseError, TypeError): - """An exception representing a type error while trying to parse a - :class:`GlobalLicensing` file. - """ - - -class GlobalLicensingParseValueError(GlobalLicensingParseError, ValueError): - """An exception representing a value error while trying to parse a - :class:`GlobalLicensing` file. - """ - - @attrs.define class _CollectionOfValidator: collection_type: Type[Collection] = attrs.field() diff --git a/src/reuse/header.py b/src/reuse/header.py index 06d838c6..6000d9a8 100644 --- a/src/reuse/header.py +++ b/src/reuse/header.py @@ -28,12 +28,11 @@ extract_reuse_info, merge_copyright_lines, ) -from .comment import ( +from .comment import CommentStyle, EmptyCommentStyle, PythonCommentStyle +from .exceptions import ( CommentCreateError, CommentParseError, - CommentStyle, - EmptyCommentStyle, - PythonCommentStyle, + MissingReuseInfoError, ) from .i18n import _ @@ -53,10 +52,6 @@ class _TextSections(NamedTuple): after: str -class MissingReuseInfo(Exception): - """Some REUSE information is missing from the result.""" - - def _create_new_header( reuse_info: ReuseInfo, template: Optional[Template] = None, @@ -68,7 +63,8 @@ def _create_new_header( Raises: CommentCreateError: if a comment could not be created. - MissingReuseInfo: if the generated comment is missing SPDX information. + MissingReuseInfoError: if the generated comment is missing SPDX + information. """ if template is None: template = DEFAULT_TEMPLATE @@ -101,7 +97,7 @@ def _create_new_header( ) ) _LOGGER.debug(result) - raise MissingReuseInfo() + raise MissingReuseInfoError() return result @@ -125,7 +121,8 @@ def create_header( Raises: CommentCreateError: if a comment could not be created. - MissingReuseInfo: if the generated comment is missing SPDX information. + MissingReuseInfoError: if the generated comment is missing SPDX + information. """ if template is None: template = DEFAULT_TEMPLATE @@ -186,7 +183,7 @@ def _find_first_spdx_comment( preceding the comment, the comment itself, and everything following it. Raises: - MissingReuseInfo: if no REUSE info can be found in any comment + MissingReuseInfoError: if no REUSE info can be found in any comment. """ if style is None: style = PythonCommentStyle @@ -203,7 +200,7 @@ def _find_first_spdx_comment( text[:index], comment + "\n", text[index + len(comment) + 1 :] ) - raise MissingReuseInfo() + raise MissingReuseInfoError() def _extract_shebang(prefix: str, text: str) -> tuple[str, str]: @@ -248,14 +245,15 @@ def find_and_replace_header( Raises: CommentCreateError: if a comment could not be created. - MissingReuseInfo: if the generated comment is missing SPDX information. + MissingReuseInfoError: if the generated comment is missing SPDX + information. """ if style is None: style = PythonCommentStyle try: before, header, after = _find_first_spdx_comment(text, style=style) - except MissingReuseInfo: + except MissingReuseInfoError: before, header, after = "", "", text # Workaround. EmptyCommentStyle should always be completely replaced. diff --git a/src/reuse/project.py b/src/reuse/project.py index 7895313f..cf6ca24d 100644 --- a/src/reuse/project.py +++ b/src/reuse/project.py @@ -21,7 +21,7 @@ import attrs from binaryornot.check import is_binary -from . import IdentifierNotFound, ReuseInfo +from . import ReuseInfo from ._licenses import EXCEPTION_MAP, LICENSE_MAP from ._util import ( _LICENSEREF_PATTERN, @@ -30,6 +30,10 @@ reuse_info_of_file, ) from .covered_files import iter_files +from .exceptions import ( + GlobalLicensingConflictError, + SpdxIdentifierNotFoundError, +) from .global_licensing import ( GlobalLicensing, NestedReuseTOML, @@ -44,12 +48,6 @@ _LOGGER = logging.getLogger(__name__) -class GlobalLicensingConflict(Exception): - """There are two global licensing files in the project that are not - compatible. - """ - - class GlobalLicensingFound(NamedTuple): path: Path cls: Type[GlobalLicensing] @@ -111,8 +109,8 @@ def from_directory( decoded. GlobalLicensingParseError: if the global licensing config file could not be parsed. - GlobalLicensingConflict: if more than one global licensing config - file is present. + GlobalLicensingConflictError: if more than one global licensing + config file is present. """ root = Path(root) if not root.exists(): @@ -323,8 +321,8 @@ def find_global_licensing( :class:`GlobalLicensing`. Raises: - GlobalLicensingConflict: if more than one global licensing config - file is present. + GlobalLicensingConflictError: if more than one global licensing + config file is present. """ candidates: list[GlobalLicensingFound] = [] dep5_path = root / ".reuse/dep5" @@ -352,7 +350,7 @@ def find_global_licensing( ] if reuse_toml_candidates: if candidates: - raise GlobalLicensingConflict( + raise GlobalLicensingConflictError( _( "Found both '{new_path}' and '{old_path}'. You" " cannot keep both files simultaneously; they are" @@ -384,13 +382,13 @@ def _identifier_of_license(self, path: Path) -> str: License Identifier. """ if not path.suffix: - raise IdentifierNotFound(f"{path} has no file extension") + raise SpdxIdentifierNotFoundError(f"{path} has no file extension") if path.stem in self.license_map: return path.stem if _LICENSEREF_PATTERN.match(path.stem): return path.stem - raise IdentifierNotFound( + raise SpdxIdentifierNotFoundError( f"Could not find SPDX License Identifier for {path}" ) @@ -418,7 +416,7 @@ def _find_licenses(self) -> dict[str, Path]: try: identifier = self._identifier_of_license(path) - except IdentifierNotFound: + except SpdxIdentifierNotFoundError: if path.name in self.license_map: _LOGGER.info( _("{path} does not have a file extension").format( diff --git a/tests/test_comment.py b/tests/test_comment.py index b0766cdf..51c1e124 100644 --- a/tests/test_comment.py +++ b/tests/test_comment.py @@ -13,8 +13,6 @@ import pytest from reuse.comment import ( - CommentCreateError, - CommentParseError, CommentStyle, CppCommentStyle, HtmlCommentStyle, @@ -22,6 +20,7 @@ PythonCommentStyle, _all_style_classes, ) +from reuse.exceptions import CommentCreateError, CommentParseError @pytest.fixture( diff --git a/tests/test_global_licensing.py b/tests/test_global_licensing.py index 17562391..62a69557 100644 --- a/tests/test_global_licensing.py +++ b/tests/test_global_licensing.py @@ -15,11 +15,13 @@ from reuse import ReuseInfo, SourceType from reuse._util import _LICENSING -from reuse.global_licensing import ( - AnnotationsItem, +from reuse.exceptions import ( GlobalLicensingParseError, GlobalLicensingParseTypeError, GlobalLicensingParseValueError, +) +from reuse.global_licensing import ( + AnnotationsItem, NestedReuseTOML, PrecedenceType, ReuseDep5, diff --git a/tests/test_header.py b/tests/test_header.py index 3ef286c2..15d5507d 100644 --- a/tests/test_header.py +++ b/tests/test_header.py @@ -11,13 +11,9 @@ import pytest from reuse import ReuseInfo -from reuse.comment import CommentCreateError, CppCommentStyle -from reuse.header import ( - MissingReuseInfo, - add_new_header, - create_header, - find_and_replace_header, -) +from reuse.comment import CppCommentStyle +from reuse.exceptions import CommentCreateError, MissingReuseInfoError +from reuse.header import add_new_header, create_header, find_and_replace_header # REUSE-IgnoreStart @@ -73,7 +69,7 @@ def test_create_header_template_no_spdx(template_no_spdx): """Create a header with a template that does not have all REUSE info.""" info = ReuseInfo({"GPL-3.0-or-later"}, {"SPDX-FileCopyrightText: Jane Doe"}) - with pytest.raises(MissingReuseInfo): + with pytest.raises(MissingReuseInfoError): create_header(info, template=template_no_spdx) diff --git a/tests/test_project.py b/tests/test_project.py index 59014bf4..448a2bf7 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -22,12 +22,12 @@ from reuse import ReuseInfo, SourceType from reuse._util import _LICENSING from reuse.covered_files import iter_files -from reuse.global_licensing import ( +from reuse.exceptions import ( + GlobalLicensingConflictError, GlobalLicensingParseError, - ReuseDep5, - ReuseTOML, ) -from reuse.project import GlobalLicensingConflict, Project +from reuse.global_licensing import ReuseDep5, ReuseTOML +from reuse.project import Project # REUSE-IgnoreStart @@ -52,7 +52,7 @@ def test_project_conflicting_global_licensing(empty_directory): (empty_directory / "REUSE.toml").write_text("version = 1") (empty_directory / ".reuse").mkdir() shutil.copy(RESOURCES_DIRECTORY / "dep5", empty_directory / ".reuse/dep5") - with pytest.raises(GlobalLicensingConflict): + with pytest.raises(GlobalLicensingConflictError): Project.from_directory(empty_directory) @@ -605,7 +605,7 @@ def test_find_global_licensing_none(empty_directory): def test_find_global_licensing_conflict(fake_repository_dep5): """Expect an error on a conflict""" (fake_repository_dep5 / "REUSE.toml").write_text("version = 1") - with pytest.raises(GlobalLicensingConflict): + with pytest.raises(GlobalLicensingConflictError): Project.find_global_licensing(fake_repository_dep5)