dbt-scoreの導入手順とカスタムルールの記載例メモ
この記事は、dbt Advent Calendar 2024 シリーズ2 の11日目の記事です。
dbt Advent Calendar 2024
https://qiita.com/advent-calendar/2024/dbt
dbt(Data Build Tool)の自動検証ツールに、
dbt-scoreというものがあり、試してみたので導入手順メモを残しておきます。
dbt-score
https://dbt-score.picnic.tech/
dbt-checkpointよりも用意されているルールは少ないですが、
dbt-scoreの方が、独自のルールが書きやすく導入しやすいかも知れないです。
この手順で試したversion
- dbt-core==1.9.0rc2
- dbt-postgres==1.8.2
- dbt-score==0.8.0
導入手順
dbtのプロジェクトを準備
Gitのリポジトリ用にディレクトリを作ります。
mkdir dbt-score-study && cd $_
git init
Python仮想環境を用意します。
python -m venv venv
. venv/bin/activate
echo "venv" >> .gitignore
dbtをインストールします(ここでは、とりあえずPostgreSQL向けを入れます)。
# 本当は、さらっとsqliteで試したかったのですが、
# dbt-scoreが、dbt-coreのv1.6以上にしか対応しておらず、
# dbt-sqliteはv1.5から更新されていないため。
pip install dbt-postgres
pip freeze > requirements.txt
dbtプロジェクトを初期化します。
dbt init sample
echo "logs" >> .gitignore
profiles.ymlを用意します。
profiles.yml
sample:
outputs:
dev:
dbname: dbt
host: localhost
pass: dbt
port: 5432
schema: dbt
threads: 1
type: postgres
user: dbt
target: dev
動作確認します。
DBT_PROJECT_DIR=sample dbt parse
dbt-scoreの導入
dbt-scoreをインストールします。
pip install dbt-score
pip freeze > requirements.txt
dbt-scoreを実行すると、自動検証が行われてスコアが表示されます。
DBT_PROJECT_DIR=sample dbt-score lint --run-dbt-parse
$ DBT_PROJECT_DIR=sample dbt-score lint --run-dbt-parse
🥈 M: my_first_dbt_model (score: 8.0)
OK dbt_score.rules.generic.columns_have_description
OK dbt_score.rules.generic.has_description
WARN (low) dbt_score.rules.generic.has_example_sql: The model description does not include an example SQL query.
WARN (medium) dbt_score.rules.generic.has_owner: Model lacks an owner.
OK dbt_score.rules.generic.sql_has_reasonable_number_of_lines
🥈 M: my_second_dbt_model (score: 8.0)
OK dbt_score.rules.generic.columns_have_description
OK dbt_score.rules.generic.has_description
WARN (low) dbt_score.rules.generic.has_example_sql: The model description does not include an example SQL query.
WARN (medium) dbt_score.rules.generic.has_owner: Model lacks an owner.
OK dbt_score.rules.generic.sql_has_reasonable_number_of_lines
Project score: 8.0 🥈
既定で適用されたルールについては、以下に説明があります。
Generic | Rules | dbt-score
https://dbt-score.picnic.tech/rules/generic/
ここからコードも確認でき、
独自ルール実装の参考になるので、1つくらい見ておくと良いと思います。
独自ルールの作成
それでは独自のルールを実装してみます。
参考: Create rules | dbt-score
https://dbt-score.picnic.tech/create_rules/
ここでは、dbtのBestPracticesに記載されているものを1つ実装してみます。
How we style our dbt models | dbt
https://docs.getdbt.com/best-practices/how-we-style/1-how-we-style-our-dbt-models
上記ページの、次のルールを実装することにします。
Timestamp columns should be named
_at(for example, created_at) and should be in UTC. If a different timezone is used, this should be indicated with a suffix (created_at_pt).
実装は、次のようになります。
dbt_score_rules/custom_rule.py
import re
from dbt_score import Model, RuleViolation, rule
@rule
def column_name_convention_timestamp(model: Model) -> RuleViolation | None:
"""Column name convension."""
ng_cols = [
c.name for c in model.columns
if c.data_type and c.data_type.lower() == "timestamp"
and not (re.match(".*_at$", c.name) or re.match(".*_at_[^_]*$", c.name))
]
if ng_cols:
return RuleViolation(message=f"Timestamp columns should be named <event>_at or <event>_at_<tz>: {','.join(ng_cols)}")
試しにschema.ymlを変更して、実行してみます。
sample/models/example/schema.yml ※created_at,create_timeカラムを追加
version: 2
models:
- name: my_first_dbt_model
description: "A starter dbt model"
columns:
- name: id
description: "The primary key for this table"
data_tests:
- unique
- not_null
- name: created_at
data_type: timestamp
- name: create_time
data_type: timestamp
- name: my_second_dbt_model
description: "A starter dbt model"
columns:
- name: id
description: "The primary key for this table"
data_tests:
- unique
- not_null
$ DBT_PROJECT_DIR=sample dbt-score lint --run-dbt-parse
🥈 M: my_second_dbt_model (score: 8.3)
OK dbt_score.rules.generic.columns_have_description
OK dbt_score.rules.generic.has_description
WARN (low) dbt_score.rules.generic.has_example_sql: The model description does not include an example SQL query.
WARN (medium) dbt_score.rules.generic.has_owner: Model lacks an owner.
OK dbt_score.rules.generic.sql_has_reasonable_number_of_lines
OK dbt_score_rules.custom_rule.column_name_convention_timestamp
🥉 M: my_first_dbt_model (score: 6.1)
WARN (medium) dbt_score.rules.generic.columns_have_description: Columns lack a description: created_at, create_time.
OK dbt_score.rules.generic.has_description
WARN (low) dbt_score.rules.generic.has_example_sql: The model description does not include an example SQL query.
WARN (medium) dbt_score.rules.generic.has_owner: Model lacks an owner.
OK dbt_score.rules.generic.sql_has_reasonable_number_of_lines
WARN (medium) dbt_score_rules.custom_rule.column_name_convention_timestamp: Timestamp columns should be named <event>_at or <event>_at_<tz>: create_time
Project score: 7.2 🥉
my_first_dbt_modelについて、カラム名が不適切という指摘がされました。
dbt-scoreの設定 (カスタムルールのみ実行・Severityの設定)
続いて、dbt-scoreの設定についてです。
参考: Configuration | dbt-score
https://dbt-score.picnic.tech/configuration/
カスタムで作成したルールのみを実行したい場合は、
rule_namespacesで、先ほど作成したルールのモジュールを指定します。
先ほどの作成したルールは「WARN (medium)」で指摘されており、
これにより、Projectのscoreが低下していました。
このルールが厳守すべきであれば、これをcriticalにします。
するとscoreが0点になり、検証にfailさせることができます。
この場合はruleのseverityを4(critical)にします。
pyproject.toml
[tool.dbt-score]
rule_namespaces = ["dbt_score_rules"]
[tool.dbt-score.rules."dbt_score_rules.custom_rule.column_name_convention_timestamp"]
severity = 4
実行結果は次の通り、検証にfailしexit_codeも1になっています。
$ DBT_PROJECT_DIR=sample dbt-score lint --run-dbt-parse
🥇 M: my_second_dbt_model (score: 10.0)
OK dbt_score_rules.custom_rule.column_name_convention_timestamp
🚧 M: my_first_dbt_model (score: 0.0)
WARN (critical) dbt_score_rules.custom_rule.column_name_convention_timestamp: Timestamp columns should be named <event>_at or <event>_at_<tz>: create_time
Project score: 0.0 🚧
Error: evaluable score too low, fail_any_item_under = 5.0
Model my_first_dbt_model scored 0.0
$ echo $?
1
ルール適用対象の除外
特定のモデルをルールの適用対象から除外したい場合は、フィルターを使います。
参考: Filtering rules | dbt-score
https://dbt-score.picnic.tech/create_rules/#filtering-rules
フィルターの実装は、次のようになります。
dbt_score_rules/custom_filter.py
from dbt_score import Model, rule_filter
IGNORE_MODELS = [
"my_first_dbt_model",
]
@rule_filter
def ignore_models(model: Model) -> bool:
"""Ignore models in ignore list."""
return model.name not in IGNORE_MODELS
フィルターを適用したいルールに、フィルターを指定します。
この場合はruleのrule_filter_namesにフィルター名を記載します。
pyproject.toml
[tool.dbt-score]
rule_namespaces = ["dbt_score_rules"]
[tool.dbt-score.rules."dbt_score_rules.custom_rule.column_name_convention_timestamp"]
severity = 4
rule_filter_names = ["dbt_score_rules.custom_filter.ignore_models"]
実行結果は次の通り、my_first_dbt_modelの検証がskipしています。
$ DBT_PROJECT_DIR=sample dbt-score lint --run-dbt-parse
🥇 M: my_second_dbt_model (score: 10.0)
OK dbt_score_rules.custom_rule.column_name_convention_timestamp
🥇 M: my_first_dbt_model (score: 10.0)
Project score: 10.0 🥇
以上。