Обзор репозитория dbt-core: Python-пакеты
Если предыдущие уроки рассказали «что делает» dbt-core, этот рассказывает где это лежит. После прочтения вы сможете открыть github.com/dbt-labs/dbt-core и за минуту понять, в какой файл идти за ответом на конкретный вопрос.
Версия — v1.11.5 (Q4 2025 stable). Структура медленно эволюционирует — между 1.7 и 1.11 был большой split на отдельные packages (dbt-adapters, dbt-common, dbt-semantic-interfaces), но основные модули в core/dbt/ стабильны.
Структура верхнего уровня
dbt-core/
├── core/dbt/ ← главный пакет (исторически core/)
│ ├── cli/ ← CLI entry point
│ ├── config/ ← RuntimeConfig, Project, Profile
│ ├── parser/ ← ManifestLoader и парсеры
│ ├── compilation.py ← Linker, Compiler
│ ├── graph/ ← networkx wrappers, queue, selector
│ ├── context/ ← Jinja contexts
│ ├── contracts/ ← dataclasses всех артефактов
│ ├── task/ ← Task classes (Run/Test/Build/Snapshot)
│ ├── adapters/ ← legacy (теперь dbt-adapters package)
│ ├── events/ ← event system
│ ├── exceptions.py ← все исключения dbt
│ ├── flags.py ← global Flags
│ ├── tracking.py ← telemetry (опционально)
│ └── version.py ← dbt version constant
├── tests/ ← test suite (~10000+ tests)
├── scripts/ ← CI scripts
├── pyproject.toml ← dependencies, build config
└── README.md
И параллельно — отдельные packages (с 1.8+ split):
dbt-adapters/ ← отдельный repo
├── dbt/adapters/base/ ← BaseAdapter, BaseConnectionManager
├── dbt/adapters/sql/ ← SQLAdapter, SQLConnectionManager
└── dbt/adapters/contracts/ ← Credentials, AdapterResponse, ...
dbt-common/ ← отдельный repo
└── dbt_common/ ← shared utilities (events, exceptions, helpers)
dbt-semantic-interfaces/ ← отдельный repo
└── dbt_semantic_interfaces/ ← MetricFlow contracts
В этом уроке фокус — на core/dbt/. Adapter-related packages разбираем в модулях 08-09, semantic-interfaces — в модуле 13.
core/dbt/cli — entry point
Что внутри:
main.py— Click-команды (run, test, build, compile, parse, deps, debug, …). ~600 строк.flags.py—Flagsdataclass. Mediator между CLI args и programmatic args.option_types.py— кастомные Click типы (напримерChoicesAllowingHyphens,YamlObjectдля--vars).requires.py— Click decorators для preconditions (@requires.project,@requires.preflight).resolvers.py— резолверы для default values из env vars и configs.
Когда сюда лезть: вы добавляете новый CLI флаг или новую команду. Чтение main.py даёт паттерн.
core/dbt/config — RuntimeConfig
Что внутри:
runtime.py—RuntimeConfig(главный класс).project.py—Project(читает dbt_project.yml).profile.py—Profile(читает profiles.yml).renderer.py— Jinja renderer для конфигов (для env_var, var в YAML).selectors.py—SelectorConfig(selectors.yml для модуля 10).utils.py— helpers.
RuntimeConfig.from_args(args) — это canonical entry point. Внутри:
@classmethod
def from_args(cls, args) -> "RuntimeConfig":
# 1. Locate dbt_project.yml
project_root = get_project_root(args)
project_renderer = ProfileRenderer(...)
# 2. Load Project
project = Project.from_project_root(project_root, project_renderer)
# 3. Load Profile
profile_renderer = ProfileRenderer(cli_vars)
profile = Profile.from_args(args, project, profile_renderer)
# 4. Combine
return cls.from_parts(project, profile, args)
Когда сюда лезть: проблемы с target.name, var(), env_var(), schema_name override. Все они приходят из RuntimeConfig.
core/dbt/parser — ManifestLoader
Самый большой пакет — ~5000 строк Python. Что внутри:
manifest.py—ManifestLoader(~800 строк, главный класс).models.py—ModelParser— парсит.sqlмодели.macros.py—MacroParser— парсит.sqlмакросы.schemas.py—SchemaParser— парсит_models.yml,_sources.yml,_macros.yml.generic_test_builders.py— builds тесты из YAML (tests: - unique).snapshots.py—SnapshotParser.seeds.py—SeedParser.singular_test.py— singular tests (tests/my_test.sql).sources.py—SourceParser.unit_tests.py—UnitTestParser(1.8+).base.py—BaseParser, общая логика.partial.py— partial parsing (msgpack).search.py— file system search (find all .sql, all .yml).
ManifestLoader координирует все парсеры. Каждый парсер специализируется на одном типе ресурса.
# core/dbt/parser/manifest.py (упрощённо)
class ManifestLoader:
def load(self) -> Manifest:
for project in self.all_projects:
ModelParser(project, ...).parse() # adds to self.manifest.nodes
MacroParser(project, ...).parse() # adds to self.manifest.macros
SchemaParser(project, ...).parse() # patches existing nodes
SnapshotParser(project, ...).parse()
# ... остальные парсеры
self._process_refs()
self._process_sources()
return self.manifest
Когда сюда лезть: модель не находится в Manifest, схема не подцепляется, ref не резолвится, partial parse инвалидируется неожиданно. Это всё parsing issues — parser/ module.
core/dbt/compilation.py + core/dbt/graph
compilation.py — относительно небольшой файл (~400 строк):
Linker— берёт Manifest, строит networkx DAG, проверяет cycles.Compiler— компилирует одну ноду (Jinja -> SQL), используется вRunRunner.compile().
core/dbt/graph/ — wrappers вокруг networkx:
graph.py—Graph(тонкая обёртка над networkx.DiGraph).queue.py—GraphQueue(thread-safe priority queue для execution).selector.py—NodeSelector(--select,--exclude).selector_spec.py—SelectionSpec,SelectionCriteria.selector_methods.py—tag:,path:,package:,state:modified,result:error, …cli.py— CLI args для selection.
NodeSelector — это где вся магия dbt run --select tag:nightly state:modified+1 happens. SelectionCriteria — это компоненты вроде tag:nightly или +my_model+, SelectionSpec — это композиция через intersection/union/exclude. Senior, который понимает эту структуру, может добавить custom selector methods.
Когда сюда лезть: проблемы с DAG (cycles, missing deps), проблемы с selection (—select не находит, state comparison glitch’ит).
core/dbt/context — Jinja contexts
Это сердце Jinja-системы dbt. Что внутри:
base.py—BaseContext(минимальный, для конфигов и raw Jinja).target.py—TargetContext(для profiles.yml rendering).secret.py—SecretContext(env_var, ограниченный).configured.py—ConfiguredContext(project + profile rendering).docs.py—DocsContext(для{% docs %}blocks).macros.py—MacroContext,MacroNamespace.providers.py—ProviderContext,ParseProvider,RuntimeProvider. Главный файл — ~1200 строк, читать обязательно.context_config.py—ContextConfig(config.get/require/set).exceptions_jinja.py— Jinja-specific exceptions.
Иерархия context’ов — модуль 03 курса. Превью:
BaseContext
├── TargetContext (для profiles)
├── ConfiguredContext (project + profile)
│ ├── MacroResolvingContext (для tests, configs)
│ │ └── MacroContext
│ └── ProviderContext (для models, snapshots)
│ ├── ParseProvider (execute=False)
│ └── RuntimeProvider (execute=True)
Когда сюда лезть: «почему statement() не работает» (потому что execute=False), «почему ref() available в этом макросе но не в другом» (потому что разный context), «как добавить custom Jinja-функцию». Всё в context/providers.py.
core/dbt/contracts — dataclasses всех артефактов
Это API contracts dbt: что такое Manifest, RunResults, Catalog в виде Python dataclasses (с поддержкой сериализации в JSON).
Что внутри:
graph/manifest.py—Manifestdataclass.graph/nodes.py—ManifestNode,ModelNode,SnapshotNode,TestNode,SeedNode,SourceDefinition, …graph/model_config.py—ModelConfig,NodeConfig, configs для разных типов.graph/unparsed.py— pre-parse YAML schemas (для _models.yml).graph/saved_queries.py—SavedQuery(semantic layer).graph/semantic_manifest.py—SemanticManifest.results.py—RunResult,RunResultsArtifact,BaseResult.files.py—AnyFile,SchemaSourceFile,ParseFileType(для partial parsing).project.py—ProjectV1,ProjectFlags(как parsed dbt_project.yml).selection.py—SelectionSpec(selectors.yml).relation.py—Path,RelationType.util.py— JSONSerializable mixins.
Когда сюда лезть: вам нужно знать «что в JSON под этим ключом». Открываете соответствующий dataclass — там типы, описания, defaults.
core/dbt/task — Task classes
Что внутри:
runnable.py—GraphRunnableTask(база).run.py—RunTask,RunRunner,ModelRunner.compile.py—CompileTask,CompileRunner.test.py—TestTask,TestRunner.snapshot.py—SnapshotTask,SnapshotRunner.seed.py—SeedTask,SeedRunner.build.py—BuildTask(combo run + test).docs/generate.py—GenerateTask(генерация catalog.json).docs/serve.py—ServeTask.debug.py—DebugTask(dbt debug).deps.py—DepsTask(dbt deps).clean.py,init.py,list.py,parse.py,retry.py,run_operation.py,show.py— остальные команды.base.py—BaseTask,ConfiguredTask.printer.py— пользовательский вывод (event handlers для terminal).
Каждая dbt <command> имеет соответствующий *Task класс. Если хотите понять «что делает dbt clean» — открываете clean.py, читаете ~30 строк.
core/dbt/events — event system
dbt излучает события через свою event-инфраструктуру. Это structured logging + observability.
Что внутри:
types.py— все event-типы (NodeStart,NodeFinished,RunStart, …).event_handler.py— handlers для terminal output.logging.py— Python logging integration.adapter_endpoint.py— events от адаптеров.base_types.py—BaseEvent,EventLevel,EventMsg.
Когда dbt run выполняется, каждая нода излучает события:
NodeStart— начало executionLogStartLine— стартовая строка выводаNodeFinished— завершение с RunResult
Эти события подхватываются printer.py для terminal output и могут быть подписаны программно (dbtRunner.callbacks=[my_handler]). Модуль 06 курса покажет, как использовать events для метрик в Dagster.
Сравнение: что в Python пакетах, а что в Jinja
Полезное наблюдение для senior:
| Layer | Where | Что делает |
|---|---|---|
| CLI | Python (cli/) | Парсит флаги, делегирует в task |
| Config | Python (config/) | dbt_project.yml + profiles.yml |
| Parser | Python (parser/) | Читает .sql, .yml, строит Manifest |
| Linker | Python (compilation.py, graph/) | DAG из Manifest |
| Task | Python (task/) | Orchestrator nodes execution |
| Runner | Python (task/) | Compile + execute одной ноды |
| Materialization | Jinja (include/global_project/macros/materializations/) | SQL templates для table/view/incremental |
| Adapter macros | Jinja (include/global_project/macros/) | adapter.get_columns, list_relations, … |
| Adapter | Python (dbt-adapters/, dbt-duckdb/) | execute(sql), connection management |
Materialization-логика — это Jinja, не Python. Это сознательное дизайн-решение: dbt позволяет users override materializations через свои packages, и Jinja удобнее для SQL-генерации. Python-часть — только инфраструктура. Модуль 07 курса — глубокий разбор.
include/global_project/ — это «builtin package» dbt-core, содержащий все builtin macros, materializations, generic tests. Его можно открыть в репозитории и читать как обычный dbt-package — это и есть source of truth для table/view/incremental/snapshot.
Попробуй сам
- Откройте
core/dbt/в IDE с структурой. Скроллите дерево, опознавайте каждый модуль по этому уроку. git grep -n "class ProviderContext" core/dbt/— найдите определение. Откройте файл.git grep -rn "materialization" include/global_project/macros/materializations/— посмотрите как написаны builtin materializations.find core/dbt -name "*.py" | xargs wc -l | sort -nr | head -20— топ-20 самых больших файлов dbt-core. Это даёт ощущение «где наибольший concentrated complexity».- Откройте один из больших файлов (например,
context/providers.pyилиparser/manifest.py). Прочитайте class definitions, не пытаясь понять детали. Цель — карта.