mcpField
A
annotating-task-lineage

annotating-task-lineage

github.com/astronomerデータ・データベース457977 回インストール3 週間前に更新
リポジトリ

annotating-task-lineage について

Airflow のタスクに inlets と outlets を使って手動でデータリネージ注釈を加えるためのスキルです。OpenLineage 抽出器のないオペレーター向けに、データセット型や優先順位、制約を説明します。

主な機能

  1. 01タスクの inlets と outlets 設定
  2. 02OpenLineage Dataset または Airflow Assets の利用
  3. 03複数の入力と出力の注釈
  4. 04抽出の優先順位と制約の説明

活用シーン

  1. 01組み込み抽出器のないオペレーターの注釈
  2. 02テーブルレベルのリネージを迅速に追加
  3. 03カスタムオペレーターの入出力設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/astronomer/agents --skill annotating-task-lineage

インストール後、Claude Code で/annotating-task-lineageを入力して読み込まれたか確認します。

詳細

Inlets と Outlets によるタスクのリネージ注釈

このスキルでは、inlets と outlets を使用して Airflow タスクに手動でリネージ注釈を追加する方法を説明します。

参考: サポートされている最新のオペレーターとパターンについては、OpenLineage プロバイダーの開発者ガイドを参照してください。

Astro での利用

inlets と outlets で定義したリネージ注釈は、Astro の拡張されたリネージタブに表示されます。このタブでは、DAG 間およびデプロイ間のリネージを確認できます。つまり、注釈は Astro の UI にすぐに表示され、Astro 組織全体のデータフローを統一されたビューで確認できます。

この方法を使用するタイミング

シナリオInlets/Outlets を使用するか
オペレーターに OpenLineage メソッド(get_openlineage_facets_on_*)がある❌ OL メソッドを直接変更
オペレーターに組み込みの OpenLineage エクストラクターがない✅ 使用する
テーブルレベルのシンプルなリネージで十分✅ 使用する
カスタムコードなしで手早くリネージを設定したい✅ 使用する
カラムレベルのリネージが必要❌ OpenLineage メソッドまたはカスタムエクストラクターを使用
複雑な抽出ロジックが必要❌ OpenLineage メソッドまたはカスタムエクストラクターを使用

注: inlets/outlets は優先度が最も低いフォールバックです。オペレーターに OpenLineage エクストラクターまたはメソッドがある場合は、そちらが優先されます。この方法は、エクストラクターのないオペレーターに使用してください。


Inlets/Outlets でサポートされている型

inlets と outlets には、OpenLineage Dataset オブジェクトまたは Airflow Assets を使用できます。

OpenLineage Datasets(推奨)
from openlineage.client.event_v2 import Dataset

# Database tables
source_table = Dataset(
    namespace="postgres://mydb:5432",
    name="public.orders",
)
target_table = Dataset(
    namespace="snowflake://account.snowflakecomputing.com",
    name="staging.orders_clean",
)

# Files
input_file = Dataset(
    namespace="s3://my-bucket",
    name="raw/events/2024-01-01.json",
)
Airflow Assets(Airflow 3 以降)
from airflow.sdk import Asset

# Using Airflow's native Asset type
orders_asset = Asset(uri="s3://my-bucket/data/orders")
Airflow Datasets(Airflow 2.4 以降)
from airflow.datasets import Dataset

# Using Airflow's Dataset type (Airflow 2.4-2.x)
orders_dataset = Dataset(uri="s3://my-bucket/data/orders")

基本的な使い方

オペレーターでの Inlets と Outlets の設定
from airflow import DAG
from airflow.operators.bash import BashOperator
from openlineage.client.event_v2 import Dataset
import pendulum

# Define your lineage datasets
source_table = Dataset(
    namespace="snowflake://account.snowflakecomputing.com",
    name="raw.orders",
)
target_table = Dataset(
    namespace="snowflake://account.snowflakecomputing.com",
    name="staging.orders_clean",
)
output_file = Dataset(
    namespace="s3://my-bucket",
    name="exports/orders.parquet",
)

