Skip to content

The collect_unexported Python library

Common configuration settings and definitions

Constants

collect_unexported.defs.VERSION = '0.1.1' module-attribute

The version of the collect-unexported tool.

Runtime configuration settings

collect_unexported.defs.Source dataclass

The paths to various files in the examined source tree.

Source code in src/collect_unexported/defs.py
@dataclasses.dataclass(frozen=True)
class Source:
    """The paths to various files in the examined source tree."""

    top: pathlib.Path
    """The top-level source directory."""

    debian: pathlib.Path
    """The directory containing the Debian packaging files."""

    changelog: pathlib.Path
    """The path to the Debian changelog file."""

    package_name: str
    """The name of the Debian package being processed."""

    upstream_version: str
    """The upstream version of the Debian package being processed."""
changelog: pathlib.Path class-attribute

The path to the Debian changelog file.

debian: pathlib.Path class-attribute

The directory containing the Debian packaging files.

package_name: str class-attribute

The name of the Debian package being processed.

top: pathlib.Path class-attribute

The top-level source directory.

upstream_version: str class-attribute

The upstream version of the Debian package being processed.

collect_unexported.defs.Config dataclass

Bases: cfg_diag.Config

Runtime configuration for the collect-unexported tool.

Source code in src/collect_unexported/defs.py
@dataclasses.dataclass(frozen=True)
class Config(cfg_diag.Config):
    """Runtime configuration for the collect-unexported tool."""

    noop: bool
    """Do not copy the generated output file out of the temporary directory."""

    src: Source
    """Information about the Debian source package being processed."""
noop: bool class-attribute

Do not copy the generated output file out of the temporary directory.

src: Source class-attribute

Information about the Debian source package being processed.

Exceptions

collect_unexported.defs.CollectError dataclass

Bases: Exception

Base class for errors that occur during the collect-unexported operation.

Source code in src/collect_unexported/defs.py
@dataclasses.dataclass
class CollectError(Exception):
    """Base class for errors that occur during the collect-unexported operation."""

Examine the Debian source package structure

Constants

collect_unexported.examine.CHANGELOG_REL = pathlib.Path('debian/changelog') module-attribute

The relative path of the changelog file to look for.

Functions

collect_unexported.examine.find_source()

Look for a debian/changelog file higher and higher up.

Source code in src/collect_unexported/examine.py
def find_source() -> defs.Source:
    """Look for a `debian/changelog` file higher and higher up."""
    cwd: Final = pathlib.Path(".").resolve()
    try:
        changelog: Final = next(
            cfile
            for cfile in (cdir / CHANGELOG_REL for cdir in [cwd, *cwd.parents])
            if cfile.is_file()
        )
    except StopIteration as err:
        raise NoChangelogError(cwd=cwd, changelog=CHANGELOG_REL) from err

    try:
        chlog_data: Final = dchangelog.Changelog(file=changelog.open(mode="r", encoding="UTF-8"))
    except OSError as err:
        raise ChangelogReadError(changelog=changelog, err=err) from err
    except ValueError as err:
        raise ChangelogParseError(changelog=changelog, err=err) from err

    return defs.Source(
        top=changelog.parent.parent,
        debian=changelog.parent,
        changelog=changelog,
        package_name=chlog_data.package,
        upstream_version=chlog_data.version.upstream_version,
    )

collect_unexported.examine.get_repo_url(cfg)

Grab the repository URL out of the Debian upstream metadata file.

Source code in src/collect_unexported/examine.py
def get_repo_url(cfg: defs.Config) -> str:
    """Grab the repository URL out of the Debian upstream metadata file."""
    meta_path: Final = cfg.src.debian / "upstream/metadata"
    try:
        meta: Final = yaml.safe_load(meta_path.open(mode="rb"))
    except OSError as err:
        raise MetaReadError(meta_path=meta_path, err=err) from err
    except ValueError as err:
        raise MetaParseError(meta_path=meta_path, err=err) from err
    if not isinstance(meta, dict):
        raise MetaTypeError(meta_path=meta_path, meta=meta, yaml_path=".", expected="object")

    url_string: Final = meta.get("Repository")
    if url_string is None:
        raise MetaMissingError(meta_path=meta_path, meta=meta, yaml_path="Repository")
    if not isinstance(url_string, str):
        raise MetaTypeError(
            meta_path=meta_path, meta=meta, yaml_path="Repository", expected="string"
        )

    # Somewhat canonicalize the URL by making a round trip
    try:
        return uparse.urlparse(url_string).geturl()
    except ValueError as err:
        raise MetaBadRepositoryError(
            meta_path=meta_path, meta=meta, yaml_path="Repository", url=url_string, err=err
        ) from err

