エラー処理
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.UnsupportedLanguageError | ValueError | 未知の拡張子で、シバン / emacs モードにも一致しない |
bca.ParseError | ValueError | tree-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) | 発生条件 |
|---|---|---|
FileNotFoundError | 2 (ENOENT) | パスが存在しない。 |
PermissionError | 13 (EACCES) | 呼び出しユーザーに読み取り権限が与えられていない。 |
IsADirectoryError | 21 (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 withinternal: serialization error:in theerrorstring; 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.NotARepositoryError | bca.VcsError | repo_path が git 作業ツリーの中にない |
bca.InvalidRevisionError | bca.VcsError | reference / commit を解決できなかった |
bca.InvalidDiffError | bca.VcsError | vcs.score_diff に渡された diff が不正である |
bca.VcsEnvironmentError | bca.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
AnalysisFailureslots. - 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
analyzepath that raises typedOSErrorsubclasses.