コンテンツにスキップ

データ源を追加する

新しいミッションや天体のデータをspn.<name>・view.<name>・spn.load("<name>...")から使えるようにする手順です。 最も小さな実装例はsrc/sopran/missions/omni/(OMNI)なので、並べて読むと分かりやすくなります。

1. 登録する

データ源はそれぞれのパッケージの__init__.pyで登録します。coreはミッションをimportしないため、登録はミッション側で行います。

# src/sopran/missions/<name>/__init__.py
from sopran.core.sources import register_source

register_source("<name>", "sopran.missions.<name>.mission:<Mission>.from_view")

第2引数は"モジュール:属性のパス"で、最初に使われたときにimportします。 登録されたファクトリーはViewを受け取り、その期間・ダウンロード方針・Storeに結び付いた実体を返します。

class Mission:
    def __init__(self, *, store: Store | None = None, download: DownloadMode | None = None,
                 view: View | None = None) -> None:
        self.store = store or Store()
        self.download = resolve_download_mode(download)   # 方針の解決は一か所に任せる
        self.view = view

    @classmethod
    def from_view(cls, view: View) -> "Mission":
        return cls(store=view.project.store, download=view.context.download, view=view)

src/sopran/__init__.pyでこのパッケージをimportすると登録が済み、spn.<name>・view.<name>・case.<name>・spn.load("<name>.…")がすべて同じ実体を使うようになります。

2. スキーマを書く

変数ごとにVariableSchemaを書き、機器ごとにInstrumentSchemaにまとめます。単位・次元・座標系はここが正本です。

DENSITY = VariableSchema(name="density", dims=("time",), units="cm^-3", description="Proton density.")
SCHEMA = InstrumentSchema(mission="<name>", instrument="<instrument>", variables=(DENSITY,))

何桁にもまたがる量(フラックスやパワースペクトル密度など)にはlog_scale=True、対数軸で描く座標にはlog_dims=("energy",)を付けます。 配列とエンドポイントの作図は、この指定から既定の対数表示を決めます。

スキーマをリファレンスに載せるには、sopran.schema_docs.builtin_schemas()に加えてから python -m sopran.schema_docsでページを作り直します。

3. エンドポイントを作る

エンドポイントは、属性として取り出しただけではファイルを読まないようにします。少なくとも次を用意します。

メソッド 返り値
load(time=None, *, download=None, ...) SopranArray。timeを省いたらview.timeを使い、どちらも無ければ例を添えた例外にする
plot(time=None, **kwargs) load(...).plot(...)
line()、lines()、spectrogram()など PlotStack用のパネル
info()、schema()、guide()、example() 説明と定義
plan(time) 読み込む元ファイルやデータセットID

元ファイルはstore.raw_path("<name>", "<provider>", ...)の下に配布元の構成で置き、取得したらstore.register_raw_file(...)で URLとチェックサムを記録します。取得は一時ファイルに書いてから置き換え、失敗しても以前のファイルを残します。 派生量をキャッシュするときは、設定と入力の内容からvariant_idを作り、managed=Trueで登録します。

4. Pipelineに対応する(必要な場合)

PipelineBackendを継承し、対応する操作だけを上書きします。エンドポイントのpipeline(time)は、このバックエンドを付けたPipelineを返します。

from sopran.core.pipeline import Pipeline, PipelineBackend

class MyBackend(PipelineBackend):
    def scan_pipeline(self, pipeline: Pipeline):
        ...   # polars.LazyFrame を返す。stream(partition="day") はここから自動で分割される

    def run_pipeline(self, pipeline: Pipeline, *, mode, run_id, resume, only_failed, on_error, download):
        ...   # PipelineResult を返す

上書きしない操作はPipelineErrorになります。PACEの実装はsrc/sopran/missions/kaguya/pipeline.pyにあります。

5. 説明とテストを付ける

  • guide()が返す説明は、パッケージの中のREADME.md(英語)とREADME.ja.mdに書き、公開ドキュメントのURLをGuidePage(url=...)に渡します。
  • 公開ドキュメントにはデータの下にページを作り、ドキュメントの書き方のひな形に沿って書きます。
  • 任意の依存は使う経路で遅延importし、extraに加えます。tests/test_project_metadata.pyがextraと実際のimportの一致を確かめます。
  • 合成データかfixtureで、単位・欠測とゼロの区別・期間の半開区間・キャッシュのキーをテストします。