SourceObserver
メディアタイプについて安価なページ単位の証拠を生成するポート。
英語版が原文です。
プロトコル:
SourceObserver.observe(content: bytes, media_type: str) -> tuple[PageEvidence, ...]SourceObserver.sniff(content: bytes) -> str | None(任意)SourceObserverProvider.source_observers() -> tuple[SourceObserver, ...]
SourceObserver は、高コストな処理が走る前に、ロード済みソースについて安価な構造的証拠を取るための境界です。答えるのは一つの問いです。ルーティングポリシーが読む語彙で、各ページに何があるか — テキストレイヤーはあるか、画像はあるか、ページは本当に空か。
任意で、もう一つ手前の問いにも答えます。sniff はバイト列そのものからメディアタイプを認識します。これによって、アップロードのラベルを取り違えたクライアントよりも、インストール済みディストリビューションの言い分が信じられます。つまり一つのオブザーバーが、一つのソースについて二つの問いに答えます — それが何であるか、そして中身がどう見えるか。この二つは同じディストリビューションの知識であり、だからこそ前者に別のポートは存在しません。
オブザーバーは、ページ証拠を返すことで自分が理解するメディアタイプを主張し、それ以外のすべてのタイプについては何も返さないことで担当を否認します。空のタプルは「自分のものではない」を意味し、ディスパッチは次のオブザーバーへ進みます。正しいタイプなのに読み取れないバイト列は、まったく別の答えです。それは raise される InvalidSourceError であり、同じバイト列を誤読しかねないオブザーバーへ流れ落ちるのではなく、探索を終わらせます。
オブザーバーを宣言するディストリビューションをインストールすることが、メディアタイプを計画可能にし、そのバイト列を認識可能にすることです。indx が観察するすべての形式もこの経路でルーターに到達します。indx-observer-pdf、indx-observer-image、indx-observer-office、indx-observer-text、indx-observer-email、indx-observer-dxf から、サードパーティが使うのと同じエントリーポイントグループを通じてです。indx がかつて抱えていた三つのマジックバイト接頭辞も、オブザーバーと一緒にそちらへ移りました。ディスパッチは一つであり、拡張の経路が乖離していくためのビルトイン(builtin)の経路は存在しません。
機能は以前から、任意の media_types を宣言して機能スナップショットに到達できました。それでも計画はそのソースを拒否しました。観察が、ルーターに書き込まれた PDF、PNG、JPEG の上の閉じたディスパッチだったからです。インストールされたディストリビューションは、indx がすでに観察するフォーマットの読み取り機能は追加できても、フォーマットそのものは追加できませんでした。
ファイルタイプではなく証拠の上でルーティングすることが、ラダーを汎用にしています。そしてその証拠は、ファーストパーティのコード以外に出どころがありませんでした。今はこのポートが出どころです。
sniff は一階層下の門を閉じるもので、それが取り除く非対称こそが導入の理由でした。indx-source は自分で書き留めた三つのマジックバイト接頭辞だけを認識していたため、ファーストパーティのタイプは Content-Type を偽ったクライアントよりも信じられる一方、サードパーティのタイプはその同じ偽りか、連鎖の中で最も弱い信号であるファイル名からしか認識されませんでした。フォーマットを認識することは、バイトを数えるパッケージではなく、それを解析するものが持つべき知識です。
入力はソースのバイト列と、検出されたメディアタイプです — PageReader.read と SignatureDetector.detect が受け取るのと同じ組です。したがって、オブザーバーのディストリビューションは indx-interfaces だけに依存し、ローダーはバイト列の取得方法を自由に変えられます。
出力は
PageEvidence
のタプルで、1 ページにつき一つ、1 始まりで順序どおりです。ページ証拠は TextLayerState、自由文字列のシグナル(ファーストパーティのオブザーバーが発するのは font、image、empty)、任意の RegionEvidence を運びます。
これは意図的に、事前観察コンテキストの全体ではありません。source_digest はローダーが読んだものからルーターが発行し、page_count は証拠の長さから導出されます。したがってオブザーバーは、計画が束縛される同一性を偽造することも、何も生成していないページ数を主張することもできません。
indx-observer-office は、この「内容全体」という規則が書かれた理由そのものです。.docx、.xlsx、.pptx はいずれも PK\x03\x04 で始まり、それらを分けるのは zip のメンバー一覧だけで、それはファイル末尾の中央ディレクトリにあります。Office パッケージではない zip には None を返します — 宣言されたタイプとファイル名がまだ下で待っているからです。一方、別種の Office ファイルであるパッケージは raise します。それはこのオブザーバーが持ち主であるバイト列であり、呼び出し元のラベルのほうが誤っているからです。スライド、セクション、ワークシートはいずれも文字を格納しているので、ページを列挙するパート以外は何も開かずにテキスト層は USABLE です — 例外はチャートシートで、セルではなく図を保持するワークブックのタブです。ワークシートと見分けるのはシートパートのルートタグであり、それより安価な手段はないため、ワークブックのシートは一度ずつ開かれ、チャートシートは MISSING として報告されます。計画は最初から無料の段を飛ばして送り出し、読み取るものが何もなかったことをリーダーが実行時に発見するのを待ちません。
sniff は内容全体を受け取り、メディアタイプを返すか、認識しないバイト列には None を返します — 「自分のものではない」という同じ規則を、別の空値で述べたものです。先頭の一部ではなく全体なのは、固定長の先頭は、誰かが後から加える最長の接頭辞と歩調を合わせ続けねばならない二つ目の長さになるからであり、そして zip コンテナには端的に誤りだからです(メンバー名は末尾にあります)。接頭辞を読むのが安価な場合であり、それは安価なままです。それ以上のことは observe と同じ予算を引き継ぎます。
SourceObserverProvider は、EmbeddingSpaceProvider と同じように任意です。ページを読み取り、何も観察しないプロバイダーは決してこれを実装しません。埋め込み空間と違い、オブザーバーは ID を名乗らず、機能記述子にも加わりません。そのため、他のディストリビューションのものと衝突することも、機能スナップショットを動かすこともありません — インストールすることは計画できるものを変え、スナップショットは実行できるものを記録します。
- 主張しないメディアタイプには何も返さず、ディスパッチが続行できるようにする。
- 形式のバイト列がその形式を名乗らないなら、
sniffをそもそも宣言しない。これはgetattrの既定値で読まれるため、宣言しないことは省略ではなく答えです。ファーストパーティのオブザーバーも二つそうしています。indx-observer-textは、.txtと.csvと.tsvが内側の区切り文字だけが違う同じ文字列だからであり、indx-observer-emailは、メッセージが送信エージェントの書いた最初のヘッダーから始まるからです。そこで書ける署名 —— 「UTF-8 としてデコードできるか」「1 行目がName: valueに見えるか」 —— は、本当に正しい申告済みタイプを追い越して JSON も XML も HTML も生の HTTP レスポンスも自分のものだと主張します。申告されたタイプとファイル名こそが正直な手がかりであり、mimetypesはそれらの拡張子をすでにすべて対応付けています。 - 認識しないバイト列には
sniffからNoneを返し、自分が観察もするタイプだけを認識する。その後に誰も見られないタイプは、呼び出し元にとって415の代わりに422を買うだけです。 - 主張するタイプなのに読み取れないバイト列には
InvalidSourceErrorを raise し、探索を止める。 - 事前観察(preflight)の予算内に留まる。安価、ローカル、決定的で、OCR、モデル、レンダリング、ネットワークを使わない。構造を読むことが意図されたコストであり、内容を読むことは構造が尽きた場所でのみ許容される。
- ページ番号は 1 から始め、
ScopeRef.pageとBlock.indexに合わせる。 - シグナルは
indx_interfaces.preflightの共有語彙から発する。ルーティングポリシーがその文字列にマッチするからです。ポリシーが読まないシグナルは、誤りではなく不活性です。新しいシグナルに意味を持たせることは、POLICY_VERSIONの更新を伴うファーストパーティのポリシー変更です。 - モジュールスコープを安価に保つ。検出はスナップショット構築中にすべてのプロバイダーモジュールをインポートするため、重いインポートは
observe()の内側に置く。
三つのメンバーは意図的にプロトコルの外にあります。プロトコルに宣言されたメンバーは、isinstance と型チェッカーの両方が要求するメンバーになります。そして、この三つはいずれも、オブザーバーが動作するために必須なものではありません。
builtin = True はファーストパーティのオブザーバーが設定するもので、レジストリはインストール済みオブザーバーをそれらより前に並べます。これを主張しても優先度を失うだけで、得ることは決してできません。だからこそ何も検証しないのです。一つのタイプを主張する二つのインストール済みオブザーバーは、検出順で解決されます。
media_types はこのオブザーバーが何を観察するかを宣言し、機能スナップショットが resolvable.observable_media_types で報告するものです。これは広告であってゲートではありません。ディスパッチは依然として尋ね、依然として空の答えを「自分のものではない」と読みます。一つの問いに対する二つの真実の出どころは、まさにゲートが作り出すものだからです。sniff が返してよいものを制限するわけでもありません — 広告していないバイト列を認識するオブザーバーは止められず、ただ一覧に載らないだけです。
sniff は、オブザーバーがメディアタイプ検出に寄与するものです。一つのフォーマットを認識する二つのオブザーバーは同じ順序で解決され、最初の答えが静かに勝ちます。オブザーバーは衝突すべき ID を名乗らないからです。sniff からの raise は致命的ではなくログに残して読み飛ばされ、これは observe からの raise とは逆です。あちらは「自分のものであり、読めない」を意味し、次のオブザーバーに同じバイト列を誤読させるくらいなら止めたほうがよい。こちらは宣言されたタイプとファイル名がまだ下で待っています。三つのいずれも何も検証しません。誤った builtin は優先度を、載っていない media_types は可視性を、壊れた sniff は続いていく連鎖の中の一手番を失うだけです。
三つとも空のデフォルトで読まれるため、いずれも存在する前に書かれたオブザーバーもそのまま動きます — 単に広告されず、インストール済みとしてソートされ、検出には何も寄与しないだけです。
システム内の位置
Section titled “システム内の位置”Router.plan は、インストール済みの SourceLoader を通じてソースをロードします — そしてそのロードの最中に、indx-source は同じレジストリのオブザーバーに、宣言された Content-Type を信じる前に、バイト列が何であるかを尋ねます。次にインストール済みのどの機能も読み取れないメディアタイプを 415 で拒否し、同じオブザーバーでソースを観察します。何も観察しないタイプは、コード source_unreadable の 422 です。これらの境界はすべてインストール依存です。同じソースが、あるインストールでは拒否され、別のインストールでは計画されることがあり、素の pip install indx はバイト列から何一つ認識しません。
観察は、計画がインストール済み実装に到達する二つ目の場所です。リクエストごとのオプトインである signature_detection と違い、観察はすべての計画で走ります — そして同じ予算に縛られます。その予算が両方のポートに明記されているのはそのためです。