Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

エラー処理

Single-file analysis raises exceptions for file and analysis failures. Batch analysis returns those failures as AnalysisFailure values and continues. Invalid caller arguments (ValueError for a bad value, TypeError for the wrong type) still raise and abort the batch. This page lists every exception each entry point can raise and the one value type a batch returns instead.

def run(
    fixtures: Path,
    *,
    missing_path: Path,
) -> dict[str, Any]:
    """Trigger each error path and return a small report.

    ``fixtures`` is a directory containing at least ``hello.rs``;
    ``missing_path`` must NOT exist on disk.
    """
    report: dict[str, Any] = {
        "file_not_found": False,
        "unsupported": False,
        "batch_errors": 0,
    }

    # 1. 存在しないパスに対する analyze() は型付きの OSError サブクラスを送出します。
    try:
        bca.analyze(missing_path)
    except FileNotFoundError as err:
        report["file_not_found"] = True
        print(f"file_not_found: errno={err.errno} filename={err.filename}")

    # 2. 未知の拡張子に対する analyze() は
    #    UnsupportedLanguageError(それ自体が ValueError のサブクラス)を送出します。
    #    書き込みを try/finally の内側に置いているため、将来 analyse 呼び出しの
    #    前に 2 つ目の変更が加わってもクリーンアップされます。
    unknown = fixtures / "hello.unknown_extension"
    try:
        unknown.write_text("noop", encoding="utf-8")
        bca.analyze(unknown)
    except bca.UnsupportedLanguageError as err:
        report["unsupported"] = True
        print(f"unsupported_language: {err}")
    finally:
        unknown.unlink(missing_ok=True)

    # 3. analyze_batch() は AnalysisFailure を返し、ファイル単位で例外を送出することはありません。
    paths = [fixtures / "hello.rs", missing_path]
    for slot in bca.analyze_batch(paths):
        if isinstance(slot, bca.AnalysisFailure):
            report["batch_errors"] += 1
            print(f"batch_error: ({slot.error_kind}) {slot.error}")

    return report

単一ファイルの例外

bca.analyze と bca.analyze_source は次を送出します:

例外継承元発生条件
bca.UnsupportedLanguageErrorValueError未知の拡張子で、シバン / emacs モードにも一致しない
bca.ParseErrorValueErrortree-sitter rejected the source
ValueError(直接)(builtin)allow_lossy_path=False(デフォルト)での非 UTF-8 パス
OSError とそのサブクラス(builtin)std::fs::read が失敗した

analyze が送出する OSError は、errno に基づいて正規のサブクラスへ振り分けられます:

import big_code_analysis as bca

path = "src/example.rs"

try:
    bca.analyze(path)
except FileNotFoundError as err:
    print("missing:", err.errno, err.filename)
except PermissionError as err:
    print("denied:", err.errno, err.filename)
except IsADirectoryError as err:
    print("directory:", err.errno, err.filename)

各分岐は元となる errno に基づいて振り分けられます:

例外典型的な err.errno(Linux)発生条件
FileNotFoundError2 (ENOENT)パスが存在しない。
PermissionError13 (EACCES)呼び出しユーザーに読み取り権限が与えられていない。
IsADirectoryError21 (EISDIR)パスがディレクトリを指している。

On Windows the Win32 code is on err.winerror, and err.errno holds the POSIX code CPython maps it to, so the same branches apply: a missing file or a missing parent directory is a FileNotFoundError. Windows reports a directory read as access denied, so a directory raises PermissionError there rather than IsADirectoryError.

ファミリー全体を捕捉して err.errno / err.filename を自分で調べたい場合は、except OSError を使ってください。

UnsupportedLanguageError と ParseError はどちらも ValueError のサブクラスなので、単一の except ValueError で両方を捕捉できます。区別したい場合は型付きの捕捉を優先してください。

バッチのエラー

bca.analyze_batch は例外を送出する代わりに bca.AnalysisFailure 値を返すため、1 つの不正なファイルがバッチ全体を壊すことはありません。

for slot in bca.analyze_batch(paths):
    if isinstance(slot, bca.AnalysisFailure):
        log.warning("%s (%s): %s", slot.path, slot.error_kind, slot.error)
    else:
        process(slot)

error_kind は閉じた Literal です。

  • "UnsupportedLanguage": extension and shebang / emacs-mode resolution both came up empty.
  • "ParseError": tree-sitter rejected the input, or (rarely) a Rust-side JSON serialisation of the result failed. The serialisation case is prefixed with internal: serialization error: in the error string; check for the prefix when the distinction matters, since re-reading the file does not recover from a serialisation failure.
  • "IoError": a filesystem read or path-encoding failure. The closed taxonomy folds non-UTF-8 paths in here rather than adding a distinct fourth value.