with DAG(
    dag_id="etl_with_lineage",
    start_date=pendulum.datetime(2024, 1, 1, tz="UTC"),
    schedule="@daily",
) as dag:

    transform = BashOperator(
        task_id="transform_orders",
        bash_command="echo 'transforming...'",
        inlets=[source_table],           # What this task reads
        outlets=[target_table],          # What this task writes
    )

    export = BashOperator(
        task_id="export_to_s3",
        bash_command="echo 'exporting...'",
        inlets=[target_table],           # Reads from previous output
        outlets=[output_file],           # Writes to S3
    )

    transform >> export
複数の入力と出力

タスクでは、複数のソースから読み込み、複数の宛先に書き込むことがよくあります。

from openlineage.client.event_v2 import Dataset

# Multiple source tables
customers = Dataset(namespace="postgres://crm:5432", name="public.customers")
orders = Dataset(namespace="postgres://sales:5432", name="public.orders")
products = Dataset(namespace="postgres://inventory:5432", name="public.products")

# Multiple output tables
daily_summary = Dataset(namespace="snowflake://account", name="analytics.daily_summary")
customer_metrics = Dataset(namespace="snowflake://account", name="analytics.customer_metrics")

aggregate_task = PythonOperator(
    task_id="build_daily_aggregates",
    python_callable=build_aggregates,
    inlets=[customers, orders, products],      # All inputs
    outlets=[daily_summary, customer_metrics], # All outputs
)

カスタムオペレーターでのリネージ設定

カスタムオペレーターを作成する場合、次の 2 つの方法があります。

方法 1:OpenLineage メソッドを実装する(推奨)

リネージの抽出を完全に制御できるため、こちらが推奨される方法です。

from airflow.models import BaseOperator


class MyCustomOperator(BaseOperator):
    def __init__(self, source_table: str, target_table: str, **kwargs):
        super().__init__(**kwargs)
        self.source_table = source_table
        self.target_table = target_table

    def execute(self, context):
        # ... perform the actual work ...
        self.log.info(f"Processing {self.source_table} -> {self.target_table}")

    def get_openlineage_facets_on_complete(self, task_instance):
        """Return lineage after successful execution."""
        from openlineage.client.event_v2 import Dataset
        from airflow.providers.openlineage.extractors import OperatorLineage

        return OperatorLineage(
            inputs=[Dataset(namespace="warehouse://db", name=self.source_table)],
            outputs=[Dataset(namespace="warehouse://db", name=self.target_table)],
        )
方法 2:Inlets/Outlets を動的に設定する

よりシンプルなケースでは、execute メソッド内でリネージを設定します(遅延可能なオペレーターは対象外です)。

from airflow.models import BaseOperator
from openlineage.client.event_v2 import Dataset


class MyCustomOperator(BaseOperator):
    def __init__(self, source_table: str, target_table: str, **kwargs):
        super().__init__(**kwargs)
        self.source_table = source_table
        self.target_table = target_table

    def execute(self, context):
        # Set lineage dynamically based on operator parameters
        self.inlets = [
            Dataset(namespace="warehouse://db", name=self.source_table)
        ]
        self.outlets = [
            Dataset(namespace="warehouse://db", name=self.target_table)
        ]

        # ... perform the actual work ...
        self.log.info(f"Processing {self.source_table} -> {self.target_table}")

Dataset 命名ヘルパー

OpenLineage の Dataset 命名ヘルパーを使用して、プラットフォーム間で一貫した命名にします。

from openlineage.client.event_v2 import Dataset

# Snowflake
from openlineage.client.naming.snowflake import SnowflakeDatasetNaming

naming = SnowflakeDatasetNaming(
    account_identifier="myorg-myaccount",
    database="mydb",
    schema="myschema",
    table="mytable",
)
dataset = Dataset(namespace=naming.get_namespace(), name=naming.get_name())
# -> namespace: "snowflake://myorg-myaccount", name: "mydb.myschema.mytable"

# BigQuery
from openlineage.client.naming.bigquery import BigQueryDatasetNaming