Exceptions

collect_unexported.examine.DebianChangelogError dataclass

Bases: defs.CollectError

Base class for errors related to parsing the debian/changelog file.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class DebianChangelogError(defs.CollectError):
    """Base class for errors related to parsing the debian/changelog file."""

    changelog: pathlib.Path
    """The relative path to the changelog file that we were looking for."""
changelog: pathlib.Path class-attribute

The relative path to the changelog file that we were looking for.

collect_unexported.examine.NoChangelogError dataclass

Bases: DebianChangelogError

Could not find the debian/changelog file.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class NoChangelogError(DebianChangelogError):
    """Could not find the debian/changelog file."""

    cwd: pathlib.Path
    """The directory where we started looking."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not find {self.changelog} in {self.cwd} or any of its parent directories"
cwd: pathlib.Path class-attribute

The directory where we started looking.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not find {self.changelog} in {self.cwd} or any of its parent directories"

collect_unexported.examine.ChangelogParseError dataclass

Bases: DebianChangelogError

Could not parse the debian/changelog file.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class ChangelogParseError(DebianChangelogError):
    """Could not parse the debian/changelog file."""

    err: Exception
    """The error that occurred."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not parse the {self.changelog} file: {self.err}"
err: Exception class-attribute

The error that occurred.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not parse the {self.changelog} file: {self.err}"

collect_unexported.examine.MetaError dataclass

Bases: defs.CollectError

Base class for errors related to parsing the debian/upstream/metadata file.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaError(defs.CollectError):
    """Base class for errors related to parsing the debian/upstream/metadata file."""

    meta_path: pathlib.Path
    """The path to the upstream metadata file."""
meta_path: pathlib.Path class-attribute

The path to the upstream metadata file.

collect_unexported.examine.MetaReadError dataclass

Bases: MetaError

Could not read the upstream metadata file.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaReadError(MetaError):
    """Could not read the upstream metadata file."""

    err: Exception
    """The error that occurred."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not read the {self.meta_path} file: {self.err}"
err: Exception class-attribute

The error that occurred.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not read the {self.meta_path} file: {self.err}"

collect_unexported.examine.MetaParseError dataclass

Bases: MetaError

Could not parse the upstream metadata file.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaParseError(MetaError):
    """Could not parse the upstream metadata file."""

    err: Exception
    """The error that occurred."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not parse the {self.meta_path} file: {self.err}"
err: Exception class-attribute

The error that occurred.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not parse the {self.meta_path} file: {self.err}"

collect_unexported.examine.MetaContentsError dataclass

Bases: MetaError

Base class for errors related to the parsed YAML contents.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaContentsError(MetaError):
    """Base class for errors related to the parsed YAML contents."""

    meta: Any
    """The parsed contents of the Debian upstream metadata file."""

    yaml_path: str
    """The "path" to the YAML element that is not as it ought to be."""
meta: Any class-attribute

The parsed contents of the Debian upstream metadata file.

yaml_path: str class-attribute

The "path" to the YAML element that is not as it ought to be.

collect_unexported.examine.MetaBadRepositoryError dataclass

Bases: MetaContentsError

The repository URL in the upstream metadata file could not be parsed.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaBadRepositoryError(MetaContentsError):
    """The repository URL in the upstream metadata file could not be parsed."""

    url: str
    """The URL string extracted from the metadata file."""

    err: Exception
    """The error that occurred while trying to parse the URL string."""

    def __str__(self) -> str:
        """Report the bad URL in a human-readable way."""
        return (
            f"The repository URL specified in '{self.yaml_path}' in "
            f"the {self.meta_path} file - '{self.url}' - could not be parsed: {self.err}"
        )
err: Exception class-attribute

The error that occurred while trying to parse the URL string.

url: str class-attribute

The URL string extracted from the metadata file.

__str__()

Report the bad URL in a human-readable way.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Report the bad URL in a human-readable way."""
    return (
        f"The repository URL specified in '{self.yaml_path}' in "
        f"the {self.meta_path} file - '{self.url}' - could not be parsed: {self.err}"
    )

collect_unexported.examine.MetaMissingError dataclass

Bases: MetaContentsError

There is no element at all.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaMissingError(MetaContentsError):
    """There is no element at all."""

    def __str__(self) -> str:
        """Report the missing element in a human-readable way."""
        return f"No '{self.yaml_path}' element in the {self.meta_path} file"
__str__()

Report the missing element in a human-readable way.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Report the missing element in a human-readable way."""
    return f"No '{self.yaml_path}' element in the {self.meta_path} file"