For "IoError" instances the underlying OS errno is preserved in the error string via Rust’s default formatting ("<msg> (os error <N>)" on Unix). Parse it with a regex if you need it for retry classification:

import re

match = re.search(r"\(os error (\d+)\)$", slot.error)
errno = int(match.group(1)) if match else None

If you need typed OSError subclasses, call bca.analyze per file instead of analyze_batch; single-file analyze raises FileNotFoundError / PermissionError / IsADirectoryError directly.

バッチにおけるプログラマーエラー

analyze_batch も、呼び出し側のバグに対しては例外を送出します:

  • paths がイテラブルでない場合、または要素が str / os.PathLike[str] でない場合は TypeError です。これは呼び出し全体を中断し、不正な要素より前に計算された結果はすべて破棄されます。
  • metrics= が明示的に空のシーケンスであるか、未知の名前を含む場合は ValueError です。検証は入力イテラブルの __iter__ より「前に」実行されるため、この送出経路ではジェネレーターの副作用(および部分的な yield)は温存されます。

変更履歴(VCS)の例外

big_code_analysis.vcs の各関数は、bca.VcsError(それ自体が ValueError)を頂点とする型付き階層を送出するため、既存の except ValueError(または except bca.VcsError)ですべての VCS の失敗を捕捉できます(#624)。analyze(..., vcs=True) キーワード引数も同じオプション解析エラーを共有します。

例外継承元発生条件
bca.NotARepositoryErrorbca.VcsErrorrepo_path が git 作業ツリーの中にない
bca.InvalidRevisionErrorbca.VcsErrorreference / commit を解決できなかった
bca.InvalidDiffErrorbca.VcsErrorvcs.score_diff に渡された diff が不正である
bca.VcsEnvironmentErrorbca.VcsError履歴走査、差分計算、.mailmap、blame、またはキャッシュ I/O が失敗した
bca.VcsError(直接)ValueError不正なオプション値(ウィンドウ / タイムスタンプ / 計算式 / ファイルタイプの範囲 / バスファクターのしきい値 / ボットパターン / トレンドのポイント数)。メッセージが問題の値を示します

NotARepositoryError は「リポジトリではないのでこのディレクトリをスキップする」ために分岐すべきバリアントです。ベースの VcsError は不正なオプションに対して直接送出され、メッセージが問題の値を示します。一方、名前付きのサブクラスは入力の失敗(存在しないリビジョン、不正な diff)を扱います。VcsEnvironmentError は環境 / バックエンドの区分で、同じ失敗に対して web クレートが返す 500(400 ではなく)レスポンスに対応します。

import big_code_analysis as bca
from big_code_analysis import vcs

try:
    report = vcs.rank("path/to/repo", top=20)
except bca.NotARepositoryError:
    print("not a git repository, skipping")
except bca.VcsError as err:
    # 不正なウィンドウ、計算式、ファイルタイプの範囲など。
    print("bad VCS option:", err)

analyze(..., vcs=True) is the exception to the NotARepositoryError rule: a file outside any repository yields no vcs block rather than raising, so only the option-parsing VcsError reaches the caller from that path.

ログ出力のレシピ

A small logging helper for batch output keeps successes and failures aligned without bespoke formatting:

import logging
import big_code_analysis as bca

log = logging.getLogger(__name__)

def report(paths: list[str]) -> None:
    # skip_generated=False は結果リストのインデックスを `paths` と
    # 揃えたままにします。デフォルトの True では、生成ファイルはスロットを
    # 持たず、zip が黙ってずれてしまいます。
    for path, slot in zip(paths, bca.analyze_batch(paths, skip_generated=False)):
        if isinstance(slot, bca.AnalysisFailure):
            log.warning(
                "skip %s (%s): %s", path, slot.error_kind, slot.error
            )
        elif slot is None:
            # Nothing to parse: three bytes or fewer, a UTF-16 BOM, or a
            # binary leading window. The slot is held open so the zip
            # above stays aligned (#1238); it is not a failure.
            log.info("skip %s: empty or binary", path)
        else:
            log.info(
                "ok %s sloc=%s", path,
                slot["metrics"]["loc"]["sloc"],
            )

関連項目

  • Batch processing: the never-raise contract that routes per-file failures into AnalysisFailure slots.
  • Async patterns: asyncio.gather(..., return_exceptions=True) is the async-side equivalent of the batch contract; per-task exceptions land in the result list instead of cancelling the whole gather.
  • Quick start: the single-file analyze path that raises typed OSError subclasses.