Sphinx 拡張のエラー出力方法まとめ
Sphinx 拡張ではエラーを表示したくなる場面はいくつかある。
設定値が誤っている場合や、環境の設定不足、記述の誤りや通信エラーなど。
そんなときに Sphinx/docutils フレームワークを使ってエラーを出力していくのだが、
場面や用途によってエラーの出力方法が異なる。
今回はそんな Sphinx 拡張のエラー出力方法をまとめたいと思う。
というか、最近 sphinxcontrib-blockdiag を書き直していて、何種類も使い分けている理由が分からずに混乱していたので、
自分のためにまとめ直すことにした。
ディレクティブ内でエラーを出力する
DirectiveError を投げる
docutils.parsers.rst に定義されている DirectiveError 例外を投げることでエラーが出力される。
また、ショートカットとして Directive クラスには debug(), info(), warning(), error(), severe() というメソッドが用意されていて、
エラーメッセージを引数にこれを呼び出すと DirectiveError のインスタンスを得ることができる。
これを使ってエラーを出力する場合は次のようになる。
class MyDirective(Directive): def run(self): if not self.arguments: raise self.error('no arguments') # 通常の処理...
Reporter を呼び出す
DirectiveError は例外処理を用いるので、ディレクティブの処理を続けることはできない。
これは致命的な問題があった場合はやむを得ないが、軽微な問題を報告しつつ処理を先に進めたいということもある。
その場合は Reporter を使う方法がある。
Reporter には debug(), info(), warning(), error(), severe() のメソッドがあり(ちなみに実装は docutils/utils/__init__.py にある)、
各メソッドは system_message ノードを返してくるので、
ディレクティブの実行結果にこれを含めることでエラーを通知する。
Reporter クラスのインスタンスは、ディレクティブからは self.state.document.reporter から得ることができる。
これを使ってエラーを出力する場合は次のようになる。
class MyDirective(Directive): def run(self): paragraph = nodes.paragraph() if not os.path.exists(self.argument[0]): msg = "file not found: %s" % self.argument[0] node += self.state.document.reporter.warning(msg, line=self.lineno) else: # ... return [node]
reporter が生成した system_message ノードを単に return する実装もよく見かけるが、
うまく利用するとエラーを報告しつつ処理を進めることができるのでうまく利用したい。
もちろん、system_message ノードは単純な docutils のノードであるため、
自分で nodes.system_message() として生成することも可能である。
ロール内でエラーを出力する
Reporter を呼び出す
ロール内でも Reporter を利用することができる。
Reporter オブジェクトには引数 `inliner` の reporter 属性からアクセスできる。
また、ロール内でエラーが発生した場合は problematic ノードを返却する必要があるため、
Reporter とあわせて Inliner#problematic() を呼び出す必要がある。
def my_role(name, rawtext, text, lineno, inliner, options={}, content=[]): try: # 通常の処理 except Exception as exc: msg = inliner.reporter.error('critical error: %r' % exc) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg]
軽微なエラーの場合は problematic ノードを返すのではなく、返り値のノードに system_message ノードを混ぜ込むとよい。
Writer 内でエラーを出力する
docutils の場合
system_message ノードを作って self.visit_system_message() メソッドに投げ込むとよい(みたい)。
HTML writer では self.document.reporter から Reporter にアクセスできるが、
LaTeX writer では self.document そのものがなく、代わりに self.warn() や self.error() に reporter のメソッドが登録されているなど、
統一的な方法は用意されていない模様*1。
sphinx の場合 (vistor 関数)
Builder クラスの warn(), info() を使う。
Builder オブジェクトには引数 `self` の builder 属性からアクセスできる。
def html_visit_my_node(self, node): try: # 通常の処理 except Exception as exc: self.builder.warn('Error happen: %r" % exc) raise nodes.SkipNode
warn() メソッドはエラーを画面に表示するだけなので、処理を終了する場合は SkipNode を投げる必要がある。
軽微なエラーなどで処理を継続する場合はそのまま進めて良い。
Sphinx の場合 (その他のイベントハンドラ)
Sphinx クラスの warn(), info() を使う。
Sphinx オブジェクトはハンドラの最初の引数として渡される。
def on_doctree_resolved(self, doctree, docname): try: # 通常の処理 except Exception as exc: self.warn('Error happen: %r' % exc)
Builder#warn() メソッドと同じく*2 Sphinx#warn() メソッドはエラーを画面に表示するだけなので、
処理を終了する場合は自分で return などを呼び関数から抜け出す必要がある。
まとめ
理由はなんとなくわかるんだけども、やり方がバラバラでつらいですね。諦めましょう。