collect_unexported.examine.MetaTypeError dataclass

Bases: MetaContentsError

The element is not of the expected type.

Source code in src/collect_unexported/examine.py
@dataclasses.dataclass
class MetaTypeError(MetaContentsError):
    """The element is not of the expected type."""

    expected: str
    """The type we expected the element to be."""

    def __str__(self) -> str:
        """Report the missing element in a human-readable way."""
        return (
            f"The '{self.yaml_path}' element in the {self.meta_path} file is not "
            f"a YAML {self.expected}"
        )
expected: str class-attribute

The type we expected the element to be.

__str__()

Report the missing element in a human-readable way.

Source code in src/collect_unexported/examine.py
def __str__(self) -> str:
    """Report the missing element in a human-readable way."""
    return (
        f"The '{self.yaml_path}' element in the {self.meta_path} file is not "
        f"a YAML {self.expected}"
    )

Clone a Git repository, examine its files

Functions

collect_unexported.gitrepo.build_tarball(cfg, tempd, repo, to_include)

Collect the files to include again into an 'unexported' tarball.

Source code in src/collect_unexported/gitrepo.py
def build_tarball(
    cfg: defs.Config, tempd: pathlib.Path, repo: pathlib.Path, to_include: list[str]
) -> pathlib.Path:
    """Collect the files to include again into an 'unexported' tarball."""
    cfg.diag(lambda: f"Collecting {len(to_include)} path(s) into the 'unexported' tarball")
    udir: Final = tempd / "unexported"
    udir.mkdir(mode=0o755)
    for name in to_include:
        try:
            (repo / name).rename(udir / name)
        except OSError as err:
            raise RenameError(source=repo / name, target=udir / name, err=err) from err

    ufile: Final = (
        tempd / f"{cfg.src.package_name}_{cfg.src.upstream_version}.orig-{udir.name}.tar.gz"
    )
    subprocess.check_call(
        ["tar", "-cf", ufile.name.removesuffix(".gz"), "--", udir.name],
        cwd=tempd,
    )
    subprocess.check_call(["gzip", "-9", "-n", "--", ufile.name.removesuffix(".gz")], cwd=tempd)

    return ufile

collect_unexported.gitrepo.clone_repo(cfg, tempd, repo_url, to_include)

Clone the upstream Git repository, validate the list of files to include.

Source code in src/collect_unexported/gitrepo.py
def clone_repo(
    cfg: defs.Config, tempd: pathlib.Path, repo_url: str, to_include: list[str]
) -> pathlib.Path:
    """Clone the upstream Git repository, validate the list of files to include."""
    repo: Final = tempd / "repo"

    cfg.diag(lambda: f"Cloning {repo_url} into {repo}")
    try:
        subprocess.check_call(["git", "clone", "--", repo_url, repo])
    except (subprocess.CalledProcessError, OSError) as err:
        raise RepoCloneError(repo_url=repo_url, err=err) from err

    git_tag: Final = cfg.src.upstream_version
    cfg.diag(lambda: f"Checking out the '{git_tag}' tag")
    try:
        subprocess.check_call(["git", "checkout", f"refs/tags/{git_tag}"], cwd=repo)
    except (subprocess.CalledProcessError, OSError) as err:
        raise RepoTagError(repo_url=repo_url, tag=git_tag, err=err) from err

    ignored: Final = parse.get_ignored_files(repo / ".gitattributes")
    missing: Final = [name for name in to_include if name not in ignored]
    if missing:
        raise RepoNonIgnoredError(repo_url=repo_url, ignored=ignored, missing=missing)

    return repo

Exceptions

collect_unexported.gitrepo.RepoError dataclass

Bases: defs.CollectError

Base class for errors that occurred while handling the Git repository.

Source code in src/collect_unexported/gitrepo.py
@dataclasses.dataclass
class RepoError(defs.CollectError):
    """Base class for errors that occurred while handling the Git repository."""

    repo_url: str
    """The upstream repository URL."""

    err: Exception
    """The error that occurred while switching to the Git tag."""
err: Exception class-attribute

The error that occurred while switching to the Git tag.

repo_url: str class-attribute

The upstream repository URL.

collect_unexported.gitrepo.RepoCloneError dataclass

Bases: RepoError

Could not clone the Git repository.