naming = BigQueryDatasetNaming(
    project="my-project",
    dataset="my_dataset",
    table="my_table",
)
dataset = Dataset(namespace=naming.get_namespace(), name=naming.get_name())
# -> namespace: "bigquery", name: "my-project.my_dataset.my_table"

# S3
from openlineage.client.naming.s3 import S3DatasetNaming

naming = S3DatasetNaming(bucket="my-bucket", key="path/to/file.parquet")
dataset = Dataset(namespace=naming.get_namespace(), name=naming.get_name())
# -> namespace: "s3://my-bucket", name: "path/to/file.parquet"

# PostgreSQL
from openlineage.client.naming.postgres import PostgresDatasetNaming

naming = PostgresDatasetNaming(
    host="localhost",
    port=5432,
    database="mydb",
    schema="public",
    table="users",
)
dataset = Dataset(namespace=naming.get_namespace(), name=naming.get_name())
# -> namespace: "postgres://localhost:5432", name: "mydb.public.users"

注: 名前空間を手動で構築するのではなく、必ず命名ヘルパーを使用してください。お使いのプラットフォーム用のヘルパーがない場合は、OpenLineage リポジトリを確認するか、追加をリクエストしてください。


優先順位のルール

OpenLineage では、リネージの抽出に次の優先順位が適用されます。

  1. カスタムエクストラクター(最優先) - ユーザーが登録したエクストラクター
  2. OpenLineage メソッド - オペレーター内の get_openlineage_facets_on_*
  3. フックレベルのリネージ - HookLineageCollector を介してフックから収集されるリネージ
  4. Inlets/Outlets(最優先度) - ほかの方法でリネージを抽出できない場合のフォールバック

注: エクストラクターまたはメソッドが存在しても、Dataset を返さない場合、OpenLineage はフックレベルのリネージを確認し、その後 inlets/outlets にフォールバックします。


ベストプラクティス

命名ヘルパーを使用する

Dataset を一貫して作成するため、必ず OpenLineage の命名ヘルパーを使用してください。

from openlineage.client.event_v2 import Dataset
from openlineage.client.naming.snowflake import SnowflakeDatasetNaming


def snowflake_dataset(schema: str, table: str) -> Dataset:
    """Create a Snowflake Dataset using the naming helper."""
    naming = SnowflakeDatasetNaming(
        account_identifier="mycompany",
        database="analytics",
        schema=schema,
        table=table,
    )
    return Dataset(namespace=naming.get_namespace(), name=naming.get_name())


# Usage
source = snowflake_dataset("raw", "orders")
target = snowflake_dataset("staging", "orders_clean")
リネージを文書化する

データフローを説明するコメントを追加してください。

transform = SqlOperator(
    task_id="transform_orders",
    sql="...",
    # Lineage: Reads raw orders, joins with customers, writes to staging
    inlets=[
        snowflake_dataset("raw", "orders"),
        snowflake_dataset("raw", "customers"),
    ],
    outlets=[
        snowflake_dataset("staging", "order_details"),
    ],
)
リネージを正確に保つ
  • SQL クエリを変更したときは、inlets/outlets も更新してください
  • JOIN で参照するすべてのテーブルを inlet に含めてください
  • 書き込み先のすべてのテーブルを含めてください(該当する場合は一時テーブルも含みます)
  • Outlet のみ、または inlet のみの注釈も有効です。 別の DAG に対応する inlet または outlet がなくても、リネージを可視化するために片側だけの注釈を付けることが推奨されます。

制限事項

制限事項回避策
テーブルレベルのみ(カラムリネージはありません)OpenLineage のメソッドまたはカスタム抽出器を使用します
抽出器/メソッドによって上書きされます抽出器のないオペレーターにのみ使用します
DAG の解析時に静的に設定されますexecute() で動的に設定するか、OL メソッドを使用します
遅延実行可能なオペレーターでは動的なリネージが失われます代わりに OL メソッドを使用します。遅延実行時に execute() で設定した属性は失われます

関連スキル

  • creating-openlineage-extractors:カラムレベルのリネージまたは複雑な抽出に使用します
  • tracing-upstream-lineage:データの取得元を調査します
  • tracing-downstream-lineage:データに依存する対象を調査します

コメント