Looker SDKを使ってLookMLのバリデーションCIを自作した話
はじめに
こんにちは。ファインディ株式会社でデータエンジニアをやっている田頭啓介(@tagasyksk)です。
この記事は、ファインディ株式会社 Advent Calendar 2025の20日目の記事です。
ファインディではBIツールとしてLookerを利用しています。
最近ではClaude CodeなどのAIを活用してLookMLを開発する機会が増え、CIによる自動検証の重要性がより高まっています。本記事では、GitHub Actionsで行っているLookMLのバリデーションについて紹介します。
LookerのネイティブCIについて
Lookerでは、2025年12月現在プレビュー版として「Looker Continuous Integration」が提供されています。
Looker Continuous Integrationでは、次のような検証が可能です。
- Exploreのディメンションがデータベースに対して正常に実行されるか (SQL Validator)
- LookMLに記述したデータテストが通るか(Assert Validator)
- ダッシュボードやLookにエラーが無いか(Contents Validator)
- LookMLの構文が正しいか(LookML Validator)
これらは全て非常に有用ですが、LookMLに記述したメジャーを検証するCIが存在しません。構文的には正しいLookMLでも、実際にExploreを開いて指標を計算しようとするとエラーになるケースがあり、PRマージ後に初めて問題が発覚することがありました。
そこで、Looker SDKを利用して開発したExplore上のメジャーが正常に計算できるか検証するCIを作成しました。
スクリプト
devワークスペースに切り替えて、ブランチ上のモデルに対してクエリを実行するようにしています。
import os
import sys
import looker_sdk
from looker_sdk import models40, error
OUTPUT_FILE = "validation_result.md"
LIMIT = "1"
MODEL_NAME = os.getenv("LOOKER_MODEL", "your-model-name")
PROJECT_ID = os.getenv("LOOKER_PROJECT")
TARGET_WORKSPACE = os.getenv("LOOKER_WORKSPACE", "dev").lower()
TARGET_BRANCH = os.getenv("LOOKER_GIT_BRANCH")
def main():
sdk = looker_sdk.init40()
print(f"認証成功: {sdk.me().display_name}")
# devワークスペースに切り替え
setup_workspace(sdk)
# 検証実行
success_list, failed_list = run_validation(sdk)
if len(failed_list) > 0:
sys.exit(1)
def setup_workspace(sdk):
"""ワークスペースとGitブランチの切り替え"""
if TARGET_WORKSPACE == "dev":
sdk.update_session(body=models40.WriteApiSession(workspace_id="dev"))
if TARGET_BRANCH and PROJECT_ID:
sdk.update_git_branch(
project_id=PROJECT_ID,
body=models40.WriteGitBranch(name=TARGET_BRANCH)
)
sdk.reset_project_to_remote(PROJECT_ID)
def run_validation(sdk):
"""Exploreの検証を実行"""
success_explores = []
failed_explores = []
model_info = sdk.lookml_model(lookml_model_name=MODEL_NAME)
explores = model_info.explores
for exp_def in explores:
explore_name = exp_def.name
print(f"検証中: {explore_name}")
try:
explore = sdk.lookml_model_explore(MODEL_NAME, explore_name)
# 各ViewからDimensionを1つずつ取得
view_names = set()
if explore.view_name:
view_names.add(explore.view_name)
if explore.joins:
for j in explore.joins:
view_names.add(j.name)
query_dimensions = []
if explore.fields and explore.fields.dimensions:
for v_name in view_names:
found_dim = next(
(d.name for d in explore.fields.dimensions if d.view == v_name),
None
)
if found_dim:
query_dimensions.append(found_dim)
# Measureを取得
query_measure = None
if explore.fields and explore.fields.measures:
query_measure = explore.fields.measures[0].name
if not query_dimensions:
continue
fields = query_dimensions.copy()
if query_measure:
fields.append(query_measure)
# クエリ実行
query_body = models40.WriteQuery(
model=MODEL_NAME,
view=explore_name,
fields=fields,
limit=LIMIT
)
sdk.run_inline_query(result_format="json", body=query_body)
success_explores.append(explore_name)
except error.SDKError as e:
failed_explores.append({
"name": explore_name,
"error": str(e.message) if hasattr(e, 'message') else str(e)
})
return success_explores, failed_explores
検証結果はMarkdown形式でファイルに出力し、PRコメントとして投稿するようにしています。
- 成功時
- 失敗時
※GitHub Actionsのワークフローは、Pythonスクリプトを呼び出しているだけなので割愛しました。
おわりに
Looker公式のCIでは検出できないExploreの実行エラーを、Looker SDKとGitHub Actionsで検証する仕組みを紹介しました。このCIにより、PRマージ前にExploreの実行エラーを検知できるようになりました。
ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。
Discussion