Source code in src/collect_unexported/gitrepo.py
@dataclasses.dataclass
class RepoCloneError(RepoError):
    """Could not clone the Git repository."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not clone the {self.repo_url} repository: {self.err}"
__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/gitrepo.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not clone the {self.repo_url} repository: {self.err}"

collect_unexported.gitrepo.RepoTagError dataclass

Bases: RepoError

Could not switch to the Git tag corresponding to the upstream version.

Source code in src/collect_unexported/gitrepo.py
@dataclasses.dataclass
class RepoTagError(RepoError):
    """Could not switch to the Git tag corresponding to the upstream version."""

    tag: str
    """The Git tag that we tried to switch to."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not switch to the '{self.tag}' Git tag: {self.err}"
tag: str class-attribute

The Git tag that we tried to switch to.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/gitrepo.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not switch to the '{self.tag}' Git tag: {self.err}"

collect_unexported.gitrepo.RepoNonIgnoredError dataclass

Bases: defs.CollectError

Some of the files to repack are not actually ignored.

Source code in src/collect_unexported/gitrepo.py
@dataclasses.dataclass
class RepoNonIgnoredError(defs.CollectError):
    """Some of the files to repack are not actually ignored."""

    repo_url: str
    """The upstream repository URL."""

    ignored: list[str]
    """The files that were specified as ignored in the .gitattributes file."""

    missing: list[str]
    """The files that were specified for inclusion, but are not ignored."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Non-ignored files specified for inclusion: {self.missing!r}"
ignored: list[str] class-attribute

The files that were specified as ignored in the .gitattributes file.

missing: list[str] class-attribute

The files that were specified for inclusion, but are not ignored.

repo_url: str class-attribute

The upstream repository URL.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/gitrepo.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Non-ignored files specified for inclusion: {self.missing!r}"

collect_unexported.gitrepo.RenameError dataclass

Bases: defs.CollectError

Could not rename a file.

Source code in src/collect_unexported/gitrepo.py
@dataclasses.dataclass
class RenameError(defs.CollectError):
    """Could not rename a file."""

    source: pathlib.Path
    """The file we tried to rename."""

    target: pathlib.Path
    """What we tried to rename it to."""

    err: Exception
    """The error we encountered."""

    def __str__(self) -> str:
        """Report the failure in a human-readable manner."""
        return f"Could not rename {self.source} to {self.target}: {self.err}"
err: Exception class-attribute

The error we encountered.

source: pathlib.Path class-attribute

The file we tried to rename.

target: pathlib.Path class-attribute

What we tried to rename it to.

__str__()

Report the failure in a human-readable manner.

Source code in src/collect_unexported/gitrepo.py
def __str__(self) -> str:
    """Report the failure in a human-readable manner."""
    return f"Could not rename {self.source} to {self.target}: {self.err}"

Parse some configuration files

Functions

collect_unexported.parse.parse_config_file(cfg, config_file)

Parse the list of paths to include from the TOML config file.

Source code in src/collect_unexported/parse.py
def parse_config_file(cfg: defs.Config, config_file: pathlib.Path) -> list[str]:
    """Parse the list of paths to include from the TOML config file."""
    cfg.diag(lambda: f"Reading the collect-unexported configuration from {config_file}")
    try:
        cdata: Final = tomllib.load(config_file.open(mode="rb"))
    except OSError as err:
        raise ConfigReadError(config_file=config_file, err=err) from err
    except ValueError as err:
        raise ConfigParseError(config_file=config_file, err=err) from err
    if (
        not isinstance(cdata, dict)
        or not isinstance(cdata.get("format"), dict)
        or not isinstance(cdata["format"].get("version"), dict)
        or not isinstance(cdata["format"]["version"].get("major"), int)
        or cdata["format"]["version"]["major"] != 0
    ):
        raise ConfigTypeError(
            config_file=config_file, contents=cdata, path="format.version.major", expected="0"
        )

    files: Final = cdata.get("files")
    if not isinstance(files, dict):
        raise ConfigMissingError(config_file=config_file, contents=cdata, path="files")
    lines: Final = files.get("include")
    if lines is None:
        raise ConfigMissingError(config_file=config_file, contents=cdata, path="files.include")
    if not isinstance(lines, list) or any(not isinstance(item, str) for item in lines):
        raise ConfigTypeError(
            config_file=config_file,
            contents=cdata,
            path="files.include",
            expected="a list of strings",
        )
    return lines

collect_unexported.parse.get_ignored_files(attr_path)

Parse a .gitattributes file, return the ignored patterns.

Source code in src/collect_unexported/parse.py
def get_ignored_files(attr_path: pathlib.Path) -> list[str]:
    """Parse a .gitattributes file, return the ignored patterns."""
    try:
        lines: Final = [
            line
            for line in (
                raw.strip() for raw in attr_path.read_text(encoding="ISO-8859-15").splitlines()
            )
            if line and not line.startswith("#")
        ]
    except OSError as err:
        raise AttributesReadError(attr_path=attr_path, err=err) from err
    except ValueError as err:
        raise AttributesParseError(attr_path=attr_path, err=err) from err
    return [
        relpath
        for relpath in (_is_ignored(parts) for parts in (line.split() for line in lines))
        if relpath is not None
    ]

Exceptions

collect_unexported.parse.ConfigError dataclass

Bases: defs.CollectError

Base class for errors related to configuration file parsing.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class ConfigError(defs.CollectError):
    """Base class for errors related to configuration file parsing."""

    config_file: pathlib.Path
    """The path to the config file we tried to process."""
config_file: pathlib.Path class-attribute

The path to the config file we tried to process.

collect_unexported.parse.ConfigContentsError dataclass

Bases: ConfigError

Something in the config file was not as we expected it to be.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class ConfigContentsError(ConfigError):
    """Something in the config file was not as we expected it to be."""

    contents: Any
    """The contents of the config file."""

    path: str
    """The path to the element that was not as it should be."""
contents: Any class-attribute

The contents of the config file.

path: str class-attribute

The path to the element that was not as it should be.

collect_unexported.parse.ConfigReadError dataclass

Bases: ConfigError

An error that occurred while reading the config file.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class ConfigReadError(ConfigError):
    """An error that occurred while reading the config file."""

    err: Exception
    """The error that we encountered."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not read the {self.config_file} file: {self.err}"
err: Exception class-attribute

The error that we encountered.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/parse.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not read the {self.config_file} file: {self.err}"

collect_unexported.parse.ConfigMissingError dataclass

Bases: ConfigContentsError

An element was not found at all in the config file.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class ConfigMissingError(ConfigContentsError):
    """An element was not found at all in the config file."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"No '{self.path}' element in the {self.config_file} file"
__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/parse.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"No '{self.path}' element in the {self.config_file} file"

collect_unexported.parse.ConfigParseError dataclass

Bases: ConfigError

An error that occurred while parsing the config file.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class ConfigParseError(ConfigError):
    """An error that occurred while parsing the config file."""

    err: Exception
    """The error that we encountered."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not parse the {self.config_file} file: {self.err}"
err: Exception class-attribute

The error that we encountered.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/parse.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not parse the {self.config_file} file: {self.err}"

collect_unexported.parse.ConfigTypeError dataclass

Bases: ConfigContentsError

An element was not of the correct type.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class ConfigTypeError(ConfigContentsError):
    """An element was not of the correct type."""

    expected: str
    """What the element should have been."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"The '{self.path}' element in the {self.config_file} file is not {self.expected}"
expected: str class-attribute

What the element should have been.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/parse.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"The '{self.path}' element in the {self.config_file} file is not {self.expected}"

collect_unexported.parse.AttributesError dataclass

Bases: defs.CollectError

Base class for errors related to .gitattributes parsing.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class AttributesError(defs.CollectError):
    """Base class for errors related to .gitattributes parsing."""

    attr_path: pathlib.Path
    """The path to the .gitattributes file we tried to process."""
attr_path: pathlib.Path class-attribute

The path to the .gitattributes file we tried to process.

collect_unexported.parse.AttributesReadError dataclass

Bases: AttributesError

An error that occurred while reading the .gitattributes file.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class AttributesReadError(AttributesError):
    """An error that occurred while reading the .gitattributes file."""

    err: Exception
    """The error that we encountered."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not read the {self.attr_path} file: {self.err}"
err: Exception class-attribute

The error that we encountered.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/parse.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not read the {self.attr_path} file: {self.err}"

collect_unexported.parse.AttributesParseError dataclass

Bases: AttributesError

An error that occurred while parsing the .gitattributes file.

Source code in src/collect_unexported/parse.py
@dataclasses.dataclass
class AttributesParseError(AttributesError):
    """An error that occurred while parsing the .gitattributes file."""

    err: Exception
    """The error that we encountered."""

    def __str__(self) -> str:
        """Complain in a human-readable manner."""
        return f"Could not parse the {self.attr_path} file: {self.err}"
err: Exception class-attribute

The error that we encountered.

__str__()

Complain in a human-readable manner.

Source code in src/collect_unexported/parse.py
def __str__(self) -> str:
    """Complain in a human-readable manner."""
    return f"Could not parse the {self.attr_path} file: {self.err}"