mirror of
https://github.com/dbt-labs/dbt-core
synced 2025-12-19 21:11:28 +00:00
Compare commits
82 Commits
jerco/hack
...
adding-sem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2da925aa25 | ||
|
|
43e24c5ae6 | ||
|
|
89d111a5f6 | ||
|
|
e1b5e68904 | ||
|
|
065ab2ebc2 | ||
|
|
20c95a4993 | ||
|
|
c40b488cb4 | ||
|
|
585e7c59e8 | ||
|
|
7077c47551 | ||
|
|
f789b2535a | ||
|
|
2bfc6917e2 | ||
|
|
d74ae19523 | ||
|
|
1c7c23ac73 | ||
|
|
86e8722cd8 | ||
|
|
7a61602738 | ||
|
|
dd4b47d8b1 | ||
|
|
eb200b4687 | ||
|
|
0fc080d222 | ||
|
|
5da63602b3 | ||
|
|
457ff3ef48 | ||
|
|
0dbdecef10 | ||
|
|
b13b0e9492 | ||
|
|
b9fdfd9e36 | ||
|
|
4d6352db14 | ||
|
|
9eb82c6497 | ||
|
|
89cc89dfdf | ||
|
|
2b0f6597a4 | ||
|
|
294def205f | ||
|
|
34fa703466 | ||
|
|
ab3f8dcbfd | ||
|
|
02c20477b9 | ||
|
|
d9a4ee126a | ||
|
|
94d6d19fb4 | ||
|
|
d43c070007 | ||
|
|
9ef236601b | ||
|
|
9d6f961d2b | ||
|
|
5453840950 | ||
|
|
d453964546 | ||
|
|
748a932811 | ||
|
|
8217ad4722 | ||
|
|
6ef3fbbf76 | ||
|
|
76fd12c7cd | ||
|
|
9ecb6e50e4 | ||
|
|
ce9d0afb8a | ||
|
|
c39ea807e8 | ||
|
|
1e35339389 | ||
|
|
304797b099 | ||
|
|
b9bdb775ab | ||
|
|
df93858b4b | ||
|
|
e8da84fb9e | ||
|
|
7e90e067af | ||
|
|
5e4e917de5 | ||
|
|
05dc0212e7 | ||
|
|
c00052cbfb | ||
|
|
3d54a83822 | ||
|
|
fafd5edbda | ||
|
|
8478262580 | ||
|
|
83b1fee062 | ||
|
|
0fbbc896b2 | ||
|
|
0544b08543 | ||
|
|
bef6edb942 | ||
|
|
99f27de934 | ||
|
|
9c91f3a7bd | ||
|
|
1b6fed2ffd | ||
|
|
0721f2c1b7 | ||
|
|
b9a35da118 | ||
|
|
60f80056b1 | ||
|
|
540c3b79aa | ||
|
|
16f529e1d4 | ||
|
|
ebfcf2a9ef | ||
|
|
67a8138b65 | ||
|
|
85d0b5afc7 | ||
|
|
1fbcaa4484 | ||
|
|
481235a943 | ||
|
|
2289e45571 | ||
|
|
b5d303f12a | ||
|
|
c3be975783 | ||
|
|
47c2edb42a | ||
|
|
b3440417ad | ||
|
|
020f639c7a | ||
|
|
55db15aba8 | ||
|
|
bce0e7c096 |
@@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 1.4.0a1
|
current_version = 1.5.0a1
|
||||||
parse = (?P<major>\d+)
|
parse = (?P<major>\d+)
|
||||||
\.(?P<minor>\d+)
|
\.(?P<minor>\d+)
|
||||||
\.(?P<patch>\d+)
|
\.(?P<patch>\d+)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
For information on prior major and minor releases, see their changelogs:
|
For information on prior major and minor releases, see their changelogs:
|
||||||
|
|
||||||
|
|
||||||
|
* [1.4](https://github.com/dbt-labs/dbt-core/blob/1.4.latest/CHANGELOG.md)
|
||||||
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
||||||
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
||||||
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Update pathspec requirement from ~=0.9.0 to >=0.9,<0.11 in /core"
|
|
||||||
time: 2022-09-23T00:06:46.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 5917
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Bump black from 22.8.0 to 22.10.0"
|
|
||||||
time: 2022-10-07T00:08:48.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 6019
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Update colorama requirement from <0.4.6,>=0.3.9 to >=0.3.9,<0.4.7 in /core"
|
|
||||||
time: 2022-10-26T00:09:10.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 6144
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
body: minor doc correction
|
|
||||||
time: 2022-09-08T15:41:57.689162-04:00
|
|
||||||
custom:
|
|
||||||
Author: andy-clapson
|
|
||||||
Issue: "5791"
|
|
||||||
PR: "5684"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
body: Generate API docs for new CLI interface
|
|
||||||
time: 2022-10-07T09:06:56.446078-05:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5528"
|
|
||||||
PR: "6022"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
time: 2022-10-17T17:14:11.715348-05:00
|
|
||||||
custom:
|
|
||||||
Author: paulbenschmidt
|
|
||||||
Issue: "5880"
|
|
||||||
PR: "324"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
body: Fix rendering of sample code for metrics
|
|
||||||
time: 2022-11-16T15:57:43.204201+01:00
|
|
||||||
custom:
|
|
||||||
Author: jtcohen6
|
|
||||||
Issue: "323"
|
|
||||||
PR: "346"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Added favor-state flag to optionally favor state nodes even if unselected node
|
|
||||||
exists
|
|
||||||
time: 2022-04-08T16:54:59.696564+01:00
|
|
||||||
custom:
|
|
||||||
Author: daniel-murray josephberni
|
|
||||||
Issue: "2968"
|
|
||||||
PR: "5859"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Proto logging messages
|
|
||||||
time: 2022-08-17T15:48:57.225267-04:00
|
|
||||||
custom:
|
|
||||||
Author: gshank
|
|
||||||
Issue: "5610"
|
|
||||||
PR: "5643"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Friendlier error messages when packages.yml is malformed
|
|
||||||
time: 2022-09-12T12:59:35.121188+01:00
|
|
||||||
custom:
|
|
||||||
Author: jared-rimmer
|
|
||||||
Issue: "5486"
|
|
||||||
PR: "5812"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Migrate dbt-utils current_timestamp macros into core + adapters
|
|
||||||
time: 2022-09-14T09:56:25.97818-07:00
|
|
||||||
custom:
|
|
||||||
Author: colin-rogers-dbt
|
|
||||||
Issue: "5521"
|
|
||||||
PR: "5838"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Allow partitions in external tables to be supplied as a list
|
|
||||||
time: 2022-09-25T21:16:51.051239654+02:00
|
|
||||||
custom:
|
|
||||||
Author: pgoslatara
|
|
||||||
Issue: "5929"
|
|
||||||
PR: "5930"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: extend -f flag shorthand for seed command
|
|
||||||
time: 2022-10-03T11:07:05.381632-05:00
|
|
||||||
custom:
|
|
||||||
Author: dave-connors-3
|
|
||||||
Issue: "5990"
|
|
||||||
PR: "5991"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: This pulls the profile name from args when constructing a RuntimeConfig in lib.py,
|
|
||||||
enabling the dbt-server to override the value that's in the dbt_project.yml
|
|
||||||
time: 2022-11-02T15:00:03.000805-05:00
|
|
||||||
custom:
|
|
||||||
Author: racheldaniel
|
|
||||||
Issue: "6201"
|
|
||||||
PR: "6202"
|
|
||||||
6
.changes/unreleased/Features-20230118-134804.yaml
Normal file
6
.changes/unreleased/Features-20230118-134804.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Features
|
||||||
|
body: Adding the entity node
|
||||||
|
time: 2023-01-18T13:48:04.487817-06:00
|
||||||
|
custom:
|
||||||
|
Author: callum-mcdata
|
||||||
|
Issue: "6627"
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Account for disabled flags on models in schema files more completely
|
|
||||||
time: 2022-09-16T10:48:54.162273-05:00
|
|
||||||
custom:
|
|
||||||
Author: emmyoop
|
|
||||||
Issue: "3992"
|
|
||||||
PR: "5868"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Add validation of enabled config for metrics, exposures and sources
|
|
||||||
time: 2022-10-10T11:32:18.752322-05:00
|
|
||||||
custom:
|
|
||||||
Author: emmyoop
|
|
||||||
Issue: "6030"
|
|
||||||
PR: "6038"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: check length of args of python model function before accessing it
|
|
||||||
time: 2022-10-11T16:07:15.464093-04:00
|
|
||||||
custom:
|
|
||||||
Author: chamini2
|
|
||||||
Issue: "6041"
|
|
||||||
PR: "6042"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Add functors to ensure event types with str-type attributes are initialized
|
|
||||||
to spec, even when provided non-str type params.
|
|
||||||
time: 2022-10-16T17:37:42.846683-07:00
|
|
||||||
custom:
|
|
||||||
Author: versusfacit
|
|
||||||
Issue: "5436"
|
|
||||||
PR: "5874"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Allow hooks to fail without halting execution flow
|
|
||||||
time: 2022-11-07T09:53:14.340257-06:00
|
|
||||||
custom:
|
|
||||||
Author: ChenyuLInx
|
|
||||||
Issue: "5625"
|
|
||||||
PR: "6059"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Put black config in explicit config
|
|
||||||
time: 2022-09-27T19:42:59.241433-07:00
|
|
||||||
custom:
|
|
||||||
Author: max-sixty
|
|
||||||
Issue: "5946"
|
|
||||||
PR: "5947"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Added flat_graph attribute the Manifest class's deepcopy() coverage
|
|
||||||
time: 2022-09-29T13:44:06.275941-04:00
|
|
||||||
custom:
|
|
||||||
Author: peterallenwebb
|
|
||||||
Issue: "5809"
|
|
||||||
PR: "5975"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Add mypy configs so `mypy` passes from CLI
|
|
||||||
time: 2022-10-05T12:03:10.061263-07:00
|
|
||||||
custom:
|
|
||||||
Author: max-sixty
|
|
||||||
Issue: "5983"
|
|
||||||
PR: "5983"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Exception message cleanup.
|
|
||||||
time: 2022-10-07T09:46:27.682872-05:00
|
|
||||||
custom:
|
|
||||||
Author: emmyoop
|
|
||||||
Issue: "6023"
|
|
||||||
PR: "6024"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Add dmypy cache to gitignore
|
|
||||||
time: 2022-10-07T14:00:44.227644-07:00
|
|
||||||
custom:
|
|
||||||
Author: max-sixty
|
|
||||||
Issue: "6028"
|
|
||||||
PR: "5978"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Provide useful errors when the value of 'materialized' is invalid
|
|
||||||
time: 2022-10-13T18:19:12.167548-04:00
|
|
||||||
custom:
|
|
||||||
Author: peterallenwebb
|
|
||||||
Issue: "5229"
|
|
||||||
PR: "6025"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Fixed extra whitespace in strings introduced by black.
|
|
||||||
time: 2022-10-17T15:15:11.499246-05:00
|
|
||||||
custom:
|
|
||||||
Author: luke-bassett
|
|
||||||
Issue: "1350"
|
|
||||||
PR: "6086"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Clean up string formatting
|
|
||||||
time: 2022-10-17T15:58:44.676549-04:00
|
|
||||||
custom:
|
|
||||||
Author: eve-johns
|
|
||||||
Issue: "6068"
|
|
||||||
PR: "6082"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Remove the 'root_path' field from most nodes
|
|
||||||
time: 2022-10-28T10:48:37.687886-04:00
|
|
||||||
custom:
|
|
||||||
Author: gshank
|
|
||||||
Issue: "6171"
|
|
||||||
PR: "6172"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Combine certain logging events with different levels
|
|
||||||
time: 2022-10-28T11:03:44.887836-04:00
|
|
||||||
custom:
|
|
||||||
Author: gshank
|
|
||||||
Issue: "6173"
|
|
||||||
PR: "6174"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Convert threading tests to pytest
|
|
||||||
time: 2022-11-08T07:45:50.589147-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5942"
|
|
||||||
PR: "6226"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Convert postgres index tests to pytest
|
|
||||||
time: 2022-11-08T11:56:33.743042-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5770"
|
|
||||||
PR: "6228"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Convert use color tests to pytest
|
|
||||||
time: 2022-11-08T13:31:04.788547-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5771"
|
|
||||||
PR: "6230"
|
|
||||||
6
.changes/unreleased/Under the Hood-20230113-132513.yaml
Normal file
6
.changes/unreleased/Under the Hood-20230113-132513.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Under the Hood
|
||||||
|
body: Fix use of ConnectionReused logging event
|
||||||
|
time: 2023-01-13T13:25:13.023168-05:00
|
||||||
|
custom:
|
||||||
|
Author: gshank
|
||||||
|
Issue: "6168"
|
||||||
6
.changes/unreleased/Under the Hood-20230117-111737.yaml
Normal file
6
.changes/unreleased/Under the Hood-20230117-111737.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Under the Hood
|
||||||
|
body: Update deprecated github action command
|
||||||
|
time: 2023-01-17T11:17:37.046095-06:00
|
||||||
|
custom:
|
||||||
|
Author: davidbloss
|
||||||
|
Issue: "6153"
|
||||||
107
.changie.yaml
107
.changie.yaml
@@ -6,19 +6,67 @@ changelogPath: CHANGELOG.md
|
|||||||
versionExt: md
|
versionExt: md
|
||||||
versionFormat: '## dbt-core {{.Version}} - {{.Time.Format "January 02, 2006"}}'
|
versionFormat: '## dbt-core {{.Version}} - {{.Time.Format "January 02, 2006"}}'
|
||||||
kindFormat: '### {{.Kind}}'
|
kindFormat: '### {{.Kind}}'
|
||||||
changeFormat: '- {{.Body}} ([#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-core/issues/{{.Custom.Issue}}), [#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-core/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $IssueList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.Issue }}
|
||||||
|
{{- range $issueNbr := $changes }}
|
||||||
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/issues/nbr)" | replace "nbr" $issueNbr }}
|
||||||
|
{{- $IssueList = append $IssueList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $IssueList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
|
|
||||||
kinds:
|
kinds:
|
||||||
- label: Breaking Changes
|
- label: Breaking Changes
|
||||||
- label: Features
|
- label: Features
|
||||||
- label: Fixes
|
- label: Fixes
|
||||||
- label: Docs
|
- label: Docs
|
||||||
changeFormat: '- {{.Body}} ([dbt-docs/#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-docs/issues/{{.Custom.Issue}}), [dbt-docs/#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-docs/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $IssueList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.Issue }}
|
||||||
|
{{- range $issueNbr := $changes }}
|
||||||
|
{{- $changeLink := "[dbt-docs/#nbr](https://github.com/dbt-labs/dbt-docs/issues/nbr)" | replace "nbr" $issueNbr }}
|
||||||
|
{{- $IssueList = append $IssueList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $IssueList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
- label: Under the Hood
|
- label: Under the Hood
|
||||||
- label: Dependencies
|
- label: Dependencies
|
||||||
changeFormat: '- {{.Body}} ({{if ne .Custom.Issue ""}}[#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-core/issues/{{.Custom.Issue}}), {{end}}[#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-core/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $PRList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.PR }}
|
||||||
|
{{- range $pullrequest := $changes }}
|
||||||
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/pull/nbr)" | replace "nbr" $pullrequest }}
|
||||||
|
{{- $PRList = append $PRList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $PRList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
|
skipGlobalChoices: true
|
||||||
|
additionalChoices:
|
||||||
|
- key: Author
|
||||||
|
label: GitHub Username(s) (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 3
|
||||||
|
- key: PR
|
||||||
|
label: GitHub Pull Request Number (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
- label: Security
|
- label: Security
|
||||||
changeFormat: '- {{.Body}} ({{if ne .Custom.Issue ""}}[#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-core/issues/{{.Custom.Issue}}), {{end}}[#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-core/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $PRList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.PR }}
|
||||||
|
{{- range $pullrequest := $changes }}
|
||||||
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/pull/nbr)" | replace "nbr" $pullrequest }}
|
||||||
|
{{- $PRList = append $PRList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $PRList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
|
skipGlobalChoices: true
|
||||||
|
additionalChoices:
|
||||||
|
- key: Author
|
||||||
|
label: GitHub Username(s) (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 3
|
||||||
|
- key: PR
|
||||||
|
label: GitHub Pull Request Number (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
|
||||||
newlines:
|
newlines:
|
||||||
afterChangelogHeader: 1
|
afterChangelogHeader: 1
|
||||||
@@ -33,42 +81,41 @@ custom:
|
|||||||
type: string
|
type: string
|
||||||
minLength: 3
|
minLength: 3
|
||||||
- key: Issue
|
- key: Issue
|
||||||
label: GitHub Issue Number
|
label: GitHub Issue Number (separated by a single space if multiple)
|
||||||
type: int
|
type: string
|
||||||
minInt: 1
|
minLength: 1
|
||||||
- key: PR
|
|
||||||
label: GitHub Pull Request Number
|
|
||||||
type: int
|
|
||||||
minInt: 1
|
|
||||||
|
|
||||||
footerFormat: |
|
footerFormat: |
|
||||||
{{- $contributorDict := dict }}
|
{{- $contributorDict := dict }}
|
||||||
{{- /* any names added to this list should be all lowercase for later matching purposes */}}
|
{{- /* any names added to this list should be all lowercase for later matching purposes */}}
|
||||||
{{- $core_team := list "michelleark" "peterallenwebb" "emmyoop" "nathaniel-may" "gshank" "leahwicz" "chenyulinx" "stu-k" "iknox-fa" "versusfacit" "mcknight-42" "jtcohen6" "dependabot[bot]" "snyk-bot" "colin-rogers-dbt" }}
|
{{- $core_team := list "michelleark" "peterallenwebb" "emmyoop" "nathaniel-may" "gshank" "leahwicz" "chenyulinx" "stu-k" "iknox-fa" "versusfacit" "mcknight-42" "jtcohen6" "aranke" "dependabot[bot]" "snyk-bot" "colin-rogers-dbt" }}
|
||||||
{{- range $change := .Changes }}
|
{{- range $change := .Changes }}
|
||||||
{{- $authorList := splitList " " $change.Custom.Author }}
|
{{- $authorList := splitList " " $change.Custom.Author }}
|
||||||
{{- /* loop through all authors for a PR */}}
|
{{- /* loop through all authors for a single changelog */}}
|
||||||
{{- range $author := $authorList }}
|
{{- range $author := $authorList }}
|
||||||
{{- $authorLower := lower $author }}
|
{{- $authorLower := lower $author }}
|
||||||
{{- /* we only want to include non-core team contributors */}}
|
{{- /* we only want to include non-core team contributors */}}
|
||||||
{{- if not (has $authorLower $core_team)}}
|
{{- if not (has $authorLower $core_team)}}
|
||||||
{{- /* Docs kind link back to dbt-docs instead of dbt-core PRs */}}
|
{{- $changeList := splitList " " $change.Custom.Author }}
|
||||||
{{- $prLink := $change.Kind }}
|
{{- /* Docs kind link back to dbt-docs instead of dbt-core issues */}}
|
||||||
{{- if eq $change.Kind "Docs" }}
|
{{- $changeLink := $change.Kind }}
|
||||||
{{- $prLink = "[dbt-docs/#pr](https://github.com/dbt-labs/dbt-docs/pull/pr)" | replace "pr" $change.Custom.PR }}
|
{{- if or (eq $change.Kind "Dependencies") (eq $change.Kind "Security") }}
|
||||||
{{- else }}
|
{{- $changeLink = "[#nbr](https://github.com/dbt-labs/dbt-core/pull/nbr)" | replace "nbr" $change.Custom.PR }}
|
||||||
{{- $prLink = "[#pr](https://github.com/dbt-labs/dbt-core/pull/pr)" | replace "pr" $change.Custom.PR }}
|
{{- else if eq $change.Kind "Docs"}}
|
||||||
{{- end }}
|
{{- $changeLink = "[dbt-docs/#nbr](https://github.com/dbt-labs/dbt-docs/issues/nbr)" | replace "nbr" $change.Custom.Issue }}
|
||||||
{{- /* check if this contributor has other PRs associated with them already */}}
|
{{- else }}
|
||||||
{{- if hasKey $contributorDict $author }}
|
{{- $changeLink = "[#nbr](https://github.com/dbt-labs/dbt-core/issues/nbr)" | replace "nbr" $change.Custom.Issue }}
|
||||||
{{- $prList := get $contributorDict $author }}
|
{{- end }}
|
||||||
{{- $prList = append $prList $prLink }}
|
{{- /* check if this contributor has other changes associated with them already */}}
|
||||||
{{- $contributorDict := set $contributorDict $author $prList }}
|
{{- if hasKey $contributorDict $author }}
|
||||||
{{- else }}
|
{{- $contributionList := get $contributorDict $author }}
|
||||||
{{- $prList := list $prLink }}
|
{{- $contributionList = append $contributionList $changeLink }}
|
||||||
{{- $contributorDict := set $contributorDict $author $prList }}
|
{{- $contributorDict := set $contributorDict $author $contributionList }}
|
||||||
{{- end }}
|
{{- else }}
|
||||||
{{- end}}
|
{{- $contributionList := list $changeLink }}
|
||||||
|
{{- $contributorDict := set $contributorDict $author $contributionList }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end}}
|
||||||
{{- end}}
|
{{- end}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- /* no indentation here for formatting so the final markdown doesn't have unneeded indentations */}}
|
{{- /* no indentation here for formatting so the final markdown doesn't have unneeded indentations */}}
|
||||||
|
|||||||
2
.flake8
2
.flake8
@@ -9,4 +9,4 @@ ignore =
|
|||||||
E203 # makes Flake8 work like black
|
E203 # makes Flake8 work like black
|
||||||
E741
|
E741
|
||||||
E501 # long line checking is done in black
|
E501 # long line checking is done in black
|
||||||
exclude = test
|
exclude = test/
|
||||||
|
|||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
core/dbt/include/index.html binary
|
||||||
|
tests/functional/artifacts/data/state/*/manifest.json binary
|
||||||
20
.github/_README.md
vendored
20
.github/_README.md
vendored
@@ -63,12 +63,12 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
```
|
```
|
||||||
|
|
||||||
### Secrets
|
### Secrets
|
||||||
- When to use a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) vs the [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) generated for the action?
|
- When to use a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) vs the [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) generated for the action?
|
||||||
|
|
||||||
The `GITHUB_TOKEN` is used by default. In most cases it is sufficient for what you need.
|
The `GITHUB_TOKEN` is used by default. In most cases it is sufficient for what you need.
|
||||||
|
|
||||||
If you expect the workflow to result in a commit to that should retrigger workflows, you will need to use a Personal Access Token for the bot to commit the file. When using the GITHUB_TOKEN, the resulting commit will not trigger another GitHub Actions Workflow run. This is due to limitations set by GitHub. See [the docs](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) for a more detailed explanation.
|
If you expect the workflow to result in a commit to that should retrigger workflows, you will need to use a Personal Access Token for the bot to commit the file. When using the GITHUB_TOKEN, the resulting commit will not trigger another GitHub Actions Workflow run. This is due to limitations set by GitHub. See [the docs](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) for a more detailed explanation.
|
||||||
|
|
||||||
For example, we must use a PAT in our workflow to commit a new changelog yaml file for bot PRs. Once the file has been committed to the branch, it should retrigger the check to validate that a changelog exists on the PR. Otherwise, it would stay in a failed state since the check would never retrigger.
|
For example, we must use a PAT in our workflow to commit a new changelog yaml file for bot PRs. Once the file has been committed to the branch, it should retrigger the check to validate that a changelog exists on the PR. Otherwise, it would stay in a failed state since the check would never retrigger.
|
||||||
@@ -105,7 +105,7 @@ Some triggers of note that we use:
|
|||||||
|
|
||||||
```
|
```
|
||||||
# **what?**
|
# **what?**
|
||||||
# Describe what the action does.
|
# Describe what the action does.
|
||||||
|
|
||||||
# **why?**
|
# **why?**
|
||||||
# Why does this action exist?
|
# Why does this action exist?
|
||||||
@@ -138,7 +138,7 @@ Some triggers of note that we use:
|
|||||||
id: fp
|
id: fp
|
||||||
run: |
|
run: |
|
||||||
FILEPATH=.changes/unreleased/Dependencies-${{ steps.filename_time.outputs.time }}.yaml
|
FILEPATH=.changes/unreleased/Dependencies-${{ steps.filename_time.outputs.time }}.yaml
|
||||||
echo "::set-output name=FILEPATH::$FILEPATH"
|
echo "FILEPATH=$FILEPATH" >> $GITHUB_OUTPUT
|
||||||
```
|
```
|
||||||
|
|
||||||
- Print out all variables you will reference as the first step of a job. This allows for easier debugging. The first job should log all inputs. Subsequent jobs should reference outputs of other jobs, if present.
|
- Print out all variables you will reference as the first step of a job. This allows for easier debugging. The first job should log all inputs. Subsequent jobs should reference outputs of other jobs, if present.
|
||||||
@@ -158,14 +158,14 @@ Some triggers of note that we use:
|
|||||||
echo "The build_script_path: ${{ inputs.build_script_path }}"
|
echo "The build_script_path: ${{ inputs.build_script_path }}"
|
||||||
echo "The s3_bucket_name: ${{ inputs.s3_bucket_name }}"
|
echo "The s3_bucket_name: ${{ inputs.s3_bucket_name }}"
|
||||||
echo "The package_test_command: ${{ inputs.package_test_command }}"
|
echo "The package_test_command: ${{ inputs.package_test_command }}"
|
||||||
|
|
||||||
# collect all the variables that need to be used in subsequent jobs
|
# collect all the variables that need to be used in subsequent jobs
|
||||||
- name: Set Variables
|
- name: Set Variables
|
||||||
id: variables
|
id: variables
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=important_path::'performance/runner/Cargo.toml'"
|
echo "important_path='performance/runner/Cargo.toml'" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=release_id::${{github.event.inputs.release_id}}"
|
echo "release_id=${{github.event.inputs.release_id}}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=open_prs::${{github.event.inputs.open_prs}}"
|
echo "open_prs=${{github.event.inputs.open_prs}}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
job2:
|
job2:
|
||||||
needs: [job1]
|
needs: [job1]
|
||||||
@@ -190,7 +190,7 @@ ___
|
|||||||
### Actions from the Marketplace
|
### Actions from the Marketplace
|
||||||
- Don’t use external actions for things that can easily be accomplished manually.
|
- Don’t use external actions for things that can easily be accomplished manually.
|
||||||
- Always read through what an external action does before using it! Often an action in the GitHub Actions Marketplace can be replaced with a few lines in bash. This is much more maintainable (and won’t change under us) and clear as to what’s actually happening. It also prevents any
|
- Always read through what an external action does before using it! Often an action in the GitHub Actions Marketplace can be replaced with a few lines in bash. This is much more maintainable (and won’t change under us) and clear as to what’s actually happening. It also prevents any
|
||||||
- Pin actions _we don't control_ to tags.
|
- Pin actions _we don't control_ to tags.
|
||||||
|
|
||||||
### Connecting to AWS
|
### Connecting to AWS
|
||||||
- Authenticate with the aws managed workflow
|
- Authenticate with the aws managed workflow
|
||||||
@@ -208,7 +208,7 @@ ___
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Copy Artifacts from S3 via CLI
|
- name: Copy Artifacts from S3 via CLI
|
||||||
run: aws s3 cp ${{ env.s3_bucket }} . --recursive
|
run: aws s3 cp ${{ env.s3_bucket }} . --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|||||||
17
.github/actions/latest-wrangler/main.py
vendored
17
.github/actions/latest-wrangler/main.py
vendored
@@ -28,11 +28,12 @@ if __name__ == "__main__":
|
|||||||
if package_request.status_code == 404:
|
if package_request.status_code == 404:
|
||||||
if halt_on_missing:
|
if halt_on_missing:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
# everything is the latest if the package doesn't exist
|
||||||
# everything is the latest if the package doesn't exist
|
github_output = os.environ.get("GITHUB_OUTPUT")
|
||||||
print(f"::set-output name=latest::{True}")
|
with open(github_output, "at", encoding="utf-8") as gh_output:
|
||||||
print(f"::set-output name=minor_latest::{True}")
|
gh_output.write("latest=True")
|
||||||
sys.exit(0)
|
gh_output.write("minor_latest=True")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# TODO: verify package meta is "correct"
|
# TODO: verify package meta is "correct"
|
||||||
# https://github.com/dbt-labs/dbt-core/issues/4640
|
# https://github.com/dbt-labs/dbt-core/issues/4640
|
||||||
@@ -91,5 +92,7 @@ if __name__ == "__main__":
|
|||||||
latest = is_latest(pre_rel, new_version, current_latest)
|
latest = is_latest(pre_rel, new_version, current_latest)
|
||||||
minor_latest = is_latest(pre_rel, new_version, current_minor_latest)
|
minor_latest = is_latest(pre_rel, new_version, current_minor_latest)
|
||||||
|
|
||||||
print(f"::set-output name=latest::{latest}")
|
github_output = os.environ.get("GITHUB_OUTPUT")
|
||||||
print(f"::set-output name=minor_latest::{minor_latest}")
|
with open(github_output, "at", encoding="utf-8") as gh_output:
|
||||||
|
gh_output.write(f"latest={latest}")
|
||||||
|
gh_output.write(f"minor_latest={minor_latest}")
|
||||||
|
|||||||
4
.github/workflows/bot-changelog.yml
vendored
4
.github/workflows/bot-changelog.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- label: "dependencies"
|
- label: "dependencies"
|
||||||
changie_kind: "Dependency"
|
changie_kind: "Dependencies"
|
||||||
- label: "snyk"
|
- label: "snyk"
|
||||||
changie_kind: "Security"
|
changie_kind: "Security"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -58,4 +58,4 @@ jobs:
|
|||||||
commit_message: "Add automated changelog yaml from template for bot PR"
|
commit_message: "Add automated changelog yaml from template for bot PR"
|
||||||
changie_kind: ${{ matrix.changie_kind }}
|
changie_kind: ${{ matrix.changie_kind }}
|
||||||
label: ${{ matrix.label }}
|
label: ${{ matrix.label }}
|
||||||
custom_changelog_string: "custom:\n Author: ${{ github.event.pull_request.user.login }}\n Issue: 4904\n PR: ${{ github.event.pull_request.number }}"
|
custom_changelog_string: "custom:\n Author: ${{ github.event.pull_request.user.login }}\n PR: ${{ github.event.pull_request.number }}"
|
||||||
|
|||||||
165
.github/workflows/generate-cli-api-docs.yml
vendored
Normal file
165
.github/workflows/generate-cli-api-docs.yml
vendored
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# **what?**
|
||||||
|
# On push, if anything in core/dbt/docs or core/dbt/cli has been
|
||||||
|
# created or modified, regenerate the CLI API docs using sphinx.
|
||||||
|
|
||||||
|
# **why?**
|
||||||
|
# We watch for changes in core/dbt/cli because the CLI API docs rely on click
|
||||||
|
# and all supporting flags/params to be generated. We watch for changes in
|
||||||
|
# core/dbt/docs since any changes to sphinx configuration or any of the
|
||||||
|
# .rst files there could result in a differently build final index.html file.
|
||||||
|
|
||||||
|
# **when?**
|
||||||
|
# Whenever a change has been pushed to a branch, and only if there is a diff
|
||||||
|
# between the PR branch and main's core/dbt/cli and or core/dbt/docs dirs.
|
||||||
|
|
||||||
|
# TODO: add bot comment to PR informing contributor that the docs have been committed
|
||||||
|
# TODO: figure out why github action triggered pushes cause github to fail to report
|
||||||
|
# the status of jobs
|
||||||
|
|
||||||
|
name: Generate CLI API docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
CLI_DIR: ${{ github.workspace }}/core/dbt/cli
|
||||||
|
DOCS_DIR: ${{ github.workspace }}/core/dbt/docs
|
||||||
|
DOCS_BUILD_DIR: ${{ github.workspace }}/core/dbt/docs/build
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_gen:
|
||||||
|
name: check if generation needed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.pull_request.head.repo.fork == false }}
|
||||||
|
outputs:
|
||||||
|
cli_dir_changed: ${{ steps.check_cli.outputs.cli_dir_changed }}
|
||||||
|
docs_dir_changed: ${{ steps.check_docs.outputs.docs_dir_changed }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "[DEBUG] print variables"
|
||||||
|
run: |
|
||||||
|
echo "env.CLI_DIR: ${{ env.CLI_DIR }}"
|
||||||
|
echo "env.DOCS_BUILD_DIR: ${{ env.DOCS_BUILD_DIR }}"
|
||||||
|
echo "env.DOCS_DIR: ${{ env.DOCS_DIR }}"
|
||||||
|
|
||||||
|
- name: git checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: set shas
|
||||||
|
id: set_shas
|
||||||
|
run: |
|
||||||
|
THIS_SHA=$(git rev-parse @)
|
||||||
|
LAST_SHA=$(git rev-parse @~1)
|
||||||
|
|
||||||
|
echo "this sha: $THIS_SHA"
|
||||||
|
echo "last sha: $LAST_SHA"
|
||||||
|
|
||||||
|
echo "this_sha=$THIS_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "last_sha=$LAST_SHA" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: check for changes in core/dbt/cli
|
||||||
|
id: check_cli
|
||||||
|
run: |
|
||||||
|
CLI_DIR_CHANGES=$(git diff \
|
||||||
|
${{ steps.set_shas.outputs.last_sha }} \
|
||||||
|
${{ steps.set_shas.outputs.this_sha }} \
|
||||||
|
-- ${{ env.CLI_DIR }})
|
||||||
|
|
||||||
|
if [ -n "$CLI_DIR_CHANGES" ]; then
|
||||||
|
echo "changes found"
|
||||||
|
echo $CLI_DIR_CHANGES
|
||||||
|
echo "cli_dir_changed=true" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "cli_dir_changed=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "no changes found"
|
||||||
|
|
||||||
|
- name: check for changes in core/dbt/docs
|
||||||
|
id: check_docs
|
||||||
|
if: steps.check_cli.outputs.cli_dir_changed == 'false'
|
||||||
|
run: |
|
||||||
|
DOCS_DIR_CHANGES=$(git diff --name-only \
|
||||||
|
${{ steps.set_shas.outputs.last_sha }} \
|
||||||
|
${{ steps.set_shas.outputs.this_sha }} \
|
||||||
|
-- ${{ env.DOCS_DIR }} ':!${{ env.DOCS_BUILD_DIR }}')
|
||||||
|
|
||||||
|
DOCS_BUILD_DIR_CHANGES=$(git diff --name-only \
|
||||||
|
${{ steps.set_shas.outputs.last_sha }} \
|
||||||
|
${{ steps.set_shas.outputs.this_sha }} \
|
||||||
|
-- ${{ env.DOCS_BUILD_DIR }})
|
||||||
|
|
||||||
|
if [ -n "$DOCS_DIR_CHANGES" ] && [ -z "$DOCS_BUILD_DIR_CHANGES" ]; then
|
||||||
|
echo "changes found"
|
||||||
|
echo $DOCS_DIR_CHANGES
|
||||||
|
echo "docs_dir_changed=true" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "docs_dir_changed=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "no changes found"
|
||||||
|
|
||||||
|
gen_docs:
|
||||||
|
name: generate docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [check_gen]
|
||||||
|
if: |
|
||||||
|
needs.check_gen.outputs.cli_dir_changed == 'true'
|
||||||
|
|| needs.check_gen.outputs.docs_dir_changed == 'true'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "[DEBUG] print variables"
|
||||||
|
run: |
|
||||||
|
echo "env.DOCS_DIR: ${{ env.DOCS_DIR }}"
|
||||||
|
echo "github head_ref: ${{ github.head_ref }}"
|
||||||
|
|
||||||
|
- name: git checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: install python
|
||||||
|
uses: actions/setup-python@v4.3.0
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
- name: install dev requirements
|
||||||
|
run: |
|
||||||
|
python3 -m venv env
|
||||||
|
source env/bin/activate
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt -r dev-requirements.txt
|
||||||
|
|
||||||
|
- name: generate docs
|
||||||
|
run: |
|
||||||
|
source env/bin/activate
|
||||||
|
cd ${{ env.DOCS_DIR }}
|
||||||
|
|
||||||
|
echo "cleaning existing docs"
|
||||||
|
make clean
|
||||||
|
|
||||||
|
echo "creating docs"
|
||||||
|
make html
|
||||||
|
|
||||||
|
- name: debug
|
||||||
|
run: |
|
||||||
|
echo ">>>>> status"
|
||||||
|
git status
|
||||||
|
echo ">>>>> remotes"
|
||||||
|
git remote -v
|
||||||
|
echo ">>>>> branch"
|
||||||
|
git branch -v
|
||||||
|
echo ">>>>> log"
|
||||||
|
git log --pretty=oneline | head -5
|
||||||
|
|
||||||
|
- name: commit docs
|
||||||
|
run: |
|
||||||
|
git config user.name 'Github Build Bot'
|
||||||
|
git config user.email 'buildbot@fishtownanalytics.com'
|
||||||
|
git commit -am "Add generated CLI API docs"
|
||||||
|
git push -u origin ${{ github.head_ref }}
|
||||||
14
.github/workflows/main.yml
vendored
14
.github/workflows/main.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TOXENV: "unit"
|
TOXENV: "unit"
|
||||||
@@ -101,7 +101,9 @@ jobs:
|
|||||||
- name: Get current date
|
- name: Get current date
|
||||||
if: always()
|
if: always()
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%dT%H_%M_%S')" #no colons allowed for artifacts
|
run: |
|
||||||
|
CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts
|
||||||
|
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: always()
|
if: always()
|
||||||
@@ -118,8 +120,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-20.04]
|
||||||
include:
|
include:
|
||||||
- python-version: 3.8
|
- python-version: 3.8
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
@@ -168,7 +170,9 @@ jobs:
|
|||||||
- name: Get current date
|
- name: Get current date
|
||||||
if: always()
|
if: always()
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y_%m_%dT%H_%M_%S')" #no colons allowed for artifacts
|
run: |
|
||||||
|
CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts
|
||||||
|
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
12
.github/workflows/release-docker.yml
vendored
12
.github/workflows/release-docker.yml
vendored
@@ -41,9 +41,9 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
IFS="." read -r MAJOR MINOR PATCH <<< ${{ github.event.inputs.version_number }}
|
IFS="." read -r MAJOR MINOR PATCH <<< ${{ github.event.inputs.version_number }}
|
||||||
echo "::set-output name=major::$MAJOR"
|
echo "major=$MAJOR" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=minor::$MINOR"
|
echo "minor=$MINOR" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=patch::$PATCH"
|
echo "patch=$PATCH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Is pkg 'latest'
|
- name: Is pkg 'latest'
|
||||||
id: latest
|
id: latest
|
||||||
@@ -70,8 +70,10 @@ jobs:
|
|||||||
- name: Get docker build arg
|
- name: Get docker build arg
|
||||||
id: build_arg
|
id: build_arg
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=build_arg_name::"$(echo ${{ github.event.inputs.package }} | sed 's/\-/_/g')
|
BUILD_ARG_NAME=$(echo ${{ github.event.inputs.package }} | sed 's/\-/_/g')
|
||||||
echo "::set-output name=build_arg_value::"$(echo ${{ github.event.inputs.package }} | sed 's/postgres/core/g')
|
BUILD_ARG_VALUE=$(echo ${{ github.event.inputs.package }} | sed 's/postgres/core/g')
|
||||||
|
echo "build_arg_name=$BUILD_ARG_NAME" >> $GITHUB_OUTPUT
|
||||||
|
echo "build_arg_value=$BUILD_ARG_VALUE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Log in to the GHCR
|
- name: Log in to the GHCR
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -165,7 +165,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_PRERELEASE: ${{ contains(github.event.inputs.version_number, 'rc') || contains(github.event.inputs.version_number, 'b') }}
|
IS_PRERELEASE: ${{ contains(github.event.inputs.version_number, 'rc') || contains(github.event.inputs.version_number, 'b') }}
|
||||||
run: |
|
run: |
|
||||||
echo ::set-output name=isPrerelease::$IS_PRERELEASE
|
echo "isPrerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Creating GitHub Release
|
- name: Creating GitHub Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
|
|||||||
11
.github/workflows/stale.yml
vendored
11
.github/workflows/stale.yml
vendored
@@ -9,13 +9,4 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
uses: dbt-labs/actions/.github/workflows/stale-bot-matrix.yml@main
|
||||||
steps:
|
|
||||||
# pinned at v4 (https://github.com/actions/stale/releases/tag/v4.0.0)
|
|
||||||
- uses: actions/stale@cdf15f641adb27a71842045a94023bef6945e3aa
|
|
||||||
with:
|
|
||||||
stale-issue-message: "This issue has been marked as Stale because it has been open for 180 days with no activity. If you would like the issue to remain open, please remove the stale label or comment on the issue, or it will be closed in 7 days."
|
|
||||||
stale-pr-message: "This PR has been marked as Stale because it has been open for 180 days with no activity. If you would like the PR to remain open, please remove the stale label or comment on the PR, or it will be closed in 7 days."
|
|
||||||
close-issue-message: "Although we are closing this issue as stale, it's not gone forever. Issues can be reopened if there is renewed community interest; add a comment to notify the maintainers."
|
|
||||||
# mark issues/PRs stale when they haven't seen activity in 180 days
|
|
||||||
days-before-stale: 180
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
# run the performance measurements on the current or default branch
|
# run the performance measurements on the current or default branch
|
||||||
test-schema:
|
test-schema:
|
||||||
name: Test Log Schema
|
name: Test Log Schema
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
# turns warnings into errors
|
# turns warnings into errors
|
||||||
RUSTFLAGS: "-D warnings"
|
RUSTFLAGS: "-D warnings"
|
||||||
|
|||||||
2
.github/workflows/version-bump.yml
vendored
2
.github/workflows/version-bump.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
|||||||
- name: Set branch value
|
- name: Set branch value
|
||||||
id: variables
|
id: variables
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=BRANCH_NAME::prep-release/${{ github.event.inputs.version_number }}_$GITHUB_RUN_ID"
|
echo "BRANCH_NAME=prep-release/${{ github.event.inputs.version_number }}_$GITHUB_RUN_ID" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create PR branch
|
- name: Create PR branch
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ __pycache__/
|
|||||||
env*/
|
env*/
|
||||||
dbt_env/
|
dbt_env/
|
||||||
build/
|
build/
|
||||||
|
!core/dbt/docs/build
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Eventually the hooks described here will be run as tests before merging each PR.
|
# Eventually the hooks described here will be run as tests before merging each PR.
|
||||||
|
|
||||||
# TODO: remove global exclusion of tests when testing overhaul is complete
|
# TODO: remove global exclusion of tests when testing overhaul is complete
|
||||||
exclude: ^test/
|
exclude: ^(test/|core/dbt/docs/build/)
|
||||||
|
|
||||||
# Force all unspecified python hooks to run python 3.8
|
# Force all unspecified python hooks to run python 3.8
|
||||||
default_language_version:
|
default_language_version:
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
- "Breaking changes" listed under a version may require action from end users or external maintainers when upgrading to that version.
|
- "Breaking changes" listed under a version may require action from end users or external maintainers when upgrading to that version.
|
||||||
- Do not edit this file directly. This file is auto-generated using [changie](https://github.com/miniscruff/changie). For details on how to document a change, see [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-changelog-entry)
|
- Do not edit this file directly. This file is auto-generated using [changie](https://github.com/miniscruff/changie). For details on how to document a change, see [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-changelog-entry)
|
||||||
|
|
||||||
|
|
||||||
## Previous Releases
|
## Previous Releases
|
||||||
|
|
||||||
For information on prior major and minor releases, see their changelogs:
|
For information on prior major and minor releases, see their changelogs:
|
||||||
|
|
||||||
|
|
||||||
|
* [1.4](https://github.com/dbt-labs/dbt-core/blob/1.4.latest/CHANGELOG.md)
|
||||||
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
||||||
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
||||||
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ There are some tools that will be helpful to you in developing locally. While th
|
|||||||
|
|
||||||
These are the tools used in `dbt-core` development and testing:
|
These are the tools used in `dbt-core` development and testing:
|
||||||
|
|
||||||
- [`tox`](https://tox.readthedocs.io/en/latest/) to manage virtualenvs across python versions. We currently target the latest patch releases for Python 3.7, 3.8, 3.9, and 3.10
|
- [`tox`](https://tox.readthedocs.io/en/latest/) to manage virtualenvs across python versions. We currently target the latest patch releases for Python 3.7, 3.8, 3.9, 3.10 and 3.11
|
||||||
- [`pytest`](https://docs.pytest.org/en/latest/) to define, discover, and run tests
|
- [`pytest`](https://docs.pytest.org/en/latest/) to define, discover, and run tests
|
||||||
- [`flake8`](https://flake8.pycqa.org/en/latest/) for code linting
|
- [`flake8`](https://flake8.pycqa.org/en/latest/) for code linting
|
||||||
- [`black`](https://github.com/psf/black) for code formatting
|
- [`black`](https://github.com/psf/black) for code formatting
|
||||||
@@ -96,12 +96,15 @@ brew install postgresql
|
|||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
First make sure that you set up your `virtualenv` as described in [Setting up an environment](#setting-up-an-environment). Also ensure you have the latest version of pip installed with `pip install --upgrade pip`. Next, install `dbt-core` (and its dependencies) with:
|
First make sure that you set up your `virtualenv` as described in [Setting up an environment](#setting-up-an-environment). Also ensure you have the latest version of pip installed with `pip install --upgrade pip`. Next, install `dbt-core` (and its dependencies):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make dev
|
make dev
|
||||||
# or
|
```
|
||||||
|
or, alternatively:
|
||||||
|
```sh
|
||||||
pip install -r dev-requirements.txt -r editable-requirements.txt
|
pip install -r dev-requirements.txt -r editable-requirements.txt
|
||||||
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
When installed in this way, any changes you make to your local copy of the source code will be reflected immediately in your next `dbt` run.
|
When installed in this way, any changes you make to your local copy of the source code will be reflected immediately in your next `dbt` run.
|
||||||
@@ -160,7 +163,7 @@ suites.
|
|||||||
|
|
||||||
#### `tox`
|
#### `tox`
|
||||||
|
|
||||||
[`tox`](https://tox.readthedocs.io/en/latest/) takes care of managing virtualenvs and install dependencies in order to run tests. You can also run tests in parallel, for example, you can run unit tests for Python 3.7, Python 3.8, Python 3.9, and Python 3.10 checks in parallel with `tox -p`. Also, you can run unit tests for specific python versions with `tox -e py37`. The configuration for these tests in located in `tox.ini`.
|
[`tox`](https://tox.readthedocs.io/en/latest/) takes care of managing virtualenvs and install dependencies in order to run tests. You can also run tests in parallel, for example, you can run unit tests for Python 3.7, Python 3.8, Python 3.9, Python 3.10 and Python 3.11 checks in parallel with `tox -p`. Also, you can run unit tests for specific python versions with `tox -e py37`. The configuration for these tests in located in `tox.ini`.
|
||||||
|
|
||||||
#### `pytest`
|
#### `pytest`
|
||||||
|
|
||||||
@@ -201,13 +204,21 @@ Here are some general rules for adding tests:
|
|||||||
* Sometimes flake8 complains about lines that are actually fine, in which case you can put a comment on the line such as: # noqa or # noqa: ANNN, where ANNN is the error code that flake8 issues.
|
* Sometimes flake8 complains about lines that are actually fine, in which case you can put a comment on the line such as: # noqa or # noqa: ANNN, where ANNN is the error code that flake8 issues.
|
||||||
* To collect output for `CProfile`, run dbt with the `-r` option and the name of an output file, i.e. `dbt -r dbt.cprof run`. If you just want to profile parsing, you can do: `dbt -r dbt.cprof parse`. `pip` install `snakeviz` to view the output. Run `snakeviz dbt.cprof` and output will be rendered in a browser window.
|
* To collect output for `CProfile`, run dbt with the `-r` option and the name of an output file, i.e. `dbt -r dbt.cprof run`. If you just want to profile parsing, you can do: `dbt -r dbt.cprof parse`. `pip` install `snakeviz` to view the output. Run `snakeviz dbt.cprof` and output will be rendered in a browser window.
|
||||||
|
|
||||||
## Adding a CHANGELOG Entry
|
## Adding or modifying a CHANGELOG Entry
|
||||||
|
|
||||||
We use [changie](https://changie.dev) to generate `CHANGELOG` entries. **Note:** Do not edit the `CHANGELOG.md` directly. Your modifications will be lost.
|
We use [changie](https://changie.dev) to generate `CHANGELOG` entries. **Note:** Do not edit the `CHANGELOG.md` directly. Your modifications will be lost.
|
||||||
|
|
||||||
Follow the steps to [install `changie`](https://changie.dev/guide/installation/) for your system.
|
Follow the steps to [install `changie`](https://changie.dev/guide/installation/) for your system.
|
||||||
|
|
||||||
Once changie is installed and your PR is created, simply run `changie new` and changie will walk you through the process of creating a changelog entry. Commit the file that's created and your changelog entry is complete!
|
Once changie is installed and your PR is created for a new feature, simply run the following command and changie will walk you through the process of creating a changelog entry:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
changie new
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit the file that's created and your changelog entry is complete!
|
||||||
|
|
||||||
|
If you are contributing to a feature already in progress, you will modify the changie yaml file in dbt/.changes/unreleased/ related to your change. If you need help finding this file, please ask within the discussion for the pull request!
|
||||||
|
|
||||||
You don't need to worry about which `dbt-core` version your change will go into. Just create the changelog entry with `changie`, and open your PR against the `main` branch. All merged changes will be included in the next minor version of `dbt-core`. The Core maintainers _may_ choose to "backport" specific changes in order to patch older minor versions. In that case, a maintainer will take care of that backport after merging your PR, before releasing the new version of `dbt-core`.
|
You don't need to worry about which `dbt-core` version your change will go into. Just create the changelog entry with `changie`, and open your PR against the `main` branch. All merged changes will be included in the next minor version of `dbt-core`. The Core maintainers _may_ choose to "backport" specific changes in order to patch older minor versions. In that case, a maintainer will take care of that backport after merging your PR, before releasing the new version of `dbt-core`.
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ RUN apt-get update \
|
|||||||
python3.10 \
|
python3.10 \
|
||||||
python3.10-dev \
|
python3.10-dev \
|
||||||
python3.10-venv \
|
python3.10-venv \
|
||||||
|
python3.11 \
|
||||||
|
python3.11-dev \
|
||||||
|
python3.11-venv \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
|||||||
9
Makefile
9
Makefile
@@ -19,11 +19,16 @@ CI_FLAGS =\
|
|||||||
LOG_DIR=./logs\
|
LOG_DIR=./logs\
|
||||||
DBT_LOG_FORMAT=json
|
DBT_LOG_FORMAT=json
|
||||||
|
|
||||||
.PHONY: dev
|
.PHONY: dev_req
|
||||||
dev: ## Installs dbt-* packages in develop mode along with development dependencies.
|
dev_req: ## Installs dbt-* packages in develop mode along with only development dependencies.
|
||||||
@\
|
@\
|
||||||
pip install -r dev-requirements.txt -r editable-requirements.txt
|
pip install -r dev-requirements.txt -r editable-requirements.txt
|
||||||
|
|
||||||
|
.PHONY: dev
|
||||||
|
dev: dev_req ## Installs dbt-* packages in develop mode along with development dependencies and pre-commit.
|
||||||
|
@\
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
.PHONY: mypy
|
.PHONY: mypy
|
||||||
mypy: .env ## Runs mypy against staged changes for static type checking.
|
mypy: .env ## Runs mypy against staged changes for static type checking.
|
||||||
@\
|
@\
|
||||||
|
|||||||
@@ -2,50 +2,59 @@
|
|||||||
|
|
||||||
## The following are individual files in this directory.
|
## The following are individual files in this directory.
|
||||||
|
|
||||||
### deprecations.py
|
|
||||||
|
|
||||||
### flags.py
|
|
||||||
|
|
||||||
### main.py
|
|
||||||
|
|
||||||
### tracking.py
|
|
||||||
|
|
||||||
### version.py
|
|
||||||
|
|
||||||
### lib.py
|
|
||||||
|
|
||||||
### node_types.py
|
|
||||||
|
|
||||||
### helper_types.py
|
|
||||||
|
|
||||||
### links.py
|
|
||||||
|
|
||||||
### semver.py
|
|
||||||
|
|
||||||
### ui.py
|
|
||||||
|
|
||||||
### compilation.py
|
### compilation.py
|
||||||
|
|
||||||
|
### constants.py
|
||||||
|
|
||||||
### dataclass_schema.py
|
### dataclass_schema.py
|
||||||
|
|
||||||
|
### deprecations.py
|
||||||
|
|
||||||
### exceptions.py
|
### exceptions.py
|
||||||
|
|
||||||
|
### flags.py
|
||||||
|
|
||||||
|
### helper_types.py
|
||||||
|
|
||||||
### hooks.py
|
### hooks.py
|
||||||
|
|
||||||
|
### lib.py
|
||||||
|
|
||||||
|
### links.py
|
||||||
|
|
||||||
### logger.py
|
### logger.py
|
||||||
|
|
||||||
|
### main.py
|
||||||
|
|
||||||
|
### node_types.py
|
||||||
|
|
||||||
### profiler.py
|
### profiler.py
|
||||||
|
|
||||||
|
### selected_resources.py
|
||||||
|
|
||||||
|
### semver.py
|
||||||
|
|
||||||
|
### tracking.py
|
||||||
|
|
||||||
|
### ui.py
|
||||||
|
|
||||||
### utils.py
|
### utils.py
|
||||||
|
|
||||||
|
### version.py
|
||||||
|
|
||||||
|
|
||||||
## The subdirectories will be documented in a README in the subdirectory
|
## The subdirectories will be documented in a README in the subdirectory
|
||||||
* config
|
|
||||||
* include
|
|
||||||
* adapters
|
* adapters
|
||||||
* context
|
* cli
|
||||||
* deps
|
|
||||||
* graph
|
|
||||||
* task
|
|
||||||
* clients
|
* clients
|
||||||
|
* config
|
||||||
|
* context
|
||||||
|
* contracts
|
||||||
|
* deps
|
||||||
|
* docs
|
||||||
* events
|
* events
|
||||||
|
* graph
|
||||||
|
* include
|
||||||
|
* parser
|
||||||
|
* task
|
||||||
|
* tests
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|||||||
import re
|
import re
|
||||||
from typing import Dict, ClassVar, Any, Optional
|
from typing import Dict, ClassVar, Any, Optional
|
||||||
|
|
||||||
from dbt.exceptions import RuntimeException
|
from dbt.exceptions import DbtRuntimeError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -85,7 +85,7 @@ class Column:
|
|||||||
|
|
||||||
def string_size(self) -> int:
|
def string_size(self) -> int:
|
||||||
if not self.is_string():
|
if not self.is_string():
|
||||||
raise RuntimeException("Called string_size() on non-string field!")
|
raise DbtRuntimeError("Called string_size() on non-string field!")
|
||||||
|
|
||||||
if self.dtype == "text" or self.char_size is None:
|
if self.dtype == "text" or self.char_size is None:
|
||||||
# char_size should never be None. Handle it reasonably just in case
|
# char_size should never be None. Handle it reasonably just in case
|
||||||
@@ -124,7 +124,7 @@ class Column:
|
|||||||
def from_description(cls, name: str, raw_data_type: str) -> "Column":
|
def from_description(cls, name: str, raw_data_type: str) -> "Column":
|
||||||
match = re.match(r"([^(]+)(\([^)]+\))?", raw_data_type)
|
match = re.match(r"([^(]+)(\([^)]+\))?", raw_data_type)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise RuntimeException(f'Could not interpret data type "{raw_data_type}"')
|
raise DbtRuntimeError(f'Could not interpret data type "{raw_data_type}"')
|
||||||
data_type, size_info = match.groups()
|
data_type, size_info = match.groups()
|
||||||
char_size = None
|
char_size = None
|
||||||
numeric_precision = None
|
numeric_precision = None
|
||||||
@@ -137,7 +137,7 @@ class Column:
|
|||||||
try:
|
try:
|
||||||
char_size = int(parts[0])
|
char_size = int(parts[0])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f'Could not interpret data_type "{raw_data_type}": '
|
f'Could not interpret data_type "{raw_data_type}": '
|
||||||
f'could not convert "{parts[0]}" to an integer'
|
f'could not convert "{parts[0]}" to an integer'
|
||||||
)
|
)
|
||||||
@@ -145,14 +145,14 @@ class Column:
|
|||||||
try:
|
try:
|
||||||
numeric_precision = int(parts[0])
|
numeric_precision = int(parts[0])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f'Could not interpret data_type "{raw_data_type}": '
|
f'Could not interpret data_type "{raw_data_type}": '
|
||||||
f'could not convert "{parts[0]}" to an integer'
|
f'could not convert "{parts[0]}" to an integer'
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
numeric_scale = int(parts[1])
|
numeric_scale = int(parts[1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f'Could not interpret data_type "{raw_data_type}": '
|
f'Could not interpret data_type "{raw_data_type}": '
|
||||||
f'could not convert "{parts[1]}" to an integer'
|
f'could not convert "{parts[1]}" to an integer'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from dbt.events.types import (
|
|||||||
Rollback,
|
Rollback,
|
||||||
RollbackFailed,
|
RollbackFailed,
|
||||||
)
|
)
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from dbt import flags
|
from dbt import flags
|
||||||
from dbt.utils import cast_to_str
|
from dbt.utils import cast_to_str
|
||||||
|
|
||||||
@@ -90,13 +91,13 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
key = self.get_thread_identifier()
|
key = self.get_thread_identifier()
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if key not in self.thread_connections:
|
if key not in self.thread_connections:
|
||||||
raise dbt.exceptions.InvalidConnectionException(key, list(self.thread_connections))
|
raise dbt.exceptions.InvalidConnectionError(key, list(self.thread_connections))
|
||||||
return self.thread_connections[key]
|
return self.thread_connections[key]
|
||||||
|
|
||||||
def set_thread_connection(self, conn: Connection) -> None:
|
def set_thread_connection(self, conn: Connection) -> None:
|
||||||
key = self.get_thread_identifier()
|
key = self.get_thread_identifier()
|
||||||
if key in self.thread_connections:
|
if key in self.thread_connections:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
"In set_thread_connection, existing connection exists for {}"
|
"In set_thread_connection, existing connection exists for {}"
|
||||||
)
|
)
|
||||||
self.thread_connections[key] = conn
|
self.thread_connections[key] = conn
|
||||||
@@ -136,47 +137,49 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
:return: A context manager that handles exceptions raised by the
|
:return: A context manager that handles exceptions raised by the
|
||||||
underlying database.
|
underlying database.
|
||||||
"""
|
"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError(
|
||||||
"`exception_handler` is not implemented for this adapter!"
|
"`exception_handler` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_connection_name(self, name: Optional[str] = None) -> Connection:
|
def set_connection_name(self, name: Optional[str] = None) -> Connection:
|
||||||
conn_name: str
|
"""Called by 'acquire_connection' in BaseAdapter, which is called by
|
||||||
if name is None:
|
'connection_named', called by 'connection_for(node)'.
|
||||||
# if a name isn't specified, we'll re-use a single handle
|
Creates a connection for this thread if one doesn't already
|
||||||
# named 'master'
|
exist, and will rename an existing connection."""
|
||||||
conn_name = "master"
|
|
||||||
else:
|
|
||||||
if not isinstance(name, str):
|
|
||||||
raise dbt.exceptions.CompilerException(
|
|
||||||
f"For connection name, got {name} - not a string!"
|
|
||||||
)
|
|
||||||
assert isinstance(name, str)
|
|
||||||
conn_name = name
|
|
||||||
|
|
||||||
|
conn_name: str = "master" if name is None else name
|
||||||
|
|
||||||
|
# Get a connection for this thread
|
||||||
conn = self.get_if_exists()
|
conn = self.get_if_exists()
|
||||||
|
|
||||||
|
if conn and conn.name == conn_name and conn.state == "open":
|
||||||
|
# Found a connection and nothing to do, so just return it
|
||||||
|
return conn
|
||||||
|
|
||||||
if conn is None:
|
if conn is None:
|
||||||
|
# Create a new connection
|
||||||
conn = Connection(
|
conn = Connection(
|
||||||
type=Identifier(self.TYPE),
|
type=Identifier(self.TYPE),
|
||||||
name=None,
|
name=conn_name,
|
||||||
state=ConnectionState.INIT,
|
state=ConnectionState.INIT,
|
||||||
transaction_open=False,
|
transaction_open=False,
|
||||||
handle=None,
|
handle=None,
|
||||||
credentials=self.profile.credentials,
|
credentials=self.profile.credentials,
|
||||||
)
|
)
|
||||||
self.set_thread_connection(conn)
|
|
||||||
|
|
||||||
if conn.name == conn_name and conn.state == "open":
|
|
||||||
return conn
|
|
||||||
|
|
||||||
fire_event(NewConnection(conn_name=conn_name, conn_type=self.TYPE))
|
|
||||||
|
|
||||||
if conn.state == "open":
|
|
||||||
fire_event(ConnectionReused(conn_name=conn_name))
|
|
||||||
else:
|
|
||||||
conn.handle = LazyHandle(self.open)
|
conn.handle = LazyHandle(self.open)
|
||||||
|
# Add the connection to thread_connections for this thread
|
||||||
|
self.set_thread_connection(conn)
|
||||||
|
fire_event(
|
||||||
|
NewConnection(conn_name=conn_name, conn_type=self.TYPE, node_info=get_node_info())
|
||||||
|
)
|
||||||
|
else: # existing connection either wasn't open or didn't have the right name
|
||||||
|
if conn.state != "open":
|
||||||
|
conn.handle = LazyHandle(self.open)
|
||||||
|
if conn.name != conn_name:
|
||||||
|
orig_conn_name: str = conn.name or ""
|
||||||
|
conn.name = conn_name
|
||||||
|
fire_event(ConnectionReused(orig_conn_name=orig_conn_name, conn_name=conn_name))
|
||||||
|
|
||||||
conn.name = conn_name
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -208,7 +211,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
connect should trigger a retry.
|
connect should trigger a retry.
|
||||||
:type retryable_exceptions: Iterable[Type[Exception]]
|
:type retryable_exceptions: Iterable[Type[Exception]]
|
||||||
:param int retry_limit: How many times to retry the call to connect. If this limit
|
:param int retry_limit: How many times to retry the call to connect. If this limit
|
||||||
is exceeded before a successful call, a FailedToConnectException will be raised.
|
is exceeded before a successful call, a FailedToConnectError will be raised.
|
||||||
Must be non-negative.
|
Must be non-negative.
|
||||||
:param retry_timeout: Time to wait between attempts to connect. Can also take a
|
:param retry_timeout: Time to wait between attempts to connect. Can also take a
|
||||||
Callable that takes the number of attempts so far, beginning at 0, and returns an int
|
Callable that takes the number of attempts so far, beginning at 0, and returns an int
|
||||||
@@ -217,14 +220,14 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
:param int _attempts: Parameter used to keep track of the number of attempts in calling the
|
:param int _attempts: Parameter used to keep track of the number of attempts in calling the
|
||||||
connect function across recursive calls. Passed as an argument to retry_timeout if it
|
connect function across recursive calls. Passed as an argument to retry_timeout if it
|
||||||
is a Callable. This parameter should not be set by the initial caller.
|
is a Callable. This parameter should not be set by the initial caller.
|
||||||
:raises dbt.exceptions.FailedToConnectException: Upon exhausting all retry attempts without
|
:raises dbt.exceptions.FailedToConnectError: Upon exhausting all retry attempts without
|
||||||
successfully acquiring a handle.
|
successfully acquiring a handle.
|
||||||
:return: The given connection with its appropriate state and handle attributes set
|
:return: The given connection with its appropriate state and handle attributes set
|
||||||
depending on whether we successfully acquired a handle or not.
|
depending on whether we successfully acquired a handle or not.
|
||||||
"""
|
"""
|
||||||
timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
|
timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
|
||||||
if timeout < 0:
|
if timeout < 0:
|
||||||
raise dbt.exceptions.FailedToConnectException(
|
raise dbt.exceptions.FailedToConnectError(
|
||||||
"retry_timeout cannot be negative or return a negative time."
|
"retry_timeout cannot be negative or return a negative time."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -232,7 +235,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
# This guard is not perfect others may add to the recursion limit (e.g. built-ins).
|
# This guard is not perfect others may add to the recursion limit (e.g. built-ins).
|
||||||
connection.handle = None
|
connection.handle = None
|
||||||
connection.state = ConnectionState.FAIL
|
connection.state = ConnectionState.FAIL
|
||||||
raise dbt.exceptions.FailedToConnectException("retry_limit cannot be negative")
|
raise dbt.exceptions.FailedToConnectError("retry_limit cannot be negative")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
connection.handle = connect()
|
connection.handle = connect()
|
||||||
@@ -243,7 +246,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
if retry_limit <= 0:
|
if retry_limit <= 0:
|
||||||
connection.handle = None
|
connection.handle = None
|
||||||
connection.state = ConnectionState.FAIL
|
connection.state = ConnectionState.FAIL
|
||||||
raise dbt.exceptions.FailedToConnectException(str(e))
|
raise dbt.exceptions.FailedToConnectError(str(e))
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
|
f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
|
||||||
@@ -265,12 +268,12 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
connection.handle = None
|
connection.handle = None
|
||||||
connection.state = ConnectionState.FAIL
|
connection.state = ConnectionState.FAIL
|
||||||
raise dbt.exceptions.FailedToConnectException(str(e))
|
raise dbt.exceptions.FailedToConnectError(str(e))
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def cancel_open(self) -> Optional[List[str]]:
|
def cancel_open(self) -> Optional[List[str]]:
|
||||||
"""Cancel all open connections on the adapter. (passable)"""
|
"""Cancel all open connections on the adapter. (passable)"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError(
|
||||||
"`cancel_open` is not implemented for this adapter!"
|
"`cancel_open` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -285,7 +288,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
This should be thread-safe, or hold the lock if necessary. The given
|
This should be thread-safe, or hold the lock if necessary. The given
|
||||||
connection should not be in either in_use or available.
|
connection should not be in either in_use or available.
|
||||||
"""
|
"""
|
||||||
raise dbt.exceptions.NotImplementedException("`open` is not implemented for this adapter!")
|
raise dbt.exceptions.NotImplementedError("`open` is not implemented for this adapter!")
|
||||||
|
|
||||||
def release(self) -> None:
|
def release(self) -> None:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -317,16 +320,12 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def begin(self) -> None:
|
def begin(self) -> None:
|
||||||
"""Begin a transaction. (passable)"""
|
"""Begin a transaction. (passable)"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`begin` is not implemented for this adapter!")
|
||||||
"`begin` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def commit(self) -> None:
|
def commit(self) -> None:
|
||||||
"""Commit a transaction. (passable)"""
|
"""Commit a transaction. (passable)"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`commit` is not implemented for this adapter!")
|
||||||
"`commit` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _rollback_handle(cls, connection: Connection) -> None:
|
def _rollback_handle(cls, connection: Connection) -> None:
|
||||||
@@ -336,7 +335,9 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
except Exception:
|
except Exception:
|
||||||
fire_event(
|
fire_event(
|
||||||
RollbackFailed(
|
RollbackFailed(
|
||||||
conn_name=cast_to_str(connection.name), exc_info=traceback.format_exc()
|
conn_name=cast_to_str(connection.name),
|
||||||
|
exc_info=traceback.format_exc(),
|
||||||
|
node_info=get_node_info(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -345,21 +346,27 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
"""Perform the actual close operation."""
|
"""Perform the actual close operation."""
|
||||||
# On windows, sometimes connection handles don't have a close() attr.
|
# On windows, sometimes connection handles don't have a close() attr.
|
||||||
if hasattr(connection.handle, "close"):
|
if hasattr(connection.handle, "close"):
|
||||||
fire_event(ConnectionClosed(conn_name=cast_to_str(connection.name)))
|
fire_event(
|
||||||
|
ConnectionClosed(conn_name=cast_to_str(connection.name), node_info=get_node_info())
|
||||||
|
)
|
||||||
connection.handle.close()
|
connection.handle.close()
|
||||||
else:
|
else:
|
||||||
fire_event(ConnectionLeftOpen(conn_name=cast_to_str(connection.name)))
|
fire_event(
|
||||||
|
ConnectionLeftOpen(
|
||||||
|
conn_name=cast_to_str(connection.name), node_info=get_node_info()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _rollback(cls, connection: Connection) -> None:
|
def _rollback(cls, connection: Connection) -> None:
|
||||||
"""Roll back the given connection."""
|
"""Roll back the given connection."""
|
||||||
if connection.transaction_open is False:
|
if connection.transaction_open is False:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
f"Tried to rollback transaction on connection "
|
f"Tried to rollback transaction on connection "
|
||||||
f'"{connection.name}", but it does not have one open!'
|
f'"{connection.name}", but it does not have one open!'
|
||||||
)
|
)
|
||||||
|
|
||||||
fire_event(Rollback(conn_name=cast_to_str(connection.name)))
|
fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
|
||||||
cls._rollback_handle(connection)
|
cls._rollback_handle(connection)
|
||||||
|
|
||||||
connection.transaction_open = False
|
connection.transaction_open = False
|
||||||
@@ -371,7 +378,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
return connection
|
return connection
|
||||||
|
|
||||||
if connection.transaction_open and connection.handle:
|
if connection.transaction_open and connection.handle:
|
||||||
fire_event(Rollback(conn_name=cast_to_str(connection.name)))
|
fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
|
||||||
cls._rollback_handle(connection)
|
cls._rollback_handle(connection)
|
||||||
connection.transaction_open = False
|
connection.transaction_open = False
|
||||||
|
|
||||||
@@ -404,6 +411,4 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
:return: A tuple of the query status and results (empty if fetch=False).
|
:return: A tuple of the query status and results (empty if fetch=False).
|
||||||
:rtype: Tuple[AdapterResponse, agate.Table]
|
:rtype: Tuple[AdapterResponse, agate.Table]
|
||||||
"""
|
"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`execute` is not implemented for this adapter!")
|
||||||
"`execute` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from typing import (
|
|||||||
List,
|
List,
|
||||||
Mapping,
|
Mapping,
|
||||||
Iterator,
|
Iterator,
|
||||||
Union,
|
|
||||||
Set,
|
Set,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,13 +22,20 @@ import agate
|
|||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
raise_database_error,
|
DbtInternalError,
|
||||||
raise_compiler_error,
|
MacroArgTypeError,
|
||||||
invalid_type_error,
|
MacroResultError,
|
||||||
get_relation_returned_multiple_results,
|
QuoteConfigTypeError,
|
||||||
InternalException,
|
NotImplementedError,
|
||||||
NotImplementedException,
|
NullRelationCacheAttemptedError,
|
||||||
RuntimeException,
|
NullRelationDropAttemptedError,
|
||||||
|
RelationReturnedMultipleResultsError,
|
||||||
|
RenameToNoneAttemptedError,
|
||||||
|
DbtRuntimeError,
|
||||||
|
SnapshotTargetIncompleteError,
|
||||||
|
SnapshotTargetNotSnapshotTableError,
|
||||||
|
UnexpectedNullError,
|
||||||
|
UnexpectedNonTimestampError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from dbt.adapters.protocol import (
|
from dbt.adapters.protocol import (
|
||||||
@@ -38,9 +44,8 @@ from dbt.adapters.protocol import (
|
|||||||
)
|
)
|
||||||
from dbt.clients.agate_helper import empty_table, merge_tables, table_from_rows
|
from dbt.clients.agate_helper import empty_table, merge_tables, table_from_rows
|
||||||
from dbt.clients.jinja import MacroGenerator
|
from dbt.clients.jinja import MacroGenerator
|
||||||
from dbt.contracts.graph.compiled import CompileResultNode, CompiledSeedNode
|
|
||||||
from dbt.contracts.graph.manifest import Manifest, MacroManifest
|
from dbt.contracts.graph.manifest import Manifest, MacroManifest
|
||||||
from dbt.contracts.graph.parsed import ParsedSeedNode
|
from dbt.contracts.graph.nodes import ResultNode
|
||||||
from dbt.events.functions import fire_event, warn_or_error
|
from dbt.events.functions import fire_event, warn_or_error
|
||||||
from dbt.events.types import (
|
from dbt.events.types import (
|
||||||
CacheMiss,
|
CacheMiss,
|
||||||
@@ -64,16 +69,13 @@ from dbt.adapters.base import Credentials
|
|||||||
from dbt.adapters.cache import RelationsCache, _make_ref_key_msg
|
from dbt.adapters.cache import RelationsCache, _make_ref_key_msg
|
||||||
|
|
||||||
|
|
||||||
SeedModel = Union[ParsedSeedNode, CompiledSeedNode]
|
|
||||||
|
|
||||||
|
|
||||||
GET_CATALOG_MACRO_NAME = "get_catalog"
|
GET_CATALOG_MACRO_NAME = "get_catalog"
|
||||||
FRESHNESS_MACRO_NAME = "collect_freshness"
|
FRESHNESS_MACRO_NAME = "collect_freshness"
|
||||||
|
|
||||||
|
|
||||||
def _expect_row_value(key: str, row: agate.Row):
|
def _expect_row_value(key: str, row: agate.Row):
|
||||||
if key not in row.keys():
|
if key not in row.keys():
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
'Got a row without "{}" column, columns: {}'.format(key, row.keys())
|
'Got a row without "{}" column, columns: {}'.format(key, row.keys())
|
||||||
)
|
)
|
||||||
return row[key]
|
return row[key]
|
||||||
@@ -102,18 +104,10 @@ def _utc(dt: Optional[datetime], source: BaseRelation, field_name: str) -> datet
|
|||||||
assume the datetime is already for UTC and add the timezone.
|
assume the datetime is already for UTC and add the timezone.
|
||||||
"""
|
"""
|
||||||
if dt is None:
|
if dt is None:
|
||||||
raise raise_database_error(
|
raise UnexpectedNullError(field_name, source)
|
||||||
"Expected a non-null value when querying field '{}' of table "
|
|
||||||
" {} but received value 'null' instead".format(field_name, source)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif not hasattr(dt, "tzinfo"):
|
elif not hasattr(dt, "tzinfo"):
|
||||||
raise raise_database_error(
|
raise UnexpectedNonTimestampError(field_name, source, dt)
|
||||||
"Expected a timestamp value when querying field '{}' of table "
|
|
||||||
"{} but received value of type '{}' instead".format(
|
|
||||||
field_name, source, type(dt).__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif dt.tzinfo:
|
elif dt.tzinfo:
|
||||||
return dt.astimezone(pytz.UTC)
|
return dt.astimezone(pytz.UTC)
|
||||||
@@ -243,9 +237,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
return conn.name
|
return conn.name
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def connection_named(
|
def connection_named(self, name: str, node: Optional[ResultNode] = None) -> Iterator[None]:
|
||||||
self, name: str, node: Optional[CompileResultNode] = None
|
|
||||||
) -> Iterator[None]:
|
|
||||||
try:
|
try:
|
||||||
if self.connections.query_header is not None:
|
if self.connections.query_header is not None:
|
||||||
self.connections.query_header.set(name, node)
|
self.connections.query_header.set(name, node)
|
||||||
@@ -257,7 +249,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
self.connections.query_header.reset()
|
self.connections.query_header.reset()
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def connection_for(self, node: CompileResultNode) -> Iterator[None]:
|
def connection_for(self, node: ResultNode) -> Iterator[None]:
|
||||||
with self.connection_named(node.unique_id, node):
|
with self.connection_named(node.unique_id, node):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@@ -372,7 +364,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
lowercase strings.
|
lowercase strings.
|
||||||
"""
|
"""
|
||||||
info_schema_name_map = SchemaSearchMap()
|
info_schema_name_map = SchemaSearchMap()
|
||||||
nodes: Iterator[CompileResultNode] = chain(
|
nodes: Iterator[ResultNode] = chain(
|
||||||
[
|
[
|
||||||
node
|
node
|
||||||
for node in manifest.nodes.values()
|
for node in manifest.nodes.values()
|
||||||
@@ -441,7 +433,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
"""Cache a new relation in dbt. It will show up in `list relations`."""
|
"""Cache a new relation in dbt. It will show up in `list relations`."""
|
||||||
if relation is None:
|
if relation is None:
|
||||||
name = self.nice_connection_name()
|
name = self.nice_connection_name()
|
||||||
raise_compiler_error("Attempted to cache a null relation for {}".format(name))
|
raise NullRelationCacheAttemptedError(name)
|
||||||
self.cache.add(relation)
|
self.cache.add(relation)
|
||||||
# so jinja doesn't render things
|
# so jinja doesn't render things
|
||||||
return ""
|
return ""
|
||||||
@@ -453,7 +445,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
"""
|
"""
|
||||||
if relation is None:
|
if relation is None:
|
||||||
name = self.nice_connection_name()
|
name = self.nice_connection_name()
|
||||||
raise_compiler_error("Attempted to drop a null relation for {}".format(name))
|
raise NullRelationDropAttemptedError(name)
|
||||||
self.cache.drop(relation)
|
self.cache.drop(relation)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -470,9 +462,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
name = self.nice_connection_name()
|
name = self.nice_connection_name()
|
||||||
src_name = _relation_name(from_relation)
|
src_name = _relation_name(from_relation)
|
||||||
dst_name = _relation_name(to_relation)
|
dst_name = _relation_name(to_relation)
|
||||||
raise_compiler_error(
|
raise RenameToNoneAttemptedError(src_name, dst_name, name)
|
||||||
"Attempted to rename {} to {} for {}".format(src_name, dst_name, name)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache.rename(from_relation, to_relation)
|
self.cache.rename(from_relation, to_relation)
|
||||||
return ""
|
return ""
|
||||||
@@ -484,12 +474,12 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def date_function(cls) -> str:
|
def date_function(cls) -> str:
|
||||||
"""Get the date function used by this adapter's database."""
|
"""Get the date function used by this adapter's database."""
|
||||||
raise NotImplementedException("`date_function` is not implemented for this adapter!")
|
raise NotImplementedError("`date_function` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def is_cancelable(cls) -> bool:
|
def is_cancelable(cls) -> bool:
|
||||||
raise NotImplementedException("`is_cancelable` is not implemented for this adapter!")
|
raise NotImplementedError("`is_cancelable` is not implemented for this adapter!")
|
||||||
|
|
||||||
###
|
###
|
||||||
# Abstract methods about schemas
|
# Abstract methods about schemas
|
||||||
@@ -497,7 +487,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def list_schemas(self, database: str) -> List[str]:
|
def list_schemas(self, database: str) -> List[str]:
|
||||||
"""Get a list of existing schemas in database"""
|
"""Get a list of existing schemas in database"""
|
||||||
raise NotImplementedException("`list_schemas` is not implemented for this adapter!")
|
raise NotImplementedError("`list_schemas` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available.parse(lambda *a, **k: False)
|
@available.parse(lambda *a, **k: False)
|
||||||
def check_schema_exists(self, database: str, schema: str) -> bool:
|
def check_schema_exists(self, database: str, schema: str) -> bool:
|
||||||
@@ -520,13 +510,13 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
*Implementors must call self.cache.drop() to preserve cache state!*
|
*Implementors must call self.cache.drop() to preserve cache state!*
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`drop_relation` is not implemented for this adapter!")
|
raise NotImplementedError("`drop_relation` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
def truncate_relation(self, relation: BaseRelation) -> None:
|
def truncate_relation(self, relation: BaseRelation) -> None:
|
||||||
"""Truncate the given relation."""
|
"""Truncate the given relation."""
|
||||||
raise NotImplementedException("`truncate_relation` is not implemented for this adapter!")
|
raise NotImplementedError("`truncate_relation` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
@@ -535,15 +525,13 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
Implementors must call self.cache.rename() to preserve cache state.
|
Implementors must call self.cache.rename() to preserve cache state.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`rename_relation` is not implemented for this adapter!")
|
raise NotImplementedError("`rename_relation` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_list
|
@available.parse_list
|
||||||
def get_columns_in_relation(self, relation: BaseRelation) -> List[BaseColumn]:
|
def get_columns_in_relation(self, relation: BaseRelation) -> List[BaseColumn]:
|
||||||
"""Get a list of the columns in the given Relation."""
|
"""Get a list of the columns in the given Relation."""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError("`get_columns_in_relation` is not implemented for this adapter!")
|
||||||
"`get_columns_in_relation` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@available.deprecated("get_columns_in_relation", lambda *a, **k: [])
|
@available.deprecated("get_columns_in_relation", lambda *a, **k: [])
|
||||||
def get_columns_in_table(self, schema: str, identifier: str) -> List[BaseColumn]:
|
def get_columns_in_table(self, schema: str, identifier: str) -> List[BaseColumn]:
|
||||||
@@ -565,7 +553,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param self.Relation current: A relation that currently exists in the
|
:param self.Relation current: A relation that currently exists in the
|
||||||
database with columns of unspecified types.
|
database with columns of unspecified types.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError(
|
||||||
"`expand_target_column_types` is not implemented for this adapter!"
|
"`expand_target_column_types` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -580,7 +568,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:return: The relations in schema
|
:return: The relations in schema
|
||||||
:rtype: List[self.Relation]
|
:rtype: List[self.Relation]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError(
|
||||||
"`list_relations_without_caching` is not implemented for this adapter!"
|
"`list_relations_without_caching` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -622,7 +610,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
to_relation.
|
to_relation.
|
||||||
"""
|
"""
|
||||||
if not isinstance(from_relation, self.Relation):
|
if not isinstance(from_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="get_missing_columns",
|
method_name="get_missing_columns",
|
||||||
arg_name="from_relation",
|
arg_name="from_relation",
|
||||||
got_value=from_relation,
|
got_value=from_relation,
|
||||||
@@ -630,7 +618,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(to_relation, self.Relation):
|
if not isinstance(to_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="get_missing_columns",
|
method_name="get_missing_columns",
|
||||||
arg_name="to_relation",
|
arg_name="to_relation",
|
||||||
got_value=to_relation,
|
got_value=to_relation,
|
||||||
@@ -651,11 +639,11 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
expected columns.
|
expected columns.
|
||||||
|
|
||||||
:param Relation relation: The relation to check
|
:param Relation relation: The relation to check
|
||||||
:raises CompilationException: If the columns are
|
:raises InvalidMacroArgType: If the columns are
|
||||||
incorrect.
|
incorrect.
|
||||||
"""
|
"""
|
||||||
if not isinstance(relation, self.Relation):
|
if not isinstance(relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="valid_snapshot_target",
|
method_name="valid_snapshot_target",
|
||||||
arg_name="relation",
|
arg_name="relation",
|
||||||
got_value=relation,
|
got_value=relation,
|
||||||
@@ -676,24 +664,16 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
if extra:
|
if extra:
|
||||||
msg = (
|
raise SnapshotTargetIncompleteError(extra, missing)
|
||||||
'Snapshot target has ("{}") but not ("{}") - is it an '
|
|
||||||
"unmigrated previous version archive?".format(
|
|
||||||
'", "'.join(extra), '", "'.join(missing)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
msg = 'Snapshot target is not a snapshot table (missing "{}")'.format(
|
raise SnapshotTargetNotSnapshotTableError(missing)
|
||||||
'", "'.join(missing)
|
|
||||||
)
|
|
||||||
raise_compiler_error(msg)
|
|
||||||
|
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
def expand_target_column_types(
|
def expand_target_column_types(
|
||||||
self, from_relation: BaseRelation, to_relation: BaseRelation
|
self, from_relation: BaseRelation, to_relation: BaseRelation
|
||||||
) -> None:
|
) -> None:
|
||||||
if not isinstance(from_relation, self.Relation):
|
if not isinstance(from_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="expand_target_column_types",
|
method_name="expand_target_column_types",
|
||||||
arg_name="from_relation",
|
arg_name="from_relation",
|
||||||
got_value=from_relation,
|
got_value=from_relation,
|
||||||
@@ -701,7 +681,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(to_relation, self.Relation):
|
if not isinstance(to_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="expand_target_column_types",
|
method_name="expand_target_column_types",
|
||||||
arg_name="to_relation",
|
arg_name="to_relation",
|
||||||
got_value=to_relation,
|
got_value=to_relation,
|
||||||
@@ -783,7 +763,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
"schema": schema,
|
"schema": schema,
|
||||||
"database": database,
|
"database": database,
|
||||||
}
|
}
|
||||||
get_relation_returned_multiple_results(kwargs, matches)
|
raise RelationReturnedMultipleResultsError(kwargs, matches)
|
||||||
|
|
||||||
elif matches:
|
elif matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
@@ -805,20 +785,20 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
@available.parse_none
|
@available.parse_none
|
||||||
def create_schema(self, relation: BaseRelation):
|
def create_schema(self, relation: BaseRelation):
|
||||||
"""Create the given schema if it does not exist."""
|
"""Create the given schema if it does not exist."""
|
||||||
raise NotImplementedException("`create_schema` is not implemented for this adapter!")
|
raise NotImplementedError("`create_schema` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
def drop_schema(self, relation: BaseRelation):
|
def drop_schema(self, relation: BaseRelation):
|
||||||
"""Drop the given schema (and everything in it) if it exists."""
|
"""Drop the given schema (and everything in it) if it exists."""
|
||||||
raise NotImplementedException("`drop_schema` is not implemented for this adapter!")
|
raise NotImplementedError("`drop_schema` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available
|
@available
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def quote(cls, identifier: str) -> str:
|
def quote(cls, identifier: str) -> str:
|
||||||
"""Quote the given identifier, as appropriate for the database."""
|
"""Quote the given identifier, as appropriate for the database."""
|
||||||
raise NotImplementedException("`quote` is not implemented for this adapter!")
|
raise NotImplementedError("`quote` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available
|
@available
|
||||||
def quote_as_configured(self, identifier: str, quote_key: str) -> str:
|
def quote_as_configured(self, identifier: str, quote_key: str) -> str:
|
||||||
@@ -847,10 +827,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
elif quote_config is None:
|
elif quote_config is None:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise_compiler_error(
|
raise QuoteConfigTypeError(quote_config)
|
||||||
f'The seed configuration value of "quote_columns" has an '
|
|
||||||
f"invalid type {type(quote_config)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if quote_columns:
|
if quote_columns:
|
||||||
return self.quote(column)
|
return self.quote(column)
|
||||||
@@ -871,7 +848,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_text_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_text_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -883,7 +860,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_number_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_number_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -895,9 +872,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError("`convert_boolean_type` is not implemented for this adapter!")
|
||||||
"`convert_boolean_type` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -909,9 +884,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError("`convert_datetime_type` is not implemented for this adapter!")
|
||||||
"`convert_datetime_type` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -923,7 +896,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_date_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_date_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -935,7 +908,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_time_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_time_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available
|
@available
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1002,7 +975,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
else:
|
else:
|
||||||
package_name = 'the "{}" package'.format(project)
|
package_name = 'the "{}" package'.format(project)
|
||||||
|
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
'dbt could not find a macro with the name "{}" in {}'.format(
|
'dbt could not find a macro with the name "{}" in {}'.format(
|
||||||
macro_name, package_name
|
macro_name, package_name
|
||||||
)
|
)
|
||||||
@@ -1100,11 +1073,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
# now we have a 1-row table of the maximum `loaded_at_field` value and
|
# now we have a 1-row table of the maximum `loaded_at_field` value and
|
||||||
# the current time according to the db.
|
# the current time according to the db.
|
||||||
if len(table) != 1 or len(table[0]) != 2:
|
if len(table) != 1 or len(table[0]) != 2:
|
||||||
raise_compiler_error(
|
raise MacroResultError(FRESHNESS_MACRO_NAME, table)
|
||||||
'Got an invalid result from "{}" macro: {}'.format(
|
|
||||||
FRESHNESS_MACRO_NAME, [tuple(r) for r in table]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if table[0][0] is None:
|
if table[0][0] is None:
|
||||||
# no records in the table, so really the max_loaded_at was
|
# no records in the table, so really the max_loaded_at was
|
||||||
# infinitely long ago. Just call it 0:00 January 1 year UTC
|
# infinitely long ago. Just call it 0:00 January 1 year UTC
|
||||||
@@ -1181,7 +1150,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
elif location == "prepend":
|
elif location == "prepend":
|
||||||
return f"'{value}' || {add_to}"
|
return f"'{value}' || {add_to}"
|
||||||
else:
|
else:
|
||||||
raise RuntimeException(f'Got an unexpected location value of "{location}"')
|
raise DbtRuntimeError(f'Got an unexpected location value of "{location}"')
|
||||||
|
|
||||||
def get_rows_different_sql(
|
def get_rows_different_sql(
|
||||||
self,
|
self,
|
||||||
@@ -1239,7 +1208,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
return self.generate_python_submission_response(submission_result)
|
return self.generate_python_submission_response(submission_result)
|
||||||
|
|
||||||
def generate_python_submission_response(self, submission_result: Any) -> AdapterResponse:
|
def generate_python_submission_response(self, submission_result: Any) -> AdapterResponse:
|
||||||
raise NotImplementedException(
|
raise NotImplementedError(
|
||||||
"Your adapter need to implement generate_python_submission_response"
|
"Your adapter need to implement generate_python_submission_response"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1263,7 +1232,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
valid_strategies.append("default")
|
valid_strategies.append("default")
|
||||||
builtin_strategies = self.builtin_incremental_strategies()
|
builtin_strategies = self.builtin_incremental_strategies()
|
||||||
if strategy in builtin_strategies and strategy not in valid_strategies:
|
if strategy in builtin_strategies and strategy not in valid_strategies:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"The incremental strategy '{strategy}' is not valid for this adapter"
|
f"The incremental strategy '{strategy}' is not valid for this adapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1271,7 +1240,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
macro_name = f"get_incremental_{strategy}_sql"
|
macro_name = f"get_incremental_{strategy}_sql"
|
||||||
# The model_context should have MacroGenerator callable objects for all macros
|
# The model_context should have MacroGenerator callable objects for all macros
|
||||||
if macro_name not in model_context:
|
if macro_name not in model_context:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
'dbt could not find an incremental strategy macro with the name "{}" in {}'.format(
|
'dbt could not find an incremental strategy macro with the name "{}" in {}'.format(
|
||||||
macro_name, self.config.project_name
|
macro_name, self.config.project_name
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List, Optional, Type
|
from typing import List, Optional, Type
|
||||||
|
|
||||||
from dbt.adapters.base import Credentials
|
from dbt.adapters.base import Credentials
|
||||||
from dbt.exceptions import CompilationException
|
from dbt.exceptions import CompilationError
|
||||||
from dbt.adapters.protocol import AdapterProtocol
|
from dbt.adapters.protocol import AdapterProtocol
|
||||||
|
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ def project_name_from_path(include_path: str) -> str:
|
|||||||
|
|
||||||
partial = Project.partial_load(include_path)
|
partial = Project.partial_load(include_path)
|
||||||
if partial.project_name is None:
|
if partial.project_name is None:
|
||||||
raise CompilationException(f"Invalid project at {include_path}: name not set!")
|
raise CompilationError(f"Invalid project at {include_path}: name not set!")
|
||||||
return partial.project_name
|
return partial.project_name
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ from dbt.clients.jinja import QueryStringGenerator
|
|||||||
|
|
||||||
from dbt.context.manifest import generate_query_header_context
|
from dbt.context.manifest import generate_query_header_context
|
||||||
from dbt.contracts.connection import AdapterRequiredConfig, QueryComment
|
from dbt.contracts.connection import AdapterRequiredConfig, QueryComment
|
||||||
from dbt.contracts.graph.compiled import CompileResultNode
|
from dbt.contracts.graph.nodes import ResultNode
|
||||||
from dbt.contracts.graph.manifest import Manifest
|
from dbt.contracts.graph.manifest import Manifest
|
||||||
from dbt.exceptions import RuntimeException
|
from dbt.exceptions import DbtRuntimeError
|
||||||
|
|
||||||
|
|
||||||
class NodeWrapper:
|
class NodeWrapper:
|
||||||
@@ -48,7 +48,7 @@ class _QueryComment(local):
|
|||||||
if isinstance(comment, str) and "*/" in comment:
|
if isinstance(comment, str) and "*/" in comment:
|
||||||
# tell the user "no" so they don't hurt themselves by writing
|
# tell the user "no" so they don't hurt themselves by writing
|
||||||
# garbage
|
# garbage
|
||||||
raise RuntimeException(f'query comment contains illegal value "*/": {comment}')
|
raise DbtRuntimeError(f'query comment contains illegal value "*/": {comment}')
|
||||||
self.query_comment = comment
|
self.query_comment = comment
|
||||||
self.append = append
|
self.append = append
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class MacroQueryStringSetter:
|
|||||||
def reset(self):
|
def reset(self):
|
||||||
self.set("master", None)
|
self.set("master", None)
|
||||||
|
|
||||||
def set(self, name: str, node: Optional[CompileResultNode]):
|
def set(self, name: str, node: Optional[ResultNode]):
|
||||||
wrapped: Optional[NodeWrapper] = None
|
wrapped: Optional[NodeWrapper] = None
|
||||||
if node is not None:
|
if node is not None:
|
||||||
wrapped = NodeWrapper(node)
|
wrapped = NodeWrapper(node)
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from collections.abc import Hashable
|
from collections.abc import Hashable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional, TypeVar, Any, Type, Dict, Union, Iterator, Tuple, Set
|
from typing import Optional, TypeVar, Any, Type, Dict, Iterator, Tuple, Set
|
||||||
|
|
||||||
from dbt.contracts.graph.compiled import CompiledNode
|
from dbt.contracts.graph.nodes import SourceDefinition, ManifestNode, ResultNode, ParsedNode
|
||||||
from dbt.contracts.graph.parsed import ParsedSourceDefinition, ParsedNode
|
|
||||||
from dbt.contracts.relation import (
|
from dbt.contracts.relation import (
|
||||||
RelationType,
|
RelationType,
|
||||||
ComponentName,
|
ComponentName,
|
||||||
@@ -12,7 +11,11 @@ from dbt.contracts.relation import (
|
|||||||
Policy,
|
Policy,
|
||||||
Path,
|
Path,
|
||||||
)
|
)
|
||||||
from dbt.exceptions import InternalException
|
from dbt.exceptions import (
|
||||||
|
ApproximateMatchError,
|
||||||
|
DbtInternalError,
|
||||||
|
MultipleDatabasesNotAllowedError,
|
||||||
|
)
|
||||||
from dbt.node_types import NodeType
|
from dbt.node_types import NodeType
|
||||||
from dbt.utils import filter_null_values, deep_merge, classproperty
|
from dbt.utils import filter_null_values, deep_merge, classproperty
|
||||||
|
|
||||||
@@ -27,8 +30,10 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
path: Path
|
path: Path
|
||||||
type: Optional[RelationType] = None
|
type: Optional[RelationType] = None
|
||||||
quote_character: str = '"'
|
quote_character: str = '"'
|
||||||
include_policy: Policy = Policy()
|
# Python 3.11 requires that these use default_factory instead of simple default
|
||||||
quote_policy: Policy = Policy()
|
# ValueError: mutable default <class 'dbt.contracts.relation.Policy'> for field include_policy is not allowed: use default_factory
|
||||||
|
include_policy: Policy = field(default_factory=lambda: Policy())
|
||||||
|
quote_policy: Policy = field(default_factory=lambda: Policy())
|
||||||
dbt_created: bool = False
|
dbt_created: bool = False
|
||||||
|
|
||||||
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
|
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
|
||||||
@@ -39,9 +44,9 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_field_named(cls, field_name):
|
def _get_field_named(cls, field_name):
|
||||||
for field, _ in cls._get_fields():
|
for f, _ in cls._get_fields():
|
||||||
if field.name == field_name:
|
if f.name == field_name:
|
||||||
return field
|
return f
|
||||||
# this should be unreachable
|
# this should be unreachable
|
||||||
raise ValueError(f"BaseRelation has no {field_name} field!")
|
raise ValueError(f"BaseRelation has no {field_name} field!")
|
||||||
|
|
||||||
@@ -52,11 +57,11 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_quote_policy(cls) -> Policy:
|
def get_default_quote_policy(cls) -> Policy:
|
||||||
return cls._get_field_named("quote_policy").default
|
return cls._get_field_named("quote_policy").default_factory()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_include_policy(cls) -> Policy:
|
def get_default_include_policy(cls) -> Policy:
|
||||||
return cls._get_field_named("include_policy").default
|
return cls._get_field_named("include_policy").default_factory()
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"""Override `.get` to return a metadata object so we don't break
|
"""Override `.get` to return a metadata object so we don't break
|
||||||
@@ -82,7 +87,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
if not search:
|
if not search:
|
||||||
# nothing was passed in
|
# nothing was passed in
|
||||||
raise dbt.exceptions.RuntimeException(
|
raise dbt.exceptions.DbtRuntimeError(
|
||||||
"Tried to match relation, but no search path was passed!"
|
"Tried to match relation, but no search path was passed!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,7 +104,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
if approximate_match and not exact_match:
|
if approximate_match and not exact_match:
|
||||||
target = self.create(database=database, schema=schema, identifier=identifier)
|
target = self.create(database=database, schema=schema, identifier=identifier)
|
||||||
dbt.exceptions.approximate_relation_match(target, self)
|
raise ApproximateMatchError(target, self)
|
||||||
|
|
||||||
return exact_match
|
return exact_match
|
||||||
|
|
||||||
@@ -184,7 +189,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_source(cls: Type[Self], source: ParsedSourceDefinition, **kwargs: Any) -> Self:
|
def create_from_source(cls: Type[Self], source: SourceDefinition, **kwargs: Any) -> Self:
|
||||||
source_quoting = source.quoting.to_dict(omit_none=True)
|
source_quoting = source.quoting.to_dict(omit_none=True)
|
||||||
source_quoting.pop("column", None)
|
source_quoting.pop("column", None)
|
||||||
quote_policy = deep_merge(
|
quote_policy = deep_merge(
|
||||||
@@ -209,7 +214,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def create_ephemeral_from_node(
|
def create_ephemeral_from_node(
|
||||||
cls: Type[Self],
|
cls: Type[Self],
|
||||||
config: HasQuoting,
|
config: HasQuoting,
|
||||||
node: Union[ParsedNode, CompiledNode],
|
node: ManifestNode,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
# Note that ephemeral models are based on the name.
|
# Note that ephemeral models are based on the name.
|
||||||
identifier = cls.add_ephemeral_prefix(node.name)
|
identifier = cls.add_ephemeral_prefix(node.name)
|
||||||
@@ -222,7 +227,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def create_from_node(
|
def create_from_node(
|
||||||
cls: Type[Self],
|
cls: Type[Self],
|
||||||
config: HasQuoting,
|
config: HasQuoting,
|
||||||
node: Union[ParsedNode, CompiledNode],
|
node: ManifestNode,
|
||||||
quote_policy: Optional[Dict[str, bool]] = None,
|
quote_policy: Optional[Dict[str, bool]] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
@@ -243,20 +248,20 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def create_from(
|
def create_from(
|
||||||
cls: Type[Self],
|
cls: Type[Self],
|
||||||
config: HasQuoting,
|
config: HasQuoting,
|
||||||
node: Union[CompiledNode, ParsedNode, ParsedSourceDefinition],
|
node: ResultNode,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
if node.resource_type == NodeType.Source:
|
if node.resource_type == NodeType.Source:
|
||||||
if not isinstance(node, ParsedSourceDefinition):
|
if not isinstance(node, SourceDefinition):
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
"type mismatch, expected ParsedSourceDefinition but got {}".format(type(node))
|
"type mismatch, expected SourceDefinition but got {}".format(type(node))
|
||||||
)
|
)
|
||||||
return cls.create_from_source(node, **kwargs)
|
return cls.create_from_source(node, **kwargs)
|
||||||
else:
|
else:
|
||||||
if not isinstance(node, (ParsedNode, CompiledNode)):
|
# Can't use ManifestNode here because of parameterized generics
|
||||||
raise InternalException(
|
if not isinstance(node, (ParsedNode)):
|
||||||
"type mismatch, expected ParsedNode or CompiledNode but "
|
raise DbtInternalError(
|
||||||
"got {}".format(type(node))
|
f"type mismatch, expected ManifestNode but got {type(node)}"
|
||||||
)
|
)
|
||||||
return cls.create_from_node(config, node, **kwargs)
|
return cls.create_from_node(config, node, **kwargs)
|
||||||
|
|
||||||
@@ -353,7 +358,7 @@ class InformationSchema(BaseRelation):
|
|||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if not isinstance(self.information_schema_view, (type(None), str)):
|
if not isinstance(self.information_schema_view, (type(None), str)):
|
||||||
raise dbt.exceptions.CompilationException(
|
raise dbt.exceptions.CompilationError(
|
||||||
"Got an invalid name: {}".format(self.information_schema_view)
|
"Got an invalid name: {}".format(self.information_schema_view)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,7 +442,7 @@ class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
|
|||||||
if not allow_multiple_databases:
|
if not allow_multiple_databases:
|
||||||
seen = {r.database.lower() for r in self if r.database}
|
seen = {r.database.lower() for r in self if r.database}
|
||||||
if len(seen) > 1:
|
if len(seen) > 1:
|
||||||
dbt.exceptions.raise_compiler_error(str(seen))
|
raise MultipleDatabasesNotAllowedError(seen)
|
||||||
|
|
||||||
for information_schema_name, schema in self.search():
|
for information_schema_name, schema in self.search():
|
||||||
path = {"database": information_schema_name.database, "schema": schema}
|
path = {"database": information_schema_name.database, "schema": schema}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import re
|
|
||||||
import threading
|
import threading
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
||||||
@@ -9,23 +8,15 @@ from dbt.adapters.reference_keys import (
|
|||||||
_make_msg_from_ref_key,
|
_make_msg_from_ref_key,
|
||||||
_ReferenceKey,
|
_ReferenceKey,
|
||||||
)
|
)
|
||||||
import dbt.exceptions
|
from dbt.exceptions import (
|
||||||
from dbt.events.functions import fire_event, fire_event_if
|
DependentLinkNotCachedError,
|
||||||
from dbt.events.types import (
|
NewNameAlreadyInCacheError,
|
||||||
AddLink,
|
NoneRelationFoundError,
|
||||||
AddRelation,
|
ReferencedLinkNotCachedError,
|
||||||
DropCascade,
|
TruncatedModelNameCausedCollisionError,
|
||||||
DropMissingRelation,
|
|
||||||
DropRelation,
|
|
||||||
DumpAfterAddGraph,
|
|
||||||
DumpAfterRenameSchema,
|
|
||||||
DumpBeforeAddGraph,
|
|
||||||
DumpBeforeRenameSchema,
|
|
||||||
RenameSchema,
|
|
||||||
TemporaryRelation,
|
|
||||||
UncachedRelation,
|
|
||||||
UpdateReference,
|
|
||||||
)
|
)
|
||||||
|
from dbt.events.functions import fire_event, fire_event_if
|
||||||
|
from dbt.events.types import CacheAction, CacheDumpGraph
|
||||||
import dbt.flags as flags
|
import dbt.flags as flags
|
||||||
from dbt.utils import lowercase
|
from dbt.utils import lowercase
|
||||||
|
|
||||||
@@ -150,11 +141,7 @@ class _CachedRelation:
|
|||||||
:raises InternalError: If the new key already exists.
|
:raises InternalError: If the new key already exists.
|
||||||
"""
|
"""
|
||||||
if new_key in self.referenced_by:
|
if new_key in self.referenced_by:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise NewNameAlreadyInCacheError(old_key, new_key)
|
||||||
'in rename of "{}" -> "{}", new name is in the cache already'.format(
|
|
||||||
old_key, new_key
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if old_key not in self.referenced_by:
|
if old_key not in self.referenced_by:
|
||||||
return
|
return
|
||||||
@@ -270,21 +257,17 @@ class RelationsCache:
|
|||||||
if referenced is None:
|
if referenced is None:
|
||||||
return
|
return
|
||||||
if referenced is None:
|
if referenced is None:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise ReferencedLinkNotCachedError(referenced_key)
|
||||||
"in add_link, referenced link key {} not in cache!".format(referenced_key)
|
|
||||||
)
|
|
||||||
|
|
||||||
dependent = self.relations.get(dependent_key)
|
dependent = self.relations.get(dependent_key)
|
||||||
if dependent is None:
|
if dependent is None:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise DependentLinkNotCachedError(dependent_key)
|
||||||
"in add_link, dependent link key {} not in cache!".format(dependent_key)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert dependent is not None # we just raised!
|
assert dependent is not None # we just raised!
|
||||||
|
|
||||||
referenced.add_reference(dependent)
|
referenced.add_reference(dependent)
|
||||||
|
|
||||||
# TODO: Is this dead code? I can't seem to find it grepping the codebase.
|
# This is called in plugins/postgres/dbt/adapters/postgres/impl.py
|
||||||
def add_link(self, referenced, dependent):
|
def add_link(self, referenced, dependent):
|
||||||
"""Add a link between two relations to the database. If either relation
|
"""Add a link between two relations to the database. If either relation
|
||||||
does not exist, it will be added as an "external" relation.
|
does not exist, it will be added as an "external" relation.
|
||||||
@@ -306,9 +289,9 @@ class RelationsCache:
|
|||||||
# referring to a table outside our control. There's no need to make
|
# referring to a table outside our control. There's no need to make
|
||||||
# a link - we will never drop the referenced relation during a run.
|
# a link - we will never drop the referenced relation during a run.
|
||||||
fire_event(
|
fire_event(
|
||||||
UncachedRelation(
|
CacheAction(
|
||||||
dep_key=_make_msg_from_ref_key(dep_key),
|
|
||||||
ref_key=_make_msg_from_ref_key(ref_key),
|
ref_key=_make_msg_from_ref_key(ref_key),
|
||||||
|
ref_key_2=_make_msg_from_ref_key(dep_key),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -321,8 +304,10 @@ class RelationsCache:
|
|||||||
dependent = dependent.replace(type=referenced.External)
|
dependent = dependent.replace(type=referenced.External)
|
||||||
self.add(dependent)
|
self.add(dependent)
|
||||||
fire_event(
|
fire_event(
|
||||||
AddLink(
|
CacheAction(
|
||||||
dep_key=_make_msg_from_ref_key(dep_key), ref_key=_make_msg_from_ref_key(ref_key)
|
action="add_link",
|
||||||
|
ref_key=_make_msg_from_ref_key(dep_key),
|
||||||
|
ref_key_2=_make_msg_from_ref_key(ref_key),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -335,12 +320,18 @@ class RelationsCache:
|
|||||||
:param BaseRelation relation: The underlying relation.
|
:param BaseRelation relation: The underlying relation.
|
||||||
"""
|
"""
|
||||||
cached = _CachedRelation(relation)
|
cached = _CachedRelation(relation)
|
||||||
fire_event(AddRelation(relation=_make_ref_key_msg(cached)))
|
fire_event_if(
|
||||||
fire_event_if(flags.LOG_CACHE_EVENTS, lambda: DumpBeforeAddGraph(dump=self.dump_graph()))
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="before", action="adding", dump=self.dump_graph()),
|
||||||
|
)
|
||||||
|
fire_event(CacheAction(action="add_relation", ref_key=_make_ref_key_msg(cached)))
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self._setdefault(cached)
|
self._setdefault(cached)
|
||||||
fire_event_if(flags.LOG_CACHE_EVENTS, lambda: DumpAfterAddGraph(dump=self.dump_graph()))
|
fire_event_if(
|
||||||
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="after", action="adding", dump=self.dump_graph()),
|
||||||
|
)
|
||||||
|
|
||||||
def _remove_refs(self, keys):
|
def _remove_refs(self, keys):
|
||||||
"""Removes all references to all entries in keys. This does not
|
"""Removes all references to all entries in keys. This does not
|
||||||
@@ -368,16 +359,19 @@ class RelationsCache:
|
|||||||
"""
|
"""
|
||||||
dropped_key = _make_ref_key(relation)
|
dropped_key = _make_ref_key(relation)
|
||||||
dropped_key_msg = _make_ref_key_msg(relation)
|
dropped_key_msg = _make_ref_key_msg(relation)
|
||||||
fire_event(DropRelation(dropped=dropped_key_msg))
|
fire_event(CacheAction(action="drop_relation", ref_key=dropped_key_msg))
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if dropped_key not in self.relations:
|
if dropped_key not in self.relations:
|
||||||
fire_event(DropMissingRelation(relation=dropped_key_msg))
|
fire_event(CacheAction(action="drop_missing_relation", ref_key=dropped_key_msg))
|
||||||
return
|
return
|
||||||
consequences = self.relations[dropped_key].collect_consequences()
|
consequences = self.relations[dropped_key].collect_consequences()
|
||||||
# convert from a list of _ReferenceKeys to a list of ReferenceKeyMsgs
|
# convert from a list of _ReferenceKeys to a list of ReferenceKeyMsgs
|
||||||
consequence_msgs = [_make_msg_from_ref_key(key) for key in consequences]
|
consequence_msgs = [_make_msg_from_ref_key(key) for key in consequences]
|
||||||
|
fire_event(
|
||||||
fire_event(DropCascade(dropped=dropped_key_msg, consequences=consequence_msgs))
|
CacheAction(
|
||||||
|
action="drop_cascade", ref_key=dropped_key_msg, ref_list=consequence_msgs
|
||||||
|
)
|
||||||
|
)
|
||||||
self._remove_refs(consequences)
|
self._remove_refs(consequences)
|
||||||
|
|
||||||
def _rename_relation(self, old_key, new_relation):
|
def _rename_relation(self, old_key, new_relation):
|
||||||
@@ -400,12 +394,14 @@ class RelationsCache:
|
|||||||
for cached in self.relations.values():
|
for cached in self.relations.values():
|
||||||
if cached.is_referenced_by(old_key):
|
if cached.is_referenced_by(old_key):
|
||||||
fire_event(
|
fire_event(
|
||||||
UpdateReference(
|
CacheAction(
|
||||||
old_key=_make_ref_key_msg(old_key),
|
action="update_reference",
|
||||||
new_key=_make_ref_key_msg(new_key),
|
ref_key=_make_ref_key_msg(old_key),
|
||||||
cached_key=_make_ref_key_msg(cached.key()),
|
ref_key_2=_make_ref_key_msg(new_key),
|
||||||
|
ref_key_3=_make_ref_key_msg(cached.key()),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
cached.rename_key(old_key, new_key)
|
cached.rename_key(old_key, new_key)
|
||||||
|
|
||||||
self.relations[new_key] = relation
|
self.relations[new_key] = relation
|
||||||
@@ -430,27 +426,12 @@ class RelationsCache:
|
|||||||
if new_key in self.relations:
|
if new_key in self.relations:
|
||||||
# Tell user when collision caused by model names truncated during
|
# Tell user when collision caused by model names truncated during
|
||||||
# materialization.
|
# materialization.
|
||||||
match = re.search("__dbt_backup|__dbt_tmp$", new_key.identifier)
|
raise TruncatedModelNameCausedCollisionError(new_key, self.relations)
|
||||||
if match:
|
|
||||||
truncated_model_name_prefix = new_key.identifier[: match.start()]
|
|
||||||
message_addendum = (
|
|
||||||
"\n\nName collisions can occur when the length of two "
|
|
||||||
"models' names approach your database's builtin limit. "
|
|
||||||
"Try restructuring your project such that no two models "
|
|
||||||
"share the prefix '{}'.".format(truncated_model_name_prefix)
|
|
||||||
+ " Then, clean your warehouse of any removed models."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
message_addendum = ""
|
|
||||||
|
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
|
||||||
"in rename, new key {} already in cache: {}{}".format(
|
|
||||||
new_key, list(self.relations.keys()), message_addendum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if old_key not in self.relations:
|
if old_key not in self.relations:
|
||||||
fire_event(TemporaryRelation(key=_make_msg_from_ref_key(old_key)))
|
fire_event(
|
||||||
|
CacheAction(action="temporary_relation", ref_key=_make_msg_from_ref_key(old_key))
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -469,13 +450,16 @@ class RelationsCache:
|
|||||||
old_key = _make_ref_key(old)
|
old_key = _make_ref_key(old)
|
||||||
new_key = _make_ref_key(new)
|
new_key = _make_ref_key(new)
|
||||||
fire_event(
|
fire_event(
|
||||||
RenameSchema(
|
CacheAction(
|
||||||
old_key=_make_msg_from_ref_key(old_key), new_key=_make_msg_from_ref_key(new)
|
action="rename_relation",
|
||||||
|
ref_key=_make_msg_from_ref_key(old_key),
|
||||||
|
ref_key_2=_make_msg_from_ref_key(new),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fire_event_if(
|
fire_event_if(
|
||||||
flags.LOG_CACHE_EVENTS, lambda: DumpBeforeRenameSchema(dump=self.dump_graph())
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="before", action="rename", dump=self.dump_graph()),
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -485,7 +469,8 @@ class RelationsCache:
|
|||||||
self._setdefault(_CachedRelation(new))
|
self._setdefault(_CachedRelation(new))
|
||||||
|
|
||||||
fire_event_if(
|
fire_event_if(
|
||||||
flags.LOG_CACHE_EVENTS, lambda: DumpAfterRenameSchema(dump=self.dump_graph())
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="after", action="rename", dump=self.dump_graph()),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_relations(self, database: Optional[str], schema: Optional[str]) -> List[Any]:
|
def get_relations(self, database: Optional[str], schema: Optional[str]) -> List[Any]:
|
||||||
@@ -505,9 +490,7 @@ class RelationsCache:
|
|||||||
]
|
]
|
||||||
|
|
||||||
if None in results:
|
if None in results:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise NoneRelationFoundError()
|
||||||
"in get_relations, a None relation was found in the cache!"
|
|
||||||
)
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from dbt.adapters.protocol import AdapterConfig, AdapterProtocol, RelationProtoc
|
|||||||
from dbt.contracts.connection import AdapterRequiredConfig, Credentials
|
from dbt.contracts.connection import AdapterRequiredConfig, Credentials
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import AdapterImportError, PluginLoadError
|
from dbt.events.types import AdapterImportError, PluginLoadError
|
||||||
from dbt.exceptions import InternalException, RuntimeException
|
from dbt.exceptions import DbtInternalError, DbtRuntimeError
|
||||||
from dbt.include.global_project import PACKAGE_PATH as GLOBAL_PROJECT_PATH
|
from dbt.include.global_project import PACKAGE_PATH as GLOBAL_PROJECT_PATH
|
||||||
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class AdapterContainer:
|
|||||||
names = ", ".join(self.plugins.keys())
|
names = ", ".join(self.plugins.keys())
|
||||||
|
|
||||||
message = f"Invalid adapter type {name}! Must be one of {names}"
|
message = f"Invalid adapter type {name}! Must be one of {names}"
|
||||||
raise RuntimeException(message)
|
raise DbtRuntimeError(message)
|
||||||
|
|
||||||
def get_adapter_class_by_name(self, name: str) -> Type[Adapter]:
|
def get_adapter_class_by_name(self, name: str) -> Type[Adapter]:
|
||||||
plugin = self.get_plugin_by_name(name)
|
plugin = self.get_plugin_by_name(name)
|
||||||
@@ -60,7 +60,7 @@ class AdapterContainer:
|
|||||||
# the user about it via a runtime error
|
# the user about it via a runtime error
|
||||||
if exc.name == "dbt.adapters." + name:
|
if exc.name == "dbt.adapters." + name:
|
||||||
fire_event(AdapterImportError(exc=str(exc)))
|
fire_event(AdapterImportError(exc=str(exc)))
|
||||||
raise RuntimeException(f"Could not find adapter type {name}!")
|
raise DbtRuntimeError(f"Could not find adapter type {name}!")
|
||||||
# otherwise, the error had to have come from some underlying
|
# otherwise, the error had to have come from some underlying
|
||||||
# library. Log the stack trace.
|
# library. Log the stack trace.
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class AdapterContainer:
|
|||||||
plugin_type = plugin.adapter.type()
|
plugin_type = plugin.adapter.type()
|
||||||
|
|
||||||
if plugin_type != name:
|
if plugin_type != name:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"Expected to find adapter with type named {name}, got "
|
f"Expected to find adapter with type named {name}, got "
|
||||||
f"adapter with type {plugin_type}"
|
f"adapter with type {plugin_type}"
|
||||||
)
|
)
|
||||||
@@ -132,7 +132,7 @@ class AdapterContainer:
|
|||||||
try:
|
try:
|
||||||
plugin = self.plugins[plugin_name]
|
plugin = self.plugins[plugin_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InternalException(f"No plugin found for {plugin_name}") from None
|
raise DbtInternalError(f"No plugin found for {plugin_name}") from None
|
||||||
plugins.append(plugin)
|
plugins.append(plugin)
|
||||||
seen.add(plugin_name)
|
seen.add(plugin_name)
|
||||||
for dep in plugin.dependencies:
|
for dep in plugin.dependencies:
|
||||||
@@ -151,7 +151,7 @@ class AdapterContainer:
|
|||||||
try:
|
try:
|
||||||
path = self.packages[package_name]
|
path = self.packages[package_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InternalException(f"No internal package listing found for {package_name}")
|
raise DbtInternalError(f"No internal package listing found for {package_name}")
|
||||||
paths.append(path)
|
paths.append(path)
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from typing import (
|
|||||||
Generic,
|
Generic,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
|
||||||
Dict,
|
Dict,
|
||||||
Any,
|
Any,
|
||||||
)
|
)
|
||||||
@@ -17,8 +16,7 @@ from typing_extensions import Protocol
|
|||||||
import agate
|
import agate
|
||||||
|
|
||||||
from dbt.contracts.connection import Connection, AdapterRequiredConfig, AdapterResponse
|
from dbt.contracts.connection import Connection, AdapterRequiredConfig, AdapterResponse
|
||||||
from dbt.contracts.graph.compiled import CompiledNode, ManifestNode, NonSourceCompiledNode
|
from dbt.contracts.graph.nodes import ResultNode, ManifestNode
|
||||||
from dbt.contracts.graph.parsed import ParsedNode, ParsedSourceDefinition
|
|
||||||
from dbt.contracts.graph.model_config import BaseConfig
|
from dbt.contracts.graph.model_config import BaseConfig
|
||||||
from dbt.contracts.graph.manifest import Manifest
|
from dbt.contracts.graph.manifest import Manifest
|
||||||
from dbt.contracts.relation import Policy, HasQuoting
|
from dbt.contracts.relation import Policy, HasQuoting
|
||||||
@@ -48,11 +46,7 @@ class RelationProtocol(Protocol):
|
|||||||
...
|
...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from(
|
def create_from(cls: Type[Self], config: HasQuoting, node: ResultNode) -> Self:
|
||||||
cls: Type[Self],
|
|
||||||
config: HasQuoting,
|
|
||||||
node: Union[CompiledNode, ParsedNode, ParsedSourceDefinition],
|
|
||||||
) -> Self:
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +59,7 @@ class CompilerProtocol(Protocol):
|
|||||||
node: ManifestNode,
|
node: ManifestNode,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
extra_context: Optional[Dict[str, Any]] = None,
|
extra_context: Optional[Dict[str, Any]] = None,
|
||||||
) -> NonSourceCompiledNode:
|
) -> ManifestNode:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from dbt.adapters.base import BaseConnectionManager
|
|||||||
from dbt.contracts.connection import Connection, ConnectionState, AdapterResponse
|
from dbt.contracts.connection import Connection, ConnectionState, AdapterResponse
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import ConnectionUsed, SQLQuery, SQLCommit, SQLQueryStatus
|
from dbt.events.types import ConnectionUsed, SQLQuery, SQLCommit, SQLQueryStatus
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from dbt.utils import cast_to_str
|
from dbt.utils import cast_to_str
|
||||||
|
|
||||||
|
|
||||||
@@ -26,9 +27,7 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def cancel(self, connection: Connection):
|
def cancel(self, connection: Connection):
|
||||||
"""Cancel the given connection."""
|
"""Cancel the given connection."""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`cancel` is not implemented for this adapter!")
|
||||||
"`cancel` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
def cancel_open(self) -> List[str]:
|
def cancel_open(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
@@ -56,7 +55,13 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
connection = self.get_thread_connection()
|
connection = self.get_thread_connection()
|
||||||
if auto_begin and connection.transaction_open is False:
|
if auto_begin and connection.transaction_open is False:
|
||||||
self.begin()
|
self.begin()
|
||||||
fire_event(ConnectionUsed(conn_type=self.TYPE, conn_name=cast_to_str(connection.name)))
|
fire_event(
|
||||||
|
ConnectionUsed(
|
||||||
|
conn_type=self.TYPE,
|
||||||
|
conn_name=cast_to_str(connection.name),
|
||||||
|
node_info=get_node_info(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
with self.exception_handler(sql):
|
with self.exception_handler(sql):
|
||||||
if abridge_sql_log:
|
if abridge_sql_log:
|
||||||
@@ -64,7 +69,11 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
else:
|
else:
|
||||||
log_sql = sql
|
log_sql = sql
|
||||||
|
|
||||||
fire_event(SQLQuery(conn_name=cast_to_str(connection.name), sql=log_sql))
|
fire_event(
|
||||||
|
SQLQuery(
|
||||||
|
conn_name=cast_to_str(connection.name), sql=log_sql, node_info=get_node_info()
|
||||||
|
)
|
||||||
|
)
|
||||||
pre = time.time()
|
pre = time.time()
|
||||||
|
|
||||||
cursor = connection.handle.cursor()
|
cursor = connection.handle.cursor()
|
||||||
@@ -72,7 +81,9 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
|
|
||||||
fire_event(
|
fire_event(
|
||||||
SQLQueryStatus(
|
SQLQueryStatus(
|
||||||
status=str(self.get_response(cursor)), elapsed=round((time.time() - pre), 2)
|
status=str(self.get_response(cursor)),
|
||||||
|
elapsed=round((time.time() - pre)),
|
||||||
|
node_info=get_node_info(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,7 +93,7 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_response(cls, cursor: Any) -> AdapterResponse:
|
def get_response(cls, cursor: Any) -> AdapterResponse:
|
||||||
"""Get the status of the cursor."""
|
"""Get the status of the cursor."""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError(
|
||||||
"`get_response` is not implemented for this adapter!"
|
"`get_response` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,7 +149,7 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
def begin(self):
|
def begin(self):
|
||||||
connection = self.get_thread_connection()
|
connection = self.get_thread_connection()
|
||||||
if connection.transaction_open is True:
|
if connection.transaction_open is True:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
'Tried to begin a new transaction on connection "{}", but '
|
'Tried to begin a new transaction on connection "{}", but '
|
||||||
"it already had one open!".format(connection.name)
|
"it already had one open!".format(connection.name)
|
||||||
)
|
)
|
||||||
@@ -151,12 +162,12 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
def commit(self):
|
def commit(self):
|
||||||
connection = self.get_thread_connection()
|
connection = self.get_thread_connection()
|
||||||
if connection.transaction_open is False:
|
if connection.transaction_open is False:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
'Tried to commit transaction on connection "{}", but '
|
'Tried to commit transaction on connection "{}", but '
|
||||||
"it does not have one open!".format(connection.name)
|
"it does not have one open!".format(connection.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
fire_event(SQLCommit(conn_name=connection.name))
|
fire_event(SQLCommit(conn_name=connection.name, node_info=get_node_info()))
|
||||||
self.add_commit_query()
|
self.add_commit_query()
|
||||||
|
|
||||||
connection.transaction_open = False
|
connection.transaction_open = False
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import agate
|
import agate
|
||||||
from typing import Any, Optional, Tuple, Type, List
|
from typing import Any, Optional, Tuple, Type, List
|
||||||
|
|
||||||
import dbt.clients.agate_helper
|
|
||||||
from dbt.contracts.connection import Connection
|
from dbt.contracts.connection import Connection
|
||||||
import dbt.exceptions
|
from dbt.exceptions import RelationTypeNullError
|
||||||
from dbt.adapters.base import BaseAdapter, available
|
from dbt.adapters.base import BaseAdapter, available
|
||||||
from dbt.adapters.cache import _make_ref_key_msg
|
from dbt.adapters.cache import _make_ref_key_msg
|
||||||
from dbt.adapters.sql import SQLConnectionManager
|
from dbt.adapters.sql import SQLConnectionManager
|
||||||
@@ -132,9 +131,7 @@ class SQLAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def drop_relation(self, relation):
|
def drop_relation(self, relation):
|
||||||
if relation.type is None:
|
if relation.type is None:
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise RelationTypeNullError(relation)
|
||||||
"Tried to drop relation {}, but its type is null.".format(relation)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache_dropped(relation)
|
self.cache_dropped(relation)
|
||||||
self.execute_macro(DROP_RELATION_MACRO_NAME, kwargs={"relation": relation})
|
self.execute_macro(DROP_RELATION_MACRO_NAME, kwargs={"relation": relation})
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ def cli_runner():
|
|||||||
@p.cache_selected_only
|
@p.cache_selected_only
|
||||||
@p.debug
|
@p.debug
|
||||||
@p.enable_legacy_logger
|
@p.enable_legacy_logger
|
||||||
@p.event_buffer_size
|
|
||||||
@p.fail_fast
|
@p.fail_fast
|
||||||
@p.log_cache_events
|
@p.log_cache_events
|
||||||
@p.log_format
|
@p.log_format
|
||||||
@@ -47,6 +46,7 @@ def cli_runner():
|
|||||||
@p.version
|
@p.version
|
||||||
@p.version_check
|
@p.version_check
|
||||||
@p.warn_error
|
@p.warn_error
|
||||||
|
@p.warn_error_options
|
||||||
@p.write_json
|
@p.write_json
|
||||||
def cli(ctx, **kwargs):
|
def cli(ctx, **kwargs):
|
||||||
"""An ELT tool for managing your SQL transformations and data models.
|
"""An ELT tool for managing your SQL transformations and data models.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from click import ParamType
|
from click import ParamType
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from dbt.helper_types import WarnErrorOptions
|
||||||
|
|
||||||
|
|
||||||
class YAML(ParamType):
|
class YAML(ParamType):
|
||||||
"""The Click YAML type. Converts YAML strings into objects."""
|
"""The Click YAML type. Converts YAML strings into objects."""
|
||||||
@@ -17,6 +19,19 @@ class YAML(ParamType):
|
|||||||
self.fail(f"String '{value}' is not valid YAML", param, ctx)
|
self.fail(f"String '{value}' is not valid YAML", param, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class WarnErrorOptionsType(YAML):
|
||||||
|
"""The Click WarnErrorOptions type. Converts YAML strings into objects."""
|
||||||
|
|
||||||
|
name = "WarnErrorOptionsType"
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
include_exclude = super().convert(value, param, ctx)
|
||||||
|
|
||||||
|
return WarnErrorOptions(
|
||||||
|
include=include_exclude.get("include", []), exclude=include_exclude.get("exclude", [])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Truthy(ParamType):
|
class Truthy(ParamType):
|
||||||
"""The Click Truthy type. Converts strings into a "truthy" type"""
|
"""The Click Truthy type. Converts strings into a "truthy" type"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from dbt.cli.option_types import YAML
|
from dbt.cli.option_types import YAML, WarnErrorOptionsType
|
||||||
from dbt.cli.resolvers import default_project_dir, default_profiles_dir
|
from dbt.cli.resolvers import default_project_dir, default_profiles_dir
|
||||||
|
|
||||||
|
|
||||||
@@ -80,14 +80,6 @@ enable_legacy_logger = click.option(
|
|||||||
hidden=True,
|
hidden=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
event_buffer_size = click.option(
|
|
||||||
"--event-buffer-size",
|
|
||||||
envvar="DBT_EVENT_BUFFER_SIZE",
|
|
||||||
help="Sets the max number of events to buffer in EVENT_HISTORY.",
|
|
||||||
default=100000,
|
|
||||||
type=click.INT,
|
|
||||||
)
|
|
||||||
|
|
||||||
exclude = click.option("--exclude", envvar=None, help="Specify the nodes to exclude.")
|
exclude = click.option("--exclude", envvar=None, help="Specify the nodes to exclude.")
|
||||||
|
|
||||||
fail_fast = click.option(
|
fail_fast = click.option(
|
||||||
@@ -278,7 +270,7 @@ show = click.option(
|
|||||||
)
|
)
|
||||||
|
|
||||||
skip_profile_setup = click.option(
|
skip_profile_setup = click.option(
|
||||||
"--skip-profile-setup", "-s", envvar=None, help="Skip interative profile setup.", is_flag=True
|
"--skip-profile-setup", "-s", envvar=None, help="Skip interactive profile setup.", is_flag=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: The env var and name (reflected in flags) are corrections!
|
# TODO: The env var and name (reflected in flags) are corrections!
|
||||||
@@ -366,9 +358,20 @@ version_check = click.option(
|
|||||||
)
|
)
|
||||||
|
|
||||||
warn_error = click.option(
|
warn_error = click.option(
|
||||||
"--warn-error/--no-warn-error",
|
"--warn-error",
|
||||||
envvar="DBT_WARN_ERROR",
|
envvar="DBT_WARN_ERROR",
|
||||||
help="If dbt would normally warn, instead raise an exception. Examples include --models that selects nothing, deprecations, configurations with no associated models, invalid test configurations, and missing sources/refs in tests.",
|
help="If dbt would normally warn, instead raise an exception. Examples include --select that selects nothing, deprecations, configurations with no associated models, invalid test configurations, and missing sources/refs in tests.",
|
||||||
|
default=None,
|
||||||
|
flag_value=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
warn_error_options = click.option(
|
||||||
|
"--warn-error-options",
|
||||||
|
envvar="DBT_WARN_ERROR_OPTIONS",
|
||||||
|
default=None,
|
||||||
|
help="""If dbt would normally warn, instead raise an exception based on include/exclude configuration. Examples include --select that selects nothing, deprecations, configurations with no associated models, invalid test configurations,
|
||||||
|
and missing sources/refs in tests. This argument should be a YAML string, with keys 'include' or 'exclude'. eg. '{"include": "all", "exclude": ["NoNodesForSelectionCriteria"]}'""",
|
||||||
|
type=WarnErrorOptionsType(),
|
||||||
)
|
)
|
||||||
|
|
||||||
write_json = click.option(
|
write_json = click.option(
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import re
|
import re
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import dbt.exceptions
|
from dbt.exceptions import (
|
||||||
|
BlockDefinitionNotAtTopError,
|
||||||
|
DbtInternalError,
|
||||||
|
MissingCloseTagError,
|
||||||
|
MissingControlFlowStartTagError,
|
||||||
|
NestedTagsError,
|
||||||
|
UnexpectedControlFlowEndTagError,
|
||||||
|
UnexpectedMacroEOFError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def regex(pat):
|
def regex(pat):
|
||||||
@@ -139,10 +147,7 @@ class TagIterator:
|
|||||||
def _expect_match(self, expected_name, *patterns, **kwargs):
|
def _expect_match(self, expected_name, *patterns, **kwargs):
|
||||||
match = self._first_match(*patterns, **kwargs)
|
match = self._first_match(*patterns, **kwargs)
|
||||||
if match is None:
|
if match is None:
|
||||||
msg = 'unexpected EOF, expected {}, got "{}"'.format(
|
raise UnexpectedMacroEOFError(expected_name, self.data[self.pos :])
|
||||||
expected_name, self.data[self.pos :]
|
|
||||||
)
|
|
||||||
dbt.exceptions.raise_compiler_error(msg)
|
|
||||||
return match
|
return match
|
||||||
|
|
||||||
def handle_expr(self, match):
|
def handle_expr(self, match):
|
||||||
@@ -256,7 +261,7 @@ class TagIterator:
|
|||||||
elif block_type_name is not None:
|
elif block_type_name is not None:
|
||||||
yield self.handle_tag(match)
|
yield self.handle_tag(match)
|
||||||
else:
|
else:
|
||||||
raise dbt.exceptions.InternalException(
|
raise DbtInternalError(
|
||||||
"Invalid regex match in next_block, expected block start, "
|
"Invalid regex match in next_block, expected block start, "
|
||||||
"expr start, or comment start"
|
"expr start, or comment start"
|
||||||
)
|
)
|
||||||
@@ -265,13 +270,6 @@ class TagIterator:
|
|||||||
return self.find_tags()
|
return self.find_tags()
|
||||||
|
|
||||||
|
|
||||||
duplicate_tags = (
|
|
||||||
"Got nested tags: {outer.block_type_name} (started at {outer.start}) did "
|
|
||||||
"not have a matching {{% end{outer.block_type_name} %}} before a "
|
|
||||||
"subsequent {inner.block_type_name} was found (started at {inner.start})"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_CONTROL_FLOW_TAGS = {
|
_CONTROL_FLOW_TAGS = {
|
||||||
"if": "endif",
|
"if": "endif",
|
||||||
"for": "endfor",
|
"for": "endfor",
|
||||||
@@ -319,33 +317,16 @@ class BlockIterator:
|
|||||||
found = self.stack.pop()
|
found = self.stack.pop()
|
||||||
else:
|
else:
|
||||||
expected = _CONTROL_FLOW_END_TAGS[tag.block_type_name]
|
expected = _CONTROL_FLOW_END_TAGS[tag.block_type_name]
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise UnexpectedControlFlowEndTagError(tag, expected, self.tag_parser)
|
||||||
(
|
|
||||||
"Got an unexpected control flow end tag, got {} but "
|
|
||||||
"never saw a preceeding {} (@ {})"
|
|
||||||
).format(tag.block_type_name, expected, self.tag_parser.linepos(tag.start))
|
|
||||||
)
|
|
||||||
expected = _CONTROL_FLOW_TAGS[found]
|
expected = _CONTROL_FLOW_TAGS[found]
|
||||||
if expected != tag.block_type_name:
|
if expected != tag.block_type_name:
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise MissingControlFlowStartTagError(tag, expected, self.tag_parser)
|
||||||
(
|
|
||||||
"Got an unexpected control flow end tag, got {} but "
|
|
||||||
"expected {} next (@ {})"
|
|
||||||
).format(tag.block_type_name, expected, self.tag_parser.linepos(tag.start))
|
|
||||||
)
|
|
||||||
|
|
||||||
if tag.block_type_name in allowed_blocks:
|
if tag.block_type_name in allowed_blocks:
|
||||||
if self.stack:
|
if self.stack:
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise BlockDefinitionNotAtTopError(self.tag_parser, tag.start)
|
||||||
(
|
|
||||||
"Got a block definition inside control flow at {}. "
|
|
||||||
"All dbt block definitions must be at the top level"
|
|
||||||
).format(self.tag_parser.linepos(tag.start))
|
|
||||||
)
|
|
||||||
if self.current is not None:
|
if self.current is not None:
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise NestedTagsError(outer=self.current, inner=tag)
|
||||||
duplicate_tags.format(outer=self.current, inner=tag)
|
|
||||||
)
|
|
||||||
if collect_raw_data:
|
if collect_raw_data:
|
||||||
raw_data = self.data[self.last_position : tag.start]
|
raw_data = self.data[self.last_position : tag.start]
|
||||||
self.last_position = tag.start
|
self.last_position = tag.start
|
||||||
@@ -366,11 +347,7 @@ class BlockIterator:
|
|||||||
|
|
||||||
if self.current:
|
if self.current:
|
||||||
linecount = self.data[: self.current.end].count("\n") + 1
|
linecount = self.data[: self.current.end].count("\n") + 1
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise MissingCloseTagError(self.current.block_type_name, linecount)
|
||||||
("Reached EOF without finding a close tag for {} (searched from line {})").format(
|
|
||||||
self.current.block_type_name, linecount
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if collect_raw_data:
|
if collect_raw_data:
|
||||||
raw_data = self.data[self.last_position :]
|
raw_data = self.data[self.last_position :]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import json
|
|||||||
import dbt.utils
|
import dbt.utils
|
||||||
from typing import Iterable, List, Dict, Union, Optional, Any
|
from typing import Iterable, List, Dict, Union, Optional, Any
|
||||||
|
|
||||||
from dbt.exceptions import RuntimeException
|
from dbt.exceptions import DbtRuntimeError
|
||||||
|
|
||||||
|
|
||||||
BOM = BOM_UTF8.decode("utf-8") # '\ufeff'
|
BOM = BOM_UTF8.decode("utf-8") # '\ufeff'
|
||||||
@@ -168,7 +168,7 @@ class ColumnTypeBuilder(Dict[str, NullableAgateType]):
|
|||||||
return
|
return
|
||||||
elif not isinstance(value, type(existing_type)):
|
elif not isinstance(value, type(existing_type)):
|
||||||
# actual type mismatch!
|
# actual type mismatch!
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"Tables contain columns with the same names ({key}), "
|
f"Tables contain columns with the same names ({key}), "
|
||||||
f"but different types ({value} vs {existing_type})"
|
f"but different types ({value} vs {existing_type})"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ from dbt.events.types import (
|
|||||||
)
|
)
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
CommandResultError,
|
CommandResultError,
|
||||||
RuntimeException,
|
GitCheckoutError,
|
||||||
bad_package_spec,
|
GitCloningError,
|
||||||
raise_git_cloning_error,
|
UnknownGitCloningProblemError,
|
||||||
raise_git_cloning_problem,
|
DbtRuntimeError,
|
||||||
)
|
)
|
||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
@@ -27,16 +27,6 @@ def _is_commit(revision: str) -> bool:
|
|||||||
return bool(re.match(r"\b[0-9a-f]{40}\b", revision))
|
return bool(re.match(r"\b[0-9a-f]{40}\b", revision))
|
||||||
|
|
||||||
|
|
||||||
def _raise_git_cloning_error(repo, revision, error):
|
|
||||||
stderr = error.stderr.strip()
|
|
||||||
if "usage: git" in stderr:
|
|
||||||
stderr = stderr.split("\nusage: git")[0]
|
|
||||||
if re.match("fatal: destination path '(.+)' already exists", stderr):
|
|
||||||
raise_git_cloning_error(error)
|
|
||||||
|
|
||||||
bad_package_spec(repo, revision, stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def clone(repo, cwd, dirname=None, remove_git_dir=False, revision=None, subdirectory=None):
|
def clone(repo, cwd, dirname=None, remove_git_dir=False, revision=None, subdirectory=None):
|
||||||
has_revision = revision is not None
|
has_revision = revision is not None
|
||||||
is_commit = _is_commit(revision or "")
|
is_commit = _is_commit(revision or "")
|
||||||
@@ -64,7 +54,7 @@ def clone(repo, cwd, dirname=None, remove_git_dir=False, revision=None, subdirec
|
|||||||
try:
|
try:
|
||||||
result = run_cmd(cwd, clone_cmd, env={"LC_ALL": "C"})
|
result = run_cmd(cwd, clone_cmd, env={"LC_ALL": "C"})
|
||||||
except CommandResultError as exc:
|
except CommandResultError as exc:
|
||||||
_raise_git_cloning_error(repo, revision, exc)
|
raise GitCloningError(repo, revision, exc)
|
||||||
|
|
||||||
if subdirectory:
|
if subdirectory:
|
||||||
cwd_subdir = os.path.join(cwd, dirname or "")
|
cwd_subdir = os.path.join(cwd, dirname or "")
|
||||||
@@ -72,7 +62,7 @@ def clone(repo, cwd, dirname=None, remove_git_dir=False, revision=None, subdirec
|
|||||||
try:
|
try:
|
||||||
run_cmd(cwd_subdir, clone_cmd_subdir)
|
run_cmd(cwd_subdir, clone_cmd_subdir)
|
||||||
except CommandResultError as exc:
|
except CommandResultError as exc:
|
||||||
_raise_git_cloning_error(repo, revision, exc)
|
raise GitCloningError(repo, revision, exc)
|
||||||
|
|
||||||
if remove_git_dir:
|
if remove_git_dir:
|
||||||
rmdir(os.path.join(dirname, ".git"))
|
rmdir(os.path.join(dirname, ".git"))
|
||||||
@@ -115,8 +105,7 @@ def checkout(cwd, repo, revision=None):
|
|||||||
try:
|
try:
|
||||||
return _checkout(cwd, repo, revision)
|
return _checkout(cwd, repo, revision)
|
||||||
except CommandResultError as exc:
|
except CommandResultError as exc:
|
||||||
stderr = exc.stderr.strip()
|
raise GitCheckoutError(repo=repo, revision=revision, error=exc)
|
||||||
bad_package_spec(repo, revision, stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_sha(cwd):
|
def get_current_sha(cwd):
|
||||||
@@ -145,7 +134,7 @@ def clone_and_checkout(
|
|||||||
err = exc.stderr
|
err = exc.stderr
|
||||||
exists = re.match("fatal: destination path '(.+)' already exists", err)
|
exists = re.match("fatal: destination path '(.+)' already exists", err)
|
||||||
if not exists:
|
if not exists:
|
||||||
raise_git_cloning_problem(repo)
|
raise UnknownGitCloningProblemError(repo)
|
||||||
|
|
||||||
directory = None
|
directory = None
|
||||||
start_sha = None
|
start_sha = None
|
||||||
@@ -155,7 +144,7 @@ def clone_and_checkout(
|
|||||||
else:
|
else:
|
||||||
matches = re.match("Cloning into '(.+)'", err.decode("utf-8"))
|
matches = re.match("Cloning into '(.+)'", err.decode("utf-8"))
|
||||||
if matches is None:
|
if matches is None:
|
||||||
raise RuntimeException(f'Error cloning {repo} - never saw "Cloning into ..." from git')
|
raise DbtRuntimeError(f'Error cloning {repo} - never saw "Cloning into ..." from git')
|
||||||
directory = matches.group(1)
|
directory = matches.group(1)
|
||||||
fire_event(GitProgressPullingNewDependency(dir=directory))
|
fire_event(GitProgressPullingNewDependency(dir=directory))
|
||||||
full_path = os.path.join(cwd, directory)
|
full_path = os.path.join(cwd, directory)
|
||||||
|
|||||||
@@ -25,17 +25,20 @@ from dbt.utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from dbt.clients._jinja_blocks import BlockIterator, BlockData, BlockTag
|
from dbt.clients._jinja_blocks import BlockIterator, BlockData, BlockTag
|
||||||
from dbt.contracts.graph.compiled import CompiledGenericTestNode
|
from dbt.contracts.graph.nodes import GenericTestNode
|
||||||
from dbt.contracts.graph.parsed import ParsedGenericTestNode
|
|
||||||
|
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
InternalException,
|
CaughtMacroError,
|
||||||
raise_compiler_error,
|
CaughtMacroErrorWithNodeError,
|
||||||
CompilationException,
|
CompilationError,
|
||||||
invalid_materialization_argument,
|
DbtInternalError,
|
||||||
|
MaterializationArgError,
|
||||||
|
JinjaRenderingError,
|
||||||
MacroReturn,
|
MacroReturn,
|
||||||
JinjaRenderingException,
|
MaterializtionMacroNotUsedError,
|
||||||
UndefinedMacroException,
|
NoSupportedLanguagesFoundError,
|
||||||
|
UndefinedCompilationError,
|
||||||
|
UndefinedMacroError,
|
||||||
)
|
)
|
||||||
from dbt import flags
|
from dbt import flags
|
||||||
from dbt.node_types import ModelLanguage
|
from dbt.node_types import ModelLanguage
|
||||||
@@ -158,9 +161,9 @@ def quoted_native_concat(nodes):
|
|||||||
except (ValueError, SyntaxError, MemoryError):
|
except (ValueError, SyntaxError, MemoryError):
|
||||||
result = raw
|
result = raw
|
||||||
if isinstance(raw, BoolMarker) and not isinstance(result, bool):
|
if isinstance(raw, BoolMarker) and not isinstance(result, bool):
|
||||||
raise JinjaRenderingException(f"Could not convert value '{raw!s}' into type 'bool'")
|
raise JinjaRenderingError(f"Could not convert value '{raw!s}' into type 'bool'")
|
||||||
if isinstance(raw, NumberMarker) and not _is_number(result):
|
if isinstance(raw, NumberMarker) and not _is_number(result):
|
||||||
raise JinjaRenderingException(f"Could not convert value '{raw!s}' into type 'number'")
|
raise JinjaRenderingError(f"Could not convert value '{raw!s}' into type 'number'")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -238,12 +241,12 @@ class BaseMacroGenerator:
|
|||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
|
except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
|
||||||
raise_compiler_error(str(e))
|
raise CaughtMacroError(e)
|
||||||
|
|
||||||
def call_macro(self, *args, **kwargs):
|
def call_macro(self, *args, **kwargs):
|
||||||
# called from __call__ methods
|
# called from __call__ methods
|
||||||
if self.context is None:
|
if self.context is None:
|
||||||
raise InternalException("Context is still None in call_macro!")
|
raise DbtInternalError("Context is still None in call_macro!")
|
||||||
assert self.context is not None
|
assert self.context is not None
|
||||||
|
|
||||||
macro = self.get_macro()
|
macro = self.get_macro()
|
||||||
@@ -270,7 +273,7 @@ class MacroStack(threading.local):
|
|||||||
def pop(self, name):
|
def pop(self, name):
|
||||||
got = self.call_stack.pop()
|
got = self.call_stack.pop()
|
||||||
if got != name:
|
if got != name:
|
||||||
raise InternalException(f"popped {got}, expected {name}")
|
raise DbtInternalError(f"popped {got}, expected {name}")
|
||||||
|
|
||||||
|
|
||||||
class MacroGenerator(BaseMacroGenerator):
|
class MacroGenerator(BaseMacroGenerator):
|
||||||
@@ -297,8 +300,8 @@ class MacroGenerator(BaseMacroGenerator):
|
|||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
|
except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
|
||||||
raise_compiler_error(str(e), self.macro)
|
raise CaughtMacroErrorWithNodeError(exc=e, node=self.macro)
|
||||||
except CompilationException as e:
|
except CompilationError as e:
|
||||||
e.stack.append(self.macro)
|
e.stack.append(self.macro)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -377,7 +380,7 @@ class MaterializationExtension(jinja2.ext.Extension):
|
|||||||
node.defaults.append(languages)
|
node.defaults.append(languages)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
invalid_materialization_argument(materialization_name, target.name)
|
raise MaterializationArgError(materialization_name, target.name)
|
||||||
|
|
||||||
if SUPPORTED_LANG_ARG not in node.args:
|
if SUPPORTED_LANG_ARG not in node.args:
|
||||||
node.args.append(SUPPORTED_LANG_ARG)
|
node.args.append(SUPPORTED_LANG_ARG)
|
||||||
@@ -452,7 +455,7 @@ def create_undefined(node=None):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
raise_compiler_error(f"{self.name} is undefined", node=node)
|
raise UndefinedCompilationError(name=self.name, node=node)
|
||||||
|
|
||||||
return Undefined
|
return Undefined
|
||||||
|
|
||||||
@@ -510,10 +513,10 @@ def catch_jinja(node=None) -> Iterator[None]:
|
|||||||
yield
|
yield
|
||||||
except jinja2.exceptions.TemplateSyntaxError as e:
|
except jinja2.exceptions.TemplateSyntaxError as e:
|
||||||
e.translated = False
|
e.translated = False
|
||||||
raise CompilationException(str(e), node) from e
|
raise CompilationError(str(e), node) from e
|
||||||
except jinja2.exceptions.UndefinedError as e:
|
except jinja2.exceptions.UndefinedError as e:
|
||||||
raise UndefinedMacroException(str(e), node) from e
|
raise UndefinedMacroError(str(e), node) from e
|
||||||
except CompilationException as exc:
|
except CompilationError as exc:
|
||||||
exc.add_node(node)
|
exc.add_node(node)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -620,7 +623,7 @@ GENERIC_TEST_KWARGS_NAME = "_dbt_generic_test_kwargs"
|
|||||||
|
|
||||||
def add_rendered_test_kwargs(
|
def add_rendered_test_kwargs(
|
||||||
context: Dict[str, Any],
|
context: Dict[str, Any],
|
||||||
node: Union[ParsedGenericTestNode, CompiledGenericTestNode],
|
node: GenericTestNode,
|
||||||
capture_macros: bool = False,
|
capture_macros: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Render each of the test kwargs in the given context using the native
|
"""Render each of the test kwargs in the given context using the native
|
||||||
@@ -652,13 +655,13 @@ def add_rendered_test_kwargs(
|
|||||||
|
|
||||||
def get_supported_languages(node: jinja2.nodes.Macro) -> List[ModelLanguage]:
|
def get_supported_languages(node: jinja2.nodes.Macro) -> List[ModelLanguage]:
|
||||||
if "materialization" not in node.name:
|
if "materialization" not in node.name:
|
||||||
raise_compiler_error("Only materialization macros can be used with this function")
|
raise MaterializtionMacroNotUsedError(node=node)
|
||||||
|
|
||||||
no_kwargs = not node.defaults
|
no_kwargs = not node.defaults
|
||||||
no_langs_found = SUPPORTED_LANG_ARG not in node.args
|
no_langs_found = SUPPORTED_LANG_ARG not in node.args
|
||||||
|
|
||||||
if no_kwargs or no_langs_found:
|
if no_kwargs or no_langs_found:
|
||||||
raise_compiler_error(f"No supported_languages found in materialization macro {node.name}")
|
raise NoSupportedLanguagesFoundError(node=node)
|
||||||
|
|
||||||
lang_idx = node.args.index(SUPPORTED_LANG_ARG)
|
lang_idx = node.args.index(SUPPORTED_LANG_ARG)
|
||||||
# indexing defaults from the end
|
# indexing defaults from the end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import jinja2
|
import jinja2
|
||||||
from dbt.clients.jinja import get_environment
|
from dbt.clients.jinja import get_environment
|
||||||
from dbt.exceptions import raise_compiler_error
|
from dbt.exceptions import MacroNamespaceNotStringError, MacroNameNotStringError
|
||||||
|
|
||||||
|
|
||||||
def statically_extract_macro_calls(string, ctx, db_wrapper=None):
|
def statically_extract_macro_calls(string, ctx, db_wrapper=None):
|
||||||
@@ -117,20 +117,14 @@ def statically_parse_adapter_dispatch(func_call, ctx, db_wrapper):
|
|||||||
func_name = kwarg.value.value
|
func_name = kwarg.value.value
|
||||||
possible_macro_calls.append(func_name)
|
possible_macro_calls.append(func_name)
|
||||||
else:
|
else:
|
||||||
raise_compiler_error(
|
raise MacroNameNotStringError(kwarg_value=kwarg.value.value)
|
||||||
f"The macro_name parameter ({kwarg.value.value}) "
|
|
||||||
"to adapter.dispatch was not a string"
|
|
||||||
)
|
|
||||||
elif kwarg.key == "macro_namespace":
|
elif kwarg.key == "macro_namespace":
|
||||||
# This will remain to enable static resolution
|
# This will remain to enable static resolution
|
||||||
kwarg_type = type(kwarg.value).__name__
|
kwarg_type = type(kwarg.value).__name__
|
||||||
if kwarg_type == "Const":
|
if kwarg_type == "Const":
|
||||||
macro_namespace = kwarg.value.value
|
macro_namespace = kwarg.value.value
|
||||||
else:
|
else:
|
||||||
raise_compiler_error(
|
raise MacroNamespaceNotStringError(kwarg_type)
|
||||||
"The macro_namespace parameter to adapter.dispatch "
|
|
||||||
f"is a {kwarg_type}, not a string"
|
|
||||||
)
|
|
||||||
|
|
||||||
# positional arguments
|
# positional arguments
|
||||||
if packages_arg:
|
if packages_arg:
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ from dbt.events.types import (
|
|||||||
SystemErrorRetrievingModTime,
|
SystemErrorRetrievingModTime,
|
||||||
SystemCouldNotWrite,
|
SystemCouldNotWrite,
|
||||||
SystemExecutingCmd,
|
SystemExecutingCmd,
|
||||||
SystemStdOutMsg,
|
SystemStdOut,
|
||||||
SystemStdErrMsg,
|
SystemStdErr,
|
||||||
SystemReportReturnCode,
|
SystemReportReturnCode,
|
||||||
)
|
)
|
||||||
import dbt.exceptions
|
import dbt.exceptions
|
||||||
@@ -144,7 +144,8 @@ def make_symlink(source: str, link_path: str) -> None:
|
|||||||
Create a symlink at `link_path` referring to `source`.
|
Create a symlink at `link_path` referring to `source`.
|
||||||
"""
|
"""
|
||||||
if not supports_symlinks():
|
if not supports_symlinks():
|
||||||
dbt.exceptions.system_error("create a symbolic link")
|
# TODO: why not import these at top?
|
||||||
|
raise dbt.exceptions.SymbolicLinkError()
|
||||||
|
|
||||||
os.symlink(source, link_path)
|
os.symlink(source, link_path)
|
||||||
|
|
||||||
@@ -411,7 +412,7 @@ def _interpret_oserror(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
|
|||||||
_handle_posix_error(exc, cwd, cmd)
|
_handle_posix_error(exc, cwd, cmd)
|
||||||
|
|
||||||
# this should not be reachable, raise _something_ at least!
|
# this should not be reachable, raise _something_ at least!
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
"Unhandled exception in _interpret_oserror: {}".format(exc)
|
"Unhandled exception in _interpret_oserror: {}".format(exc)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -440,8 +441,8 @@ def run_cmd(cwd: str, cmd: List[str], env: Optional[Dict[str, Any]] = None) -> T
|
|||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
_interpret_oserror(exc, cwd, cmd)
|
_interpret_oserror(exc, cwd, cmd)
|
||||||
|
|
||||||
fire_event(SystemStdOutMsg(bmsg=out))
|
fire_event(SystemStdOut(bmsg=out))
|
||||||
fire_event(SystemStdErrMsg(bmsg=err))
|
fire_event(SystemStdErr(bmsg=err))
|
||||||
|
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
fire_event(SystemReportReturnCode(returncode=proc.returncode))
|
fire_event(SystemReportReturnCode(returncode=proc.returncode))
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ def load_yaml_text(contents, path=None):
|
|||||||
else:
|
else:
|
||||||
error = str(e)
|
error = str(e)
|
||||||
|
|
||||||
raise dbt.exceptions.ValidationException(error)
|
raise dbt.exceptions.DbtValidationError(error)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import List, Dict, Any, Tuple, cast, Optional
|
from typing import List, Dict, Any, Tuple, Optional
|
||||||
|
|
||||||
import networkx as nx # type: ignore
|
import networkx as nx # type: ignore
|
||||||
import pickle
|
import pickle
|
||||||
@@ -12,23 +12,23 @@ from dbt.clients import jinja
|
|||||||
from dbt.clients.system import make_directory
|
from dbt.clients.system import make_directory
|
||||||
from dbt.context.providers import generate_runtime_model_context
|
from dbt.context.providers import generate_runtime_model_context
|
||||||
from dbt.contracts.graph.manifest import Manifest, UniqueID
|
from dbt.contracts.graph.manifest import Manifest, UniqueID
|
||||||
from dbt.contracts.graph.compiled import (
|
from dbt.contracts.graph.nodes import (
|
||||||
COMPILED_TYPES,
|
ManifestNode,
|
||||||
CompiledGenericTestNode,
|
ManifestSQLNode,
|
||||||
|
GenericTestNode,
|
||||||
GraphMemberNode,
|
GraphMemberNode,
|
||||||
InjectedCTE,
|
InjectedCTE,
|
||||||
ManifestNode,
|
SeedNode,
|
||||||
NonSourceCompiledNode,
|
|
||||||
)
|
)
|
||||||
from dbt.contracts.graph.parsed import ParsedNode
|
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
dependency_not_found,
|
GraphDependencyNotFoundError,
|
||||||
InternalException,
|
DbtInternalError,
|
||||||
RuntimeException,
|
DbtRuntimeError,
|
||||||
)
|
)
|
||||||
from dbt.graph import Graph
|
from dbt.graph import Graph
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import FoundStats, CompilingNode, WritingInjectedSQLForNode
|
from dbt.events.types import FoundStats, WritingInjectedSQLForNode
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from dbt.node_types import NodeType, ModelLanguage
|
from dbt.node_types import NodeType, ModelLanguage
|
||||||
from dbt.events.format import pluralize
|
from dbt.events.format import pluralize
|
||||||
import dbt.tracking
|
import dbt.tracking
|
||||||
@@ -36,14 +36,6 @@ import dbt.tracking
|
|||||||
graph_file_name = "graph.gpickle"
|
graph_file_name = "graph.gpickle"
|
||||||
|
|
||||||
|
|
||||||
def _compiled_type_for(model: ParsedNode):
|
|
||||||
if type(model) not in COMPILED_TYPES:
|
|
||||||
raise InternalException(
|
|
||||||
f"Asked to compile {type(model)} node, but it has no compiled form"
|
|
||||||
)
|
|
||||||
return COMPILED_TYPES[type(model)]
|
|
||||||
|
|
||||||
|
|
||||||
def print_compile_stats(stats):
|
def print_compile_stats(stats):
|
||||||
names = {
|
names = {
|
||||||
NodeType.Model: "model",
|
NodeType.Model: "model",
|
||||||
@@ -56,6 +48,7 @@ def print_compile_stats(stats):
|
|||||||
NodeType.Source: "source",
|
NodeType.Source: "source",
|
||||||
NodeType.Exposure: "exposure",
|
NodeType.Exposure: "exposure",
|
||||||
NodeType.Metric: "metric",
|
NodeType.Metric: "metric",
|
||||||
|
NodeType.Entity: "entity",
|
||||||
}
|
}
|
||||||
|
|
||||||
results = {k: 0 for k in names.keys()}
|
results = {k: 0 for k in names.keys()}
|
||||||
@@ -91,6 +84,8 @@ def _generate_stats(manifest: Manifest):
|
|||||||
stats[exposure.resource_type] += 1
|
stats[exposure.resource_type] += 1
|
||||||
for metric in manifest.metrics.values():
|
for metric in manifest.metrics.values():
|
||||||
stats[metric.resource_type] += 1
|
stats[metric.resource_type] += 1
|
||||||
|
for entity in manifest.entities.values():
|
||||||
|
stats[entity.resource_type] += 1
|
||||||
for macro in manifest.macros.values():
|
for macro in manifest.macros.values():
|
||||||
stats[macro.resource_type] += 1
|
stats[macro.resource_type] += 1
|
||||||
return stats
|
return stats
|
||||||
@@ -176,7 +171,7 @@ class Compiler:
|
|||||||
# a dict for jinja rendering of SQL
|
# a dict for jinja rendering of SQL
|
||||||
def _create_node_context(
|
def _create_node_context(
|
||||||
self,
|
self,
|
||||||
node: NonSourceCompiledNode,
|
node: ManifestSQLNode,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
extra_context: Dict[str, Any],
|
extra_context: Dict[str, Any],
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
@@ -184,7 +179,7 @@ class Compiler:
|
|||||||
context = generate_runtime_model_context(node, self.config, manifest)
|
context = generate_runtime_model_context(node, self.config, manifest)
|
||||||
context.update(extra_context)
|
context.update(extra_context)
|
||||||
|
|
||||||
if isinstance(node, CompiledGenericTestNode):
|
if isinstance(node, GenericTestNode):
|
||||||
# for test nodes, add a special keyword args value to the context
|
# for test nodes, add a special keyword args value to the context
|
||||||
jinja.add_rendered_test_kwargs(context, node)
|
jinja.add_rendered_test_kwargs(context, node)
|
||||||
|
|
||||||
@@ -195,14 +190,6 @@ class Compiler:
|
|||||||
relation_cls = adapter.Relation
|
relation_cls = adapter.Relation
|
||||||
return relation_cls.add_ephemeral_prefix(name)
|
return relation_cls.add_ephemeral_prefix(name)
|
||||||
|
|
||||||
def _get_relation_name(self, node: ParsedNode):
|
|
||||||
relation_name = None
|
|
||||||
if node.is_relational and not node.is_ephemeral_model:
|
|
||||||
adapter = get_adapter(self.config)
|
|
||||||
relation_cls = adapter.Relation
|
|
||||||
relation_name = str(relation_cls.create_from(self.config, node))
|
|
||||||
return relation_name
|
|
||||||
|
|
||||||
def _inject_ctes_into_sql(self, sql: str, ctes: List[InjectedCTE]) -> str:
|
def _inject_ctes_into_sql(self, sql: str, ctes: List[InjectedCTE]) -> str:
|
||||||
"""
|
"""
|
||||||
`ctes` is a list of InjectedCTEs like:
|
`ctes` is a list of InjectedCTEs like:
|
||||||
@@ -261,10 +248,10 @@ class Compiler:
|
|||||||
|
|
||||||
def _recursively_prepend_ctes(
|
def _recursively_prepend_ctes(
|
||||||
self,
|
self,
|
||||||
model: NonSourceCompiledNode,
|
model: ManifestSQLNode,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
extra_context: Optional[Dict[str, Any]],
|
extra_context: Optional[Dict[str, Any]],
|
||||||
) -> Tuple[NonSourceCompiledNode, List[InjectedCTE]]:
|
) -> Tuple[ManifestSQLNode, List[InjectedCTE]]:
|
||||||
"""This method is called by the 'compile_node' method. Starting
|
"""This method is called by the 'compile_node' method. Starting
|
||||||
from the node that it is passed in, it will recursively call
|
from the node that it is passed in, it will recursively call
|
||||||
itself using the 'extra_ctes'. The 'ephemeral' models do
|
itself using the 'extra_ctes'. The 'ephemeral' models do
|
||||||
@@ -273,13 +260,14 @@ class Compiler:
|
|||||||
inserting CTEs into the SQL.
|
inserting CTEs into the SQL.
|
||||||
"""
|
"""
|
||||||
if model.compiled_code is None:
|
if model.compiled_code is None:
|
||||||
raise RuntimeException("Cannot inject ctes into an unparsed node", model)
|
raise DbtRuntimeError("Cannot inject ctes into an unparsed node", model)
|
||||||
if model.extra_ctes_injected:
|
if model.extra_ctes_injected:
|
||||||
return (model, model.extra_ctes)
|
return (model, model.extra_ctes)
|
||||||
|
|
||||||
# Just to make it plain that nothing is actually injected for this case
|
# Just to make it plain that nothing is actually injected for this case
|
||||||
if not model.extra_ctes:
|
if not model.extra_ctes:
|
||||||
model.extra_ctes_injected = True
|
if not isinstance(model, SeedNode):
|
||||||
|
model.extra_ctes_injected = True
|
||||||
manifest.update_node(model)
|
manifest.update_node(model)
|
||||||
return (model, model.extra_ctes)
|
return (model, model.extra_ctes)
|
||||||
|
|
||||||
@@ -293,20 +281,19 @@ class Compiler:
|
|||||||
# ephemeral model.
|
# ephemeral model.
|
||||||
for cte in model.extra_ctes:
|
for cte in model.extra_ctes:
|
||||||
if cte.id not in manifest.nodes:
|
if cte.id not in manifest.nodes:
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
f"During compilation, found a cte reference that "
|
f"During compilation, found a cte reference that "
|
||||||
f"could not be resolved: {cte.id}"
|
f"could not be resolved: {cte.id}"
|
||||||
)
|
)
|
||||||
cte_model = manifest.nodes[cte.id]
|
cte_model = manifest.nodes[cte.id]
|
||||||
|
assert not isinstance(cte_model, SeedNode)
|
||||||
|
|
||||||
if not cte_model.is_ephemeral_model:
|
if not cte_model.is_ephemeral_model:
|
||||||
raise InternalException(f"{cte.id} is not ephemeral")
|
raise DbtInternalError(f"{cte.id} is not ephemeral")
|
||||||
|
|
||||||
# This model has already been compiled, so it's been
|
# This model has already been compiled, so it's been
|
||||||
# through here before
|
# through here before
|
||||||
if getattr(cte_model, "compiled", False):
|
if getattr(cte_model, "compiled", False):
|
||||||
assert isinstance(cte_model, tuple(COMPILED_TYPES.values()))
|
|
||||||
cte_model = cast(NonSourceCompiledNode, cte_model)
|
|
||||||
new_prepended_ctes = cte_model.extra_ctes
|
new_prepended_ctes = cte_model.extra_ctes
|
||||||
|
|
||||||
# if the cte_model isn't compiled, i.e. first time here
|
# if the cte_model isn't compiled, i.e. first time here
|
||||||
@@ -343,21 +330,19 @@ class Compiler:
|
|||||||
|
|
||||||
return model, prepended_ctes
|
return model, prepended_ctes
|
||||||
|
|
||||||
# creates a compiled_node from the ManifestNode passed in,
|
# Sets compiled fields in the ManifestSQLNode passed in,
|
||||||
# creates a "context" dictionary for jinja rendering,
|
# creates a "context" dictionary for jinja rendering,
|
||||||
# and then renders the "compiled_code" using the node, the
|
# and then renders the "compiled_code" using the node, the
|
||||||
# raw_code and the context.
|
# raw_code and the context.
|
||||||
def _compile_node(
|
def _compile_node(
|
||||||
self,
|
self,
|
||||||
node: ManifestNode,
|
node: ManifestSQLNode,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
extra_context: Optional[Dict[str, Any]] = None,
|
extra_context: Optional[Dict[str, Any]] = None,
|
||||||
) -> NonSourceCompiledNode:
|
) -> ManifestSQLNode:
|
||||||
if extra_context is None:
|
if extra_context is None:
|
||||||
extra_context = {}
|
extra_context = {}
|
||||||
|
|
||||||
fire_event(CompilingNode(unique_id=node.unique_id))
|
|
||||||
|
|
||||||
data = node.to_dict(omit_none=True)
|
data = node.to_dict(omit_none=True)
|
||||||
data.update(
|
data.update(
|
||||||
{
|
{
|
||||||
@@ -367,9 +352,8 @@ class Compiler:
|
|||||||
"extra_ctes": [],
|
"extra_ctes": [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
compiled_node = _compiled_type_for(node).from_dict(data)
|
|
||||||
|
|
||||||
if compiled_node.language == ModelLanguage.python:
|
if node.language == ModelLanguage.python:
|
||||||
# TODO could we also 'minify' this code at all? just aesthetic, not functional
|
# TODO could we also 'minify' this code at all? just aesthetic, not functional
|
||||||
|
|
||||||
# quoating seems like something very specific to sql so far
|
# quoating seems like something very specific to sql so far
|
||||||
@@ -377,7 +361,7 @@ class Compiler:
|
|||||||
# TODO try to find better way to do this, given that
|
# TODO try to find better way to do this, given that
|
||||||
original_quoting = self.config.quoting
|
original_quoting = self.config.quoting
|
||||||
self.config.quoting = {key: False for key in original_quoting.keys()}
|
self.config.quoting = {key: False for key in original_quoting.keys()}
|
||||||
context = self._create_node_context(compiled_node, manifest, extra_context)
|
context = self._create_node_context(node, manifest, extra_context)
|
||||||
|
|
||||||
postfix = jinja.get_rendered(
|
postfix = jinja.get_rendered(
|
||||||
"{{ py_script_postfix(model) }}",
|
"{{ py_script_postfix(model) }}",
|
||||||
@@ -385,23 +369,21 @@ class Compiler:
|
|||||||
node,
|
node,
|
||||||
)
|
)
|
||||||
# we should NOT jinja render the python model's 'raw code'
|
# we should NOT jinja render the python model's 'raw code'
|
||||||
compiled_node.compiled_code = f"{node.raw_code}\n\n{postfix}"
|
node.compiled_code = f"{node.raw_code}\n\n{postfix}"
|
||||||
# restore quoting settings in the end since context is lazy evaluated
|
# restore quoting settings in the end since context is lazy evaluated
|
||||||
self.config.quoting = original_quoting
|
self.config.quoting = original_quoting
|
||||||
|
|
||||||
else:
|
else:
|
||||||
context = self._create_node_context(compiled_node, manifest, extra_context)
|
context = self._create_node_context(node, manifest, extra_context)
|
||||||
compiled_node.compiled_code = jinja.get_rendered(
|
node.compiled_code = jinja.get_rendered(
|
||||||
node.raw_code,
|
node.raw_code,
|
||||||
context,
|
context,
|
||||||
node,
|
node,
|
||||||
)
|
)
|
||||||
|
|
||||||
compiled_node.relation_name = self._get_relation_name(node)
|
node.compiled = True
|
||||||
|
|
||||||
compiled_node.compiled = True
|
return node
|
||||||
|
|
||||||
return compiled_node
|
|
||||||
|
|
||||||
def write_graph_file(self, linker: Linker, manifest: Manifest):
|
def write_graph_file(self, linker: Linker, manifest: Manifest):
|
||||||
filename = graph_file_name
|
filename = graph_file_name
|
||||||
@@ -419,8 +401,10 @@ class Compiler:
|
|||||||
linker.dependency(node.unique_id, (manifest.sources[dependency].unique_id))
|
linker.dependency(node.unique_id, (manifest.sources[dependency].unique_id))
|
||||||
elif dependency in manifest.metrics:
|
elif dependency in manifest.metrics:
|
||||||
linker.dependency(node.unique_id, (manifest.metrics[dependency].unique_id))
|
linker.dependency(node.unique_id, (manifest.metrics[dependency].unique_id))
|
||||||
|
elif dependency in manifest.entities:
|
||||||
|
linker.dependency(node.unique_id, (manifest.entities[dependency].unique_id))
|
||||||
else:
|
else:
|
||||||
dependency_not_found(node, dependency)
|
raise GraphDependencyNotFoundError(node, dependency)
|
||||||
|
|
||||||
def link_graph(self, linker: Linker, manifest: Manifest, add_test_edges: bool = False):
|
def link_graph(self, linker: Linker, manifest: Manifest, add_test_edges: bool = False):
|
||||||
for source in manifest.sources.values():
|
for source in manifest.sources.values():
|
||||||
@@ -431,6 +415,8 @@ class Compiler:
|
|||||||
self.link_node(linker, exposure, manifest)
|
self.link_node(linker, exposure, manifest)
|
||||||
for metric in manifest.metrics.values():
|
for metric in manifest.metrics.values():
|
||||||
self.link_node(linker, metric, manifest)
|
self.link_node(linker, metric, manifest)
|
||||||
|
for entity in manifest.entities.values():
|
||||||
|
self.link_node(linker, entity, manifest)
|
||||||
|
|
||||||
cycle = linker.find_cycles()
|
cycle = linker.find_cycles()
|
||||||
|
|
||||||
@@ -508,10 +494,13 @@ class Compiler:
|
|||||||
return Graph(linker.graph)
|
return Graph(linker.graph)
|
||||||
|
|
||||||
# writes the "compiled_code" into the target/compiled directory
|
# writes the "compiled_code" into the target/compiled directory
|
||||||
def _write_node(self, node: NonSourceCompiledNode) -> ManifestNode:
|
def _write_node(self, node: ManifestSQLNode) -> ManifestSQLNode:
|
||||||
if not node.extra_ctes_injected or node.resource_type == NodeType.Snapshot:
|
if not node.extra_ctes_injected or node.resource_type in (
|
||||||
|
NodeType.Snapshot,
|
||||||
|
NodeType.Seed,
|
||||||
|
):
|
||||||
return node
|
return node
|
||||||
fire_event(WritingInjectedSQLForNode(unique_id=node.unique_id))
|
fire_event(WritingInjectedSQLForNode(node_info=get_node_info()))
|
||||||
|
|
||||||
if node.compiled_code:
|
if node.compiled_code:
|
||||||
node.compiled_path = node.write_node(
|
node.compiled_path = node.write_node(
|
||||||
@@ -521,11 +510,11 @@ class Compiler:
|
|||||||
|
|
||||||
def compile_node(
|
def compile_node(
|
||||||
self,
|
self,
|
||||||
node: ManifestNode,
|
node: ManifestSQLNode,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
extra_context: Optional[Dict[str, Any]] = None,
|
extra_context: Optional[Dict[str, Any]] = None,
|
||||||
write: bool = True,
|
write: bool = True,
|
||||||
) -> NonSourceCompiledNode:
|
) -> ManifestSQLNode:
|
||||||
"""This is the main entry point into this code. It's called by
|
"""This is the main entry point into this code. It's called by
|
||||||
CompileRunner.compile, GenericRPCRunner.compile, and
|
CompileRunner.compile, GenericRPCRunner.compile, and
|
||||||
RunTask.get_hook_sql. It calls '_compile_node' to convert
|
RunTask.get_hook_sql. It calls '_compile_node' to convert
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ from dbt.clients.system import load_file_contents
|
|||||||
from dbt.clients.yaml_helper import load_yaml_text
|
from dbt.clients.yaml_helper import load_yaml_text
|
||||||
from dbt.contracts.connection import Credentials, HasCredentials
|
from dbt.contracts.connection import Credentials, HasCredentials
|
||||||
from dbt.contracts.project import ProfileConfig, UserConfig
|
from dbt.contracts.project import ProfileConfig, UserConfig
|
||||||
from dbt.exceptions import CompilationException
|
from dbt.exceptions import (
|
||||||
from dbt.exceptions import DbtProfileError
|
CompilationError,
|
||||||
from dbt.exceptions import DbtProjectError
|
DbtProfileError,
|
||||||
from dbt.exceptions import ValidationException
|
DbtProjectError,
|
||||||
from dbt.exceptions import RuntimeException
|
DbtValidationError,
|
||||||
from dbt.exceptions import validator_error_message
|
DbtRuntimeError,
|
||||||
|
ProfileConfigError,
|
||||||
|
)
|
||||||
from dbt.events.types import MissingProfileTarget
|
from dbt.events.types import MissingProfileTarget
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.utils import coerce_dict_str
|
from dbt.utils import coerce_dict_str
|
||||||
@@ -58,9 +60,9 @@ def read_profile(profiles_dir: str) -> Dict[str, Any]:
|
|||||||
msg = f"The profiles.yml file at {path} is empty"
|
msg = f"The profiles.yml file at {path} is empty"
|
||||||
raise DbtProfileError(INVALID_PROFILE_MESSAGE.format(error_string=msg))
|
raise DbtProfileError(INVALID_PROFILE_MESSAGE.format(error_string=msg))
|
||||||
return yaml_content
|
return yaml_content
|
||||||
except ValidationException as e:
|
except DbtValidationError as e:
|
||||||
msg = INVALID_PROFILE_MESSAGE.format(error_string=e)
|
msg = INVALID_PROFILE_MESSAGE.format(error_string=e)
|
||||||
raise ValidationException(msg) from e
|
raise DbtValidationError(msg) from e
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -73,7 +75,7 @@ def read_user_config(directory: str) -> UserConfig:
|
|||||||
if user_config is not None:
|
if user_config is not None:
|
||||||
UserConfig.validate(user_config)
|
UserConfig.validate(user_config)
|
||||||
return UserConfig.from_dict(user_config)
|
return UserConfig.from_dict(user_config)
|
||||||
except (RuntimeException, ValidationError):
|
except (DbtRuntimeError, ValidationError):
|
||||||
pass
|
pass
|
||||||
return UserConfig()
|
return UserConfig()
|
||||||
|
|
||||||
@@ -156,7 +158,7 @@ class Profile(HasCredentials):
|
|||||||
dct = self.to_profile_info(serialize_credentials=True)
|
dct = self.to_profile_info(serialize_credentials=True)
|
||||||
ProfileConfig.validate(dct)
|
ProfileConfig.validate(dct)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
raise DbtProfileError(validator_error_message(exc)) from exc
|
raise ProfileConfigError(exc) from exc
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _credentials_from_profile(
|
def _credentials_from_profile(
|
||||||
@@ -180,8 +182,8 @@ class Profile(HasCredentials):
|
|||||||
data = cls.translate_aliases(profile)
|
data = cls.translate_aliases(profile)
|
||||||
cls.validate(data)
|
cls.validate(data)
|
||||||
credentials = cls.from_dict(data)
|
credentials = cls.from_dict(data)
|
||||||
except (RuntimeException, ValidationError) as e:
|
except (DbtRuntimeError, ValidationError) as e:
|
||||||
msg = str(e) if isinstance(e, RuntimeException) else e.message
|
msg = str(e) if isinstance(e, DbtRuntimeError) else e.message
|
||||||
raise DbtProfileError(
|
raise DbtProfileError(
|
||||||
'Credentials in profile "{}", target "{}" invalid: {}'.format(
|
'Credentials in profile "{}", target "{}" invalid: {}'.format(
|
||||||
profile_name, target_name, msg
|
profile_name, target_name, msg
|
||||||
@@ -297,7 +299,7 @@ class Profile(HasCredentials):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
profile_data = renderer.render_data(raw_profile_data)
|
profile_data = renderer.render_data(raw_profile_data)
|
||||||
except CompilationException as exc:
|
except CompilationError as exc:
|
||||||
raise DbtProfileError(str(exc)) from exc
|
raise DbtProfileError(str(exc)) from exc
|
||||||
return target_name, profile_data
|
return target_name, profile_data
|
||||||
|
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ import hashlib
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from dbt import flags, deprecations
|
from dbt import flags, deprecations
|
||||||
from dbt.clients.system import resolve_path_from_base
|
from dbt.clients.system import path_exists, resolve_path_from_base, load_file_contents
|
||||||
from dbt.clients.system import path_exists
|
|
||||||
from dbt.clients.system import load_file_contents
|
|
||||||
from dbt.clients.yaml_helper import load_yaml_text
|
from dbt.clients.yaml_helper import load_yaml_text
|
||||||
from dbt.contracts.connection import QueryComment
|
from dbt.contracts.connection import QueryComment
|
||||||
from dbt.exceptions import DbtProjectError
|
from dbt.exceptions import (
|
||||||
from dbt.exceptions import SemverException
|
DbtProjectError,
|
||||||
from dbt.exceptions import validator_error_message
|
SemverError,
|
||||||
from dbt.exceptions import RuntimeException
|
ProjectContractBrokenError,
|
||||||
|
ProjectContractError,
|
||||||
|
DbtRuntimeError,
|
||||||
|
)
|
||||||
from dbt.graph import SelectionSpec
|
from dbt.graph import SelectionSpec
|
||||||
from dbt.helper_types import NoValue
|
from dbt.helper_types import NoValue
|
||||||
from dbt.semver import VersionSpecifier
|
from dbt.semver import VersionSpecifier, versions_compatible
|
||||||
from dbt.semver import versions_compatible
|
|
||||||
from dbt.version import get_installed_version
|
from dbt.version import get_installed_version
|
||||||
from dbt.utils import MultiDict
|
from dbt.utils import MultiDict
|
||||||
from dbt.node_types import NodeType
|
from dbt.node_types import NodeType
|
||||||
@@ -219,7 +219,7 @@ def _get_required_version(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
dbt_version = _parse_versions(dbt_raw_version)
|
dbt_version = _parse_versions(dbt_raw_version)
|
||||||
except SemverException as e:
|
except SemverError as e:
|
||||||
raise DbtProjectError(str(e)) from e
|
raise DbtProjectError(str(e)) from e
|
||||||
|
|
||||||
if verify_version:
|
if verify_version:
|
||||||
@@ -325,7 +325,7 @@ class PartialProject(RenderComponents):
|
|||||||
ProjectContract.validate(rendered.project_dict)
|
ProjectContract.validate(rendered.project_dict)
|
||||||
cfg = ProjectContract.from_dict(rendered.project_dict)
|
cfg = ProjectContract.from_dict(rendered.project_dict)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise DbtProjectError(validator_error_message(e)) from e
|
raise ProjectContractError(e) from e
|
||||||
# name/version are required in the Project definition, so we can assume
|
# name/version are required in the Project definition, so we can assume
|
||||||
# they are present
|
# they are present
|
||||||
name = cfg.name
|
name = cfg.name
|
||||||
@@ -381,6 +381,7 @@ class PartialProject(RenderComponents):
|
|||||||
sources: Dict[str, Any]
|
sources: Dict[str, Any]
|
||||||
tests: Dict[str, Any]
|
tests: Dict[str, Any]
|
||||||
metrics: Dict[str, Any]
|
metrics: Dict[str, Any]
|
||||||
|
entities: Dict[str, Any]
|
||||||
exposures: Dict[str, Any]
|
exposures: Dict[str, Any]
|
||||||
vars_value: VarProvider
|
vars_value: VarProvider
|
||||||
|
|
||||||
@@ -391,6 +392,7 @@ class PartialProject(RenderComponents):
|
|||||||
sources = cfg.sources
|
sources = cfg.sources
|
||||||
tests = cfg.tests
|
tests = cfg.tests
|
||||||
metrics = cfg.metrics
|
metrics = cfg.metrics
|
||||||
|
entities = cfg.entities
|
||||||
exposures = cfg.exposures
|
exposures = cfg.exposures
|
||||||
if cfg.vars is None:
|
if cfg.vars is None:
|
||||||
vars_dict: Dict[str, Any] = {}
|
vars_dict: Dict[str, Any] = {}
|
||||||
@@ -446,6 +448,7 @@ class PartialProject(RenderComponents):
|
|||||||
sources=sources,
|
sources=sources,
|
||||||
tests=tests,
|
tests=tests,
|
||||||
metrics=metrics,
|
metrics=metrics,
|
||||||
|
entities=entities,
|
||||||
exposures=exposures,
|
exposures=exposures,
|
||||||
vars=vars_value,
|
vars=vars_value,
|
||||||
config_version=cfg.config_version,
|
config_version=cfg.config_version,
|
||||||
@@ -550,6 +553,7 @@ class Project:
|
|||||||
sources: Dict[str, Any]
|
sources: Dict[str, Any]
|
||||||
tests: Dict[str, Any]
|
tests: Dict[str, Any]
|
||||||
metrics: Dict[str, Any]
|
metrics: Dict[str, Any]
|
||||||
|
entities: Dict[str, Any]
|
||||||
exposures: Dict[str, Any]
|
exposures: Dict[str, Any]
|
||||||
vars: VarProvider
|
vars: VarProvider
|
||||||
dbt_version: List[VersionSpecifier]
|
dbt_version: List[VersionSpecifier]
|
||||||
@@ -624,6 +628,7 @@ class Project:
|
|||||||
"sources": self.sources,
|
"sources": self.sources,
|
||||||
"tests": self.tests,
|
"tests": self.tests,
|
||||||
"metrics": self.metrics,
|
"metrics": self.metrics,
|
||||||
|
"entities": self.entities,
|
||||||
"exposures": self.exposures,
|
"exposures": self.exposures,
|
||||||
"vars": self.vars.to_dict(),
|
"vars": self.vars.to_dict(),
|
||||||
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
|
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
|
||||||
@@ -642,7 +647,7 @@ class Project:
|
|||||||
try:
|
try:
|
||||||
ProjectContract.validate(self.to_project_config())
|
ProjectContract.validate(self.to_project_config())
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise DbtProjectError(validator_error_message(e)) from e
|
raise ProjectContractBrokenError(e) from e
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def partial_load(cls, project_root: str, *, verify_version: bool = False) -> PartialProject:
|
def partial_load(cls, project_root: str, *, verify_version: bool = False) -> PartialProject:
|
||||||
@@ -667,7 +672,7 @@ class Project:
|
|||||||
|
|
||||||
def get_selector(self, name: str) -> Union[SelectionSpec, bool]:
|
def get_selector(self, name: str) -> Union[SelectionSpec, bool]:
|
||||||
if name not in self.selectors:
|
if name not in self.selectors:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"Could not find selector named {name}, expected one of {list(self.selectors)}"
|
f"Could not find selector named {name}, expected one of {list(self.selectors)}"
|
||||||
)
|
)
|
||||||
return self.selectors[name]["definition"]
|
return self.selectors[name]["definition"]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from dbt.context.target import TargetContext
|
|||||||
from dbt.context.secret import SecretContext, SECRET_PLACEHOLDER
|
from dbt.context.secret import SecretContext, SECRET_PLACEHOLDER
|
||||||
from dbt.context.base import BaseContext
|
from dbt.context.base import BaseContext
|
||||||
from dbt.contracts.connection import HasCredentials
|
from dbt.contracts.connection import HasCredentials
|
||||||
from dbt.exceptions import DbtProjectError, CompilationException, RecursionException
|
from dbt.exceptions import DbtProjectError, CompilationError, RecursionError
|
||||||
from dbt.utils import deep_map_render
|
from dbt.utils import deep_map_render
|
||||||
|
|
||||||
|
|
||||||
@@ -40,14 +40,14 @@ class BaseRenderer:
|
|||||||
try:
|
try:
|
||||||
with catch_jinja():
|
with catch_jinja():
|
||||||
return get_rendered(value, self.context, native=True)
|
return get_rendered(value, self.context, native=True)
|
||||||
except CompilationException as exc:
|
except CompilationError as exc:
|
||||||
msg = f"Could not render {value}: {exc.msg}"
|
msg = f"Could not render {value}: {exc.msg}"
|
||||||
raise CompilationException(msg) from exc
|
raise CompilationError(msg) from exc
|
||||||
|
|
||||||
def render_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
def render_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
return deep_map_render(self.render_entry, data)
|
return deep_map_render(self.render_entry, data)
|
||||||
except RecursionException:
|
except RecursionError:
|
||||||
raise DbtProjectError(
|
raise DbtProjectError(
|
||||||
f"Cycle detected: {self.name} input has a reference to itself", project=data
|
f"Cycle detected: {self.name} input has a reference to itself", project=data
|
||||||
)
|
)
|
||||||
@@ -159,7 +159,8 @@ class DbtProjectYamlRenderer(BaseRenderer):
|
|||||||
if first in {"seeds", "models", "snapshots", "tests"}:
|
if first in {"seeds", "models", "snapshots", "tests"}:
|
||||||
keypath_parts = {(k.lstrip("+ ") if isinstance(k, str) else k) for k in keypath}
|
keypath_parts = {(k.lstrip("+ ") if isinstance(k, str) else k) for k in keypath}
|
||||||
# model-level hooks
|
# model-level hooks
|
||||||
if "pre-hook" in keypath_parts or "post-hook" in keypath_parts:
|
late_rendered_hooks = {"pre-hook", "post-hook", "pre_hook", "post_hook"}
|
||||||
|
if keypath_parts.intersection(late_rendered_hooks):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ from dbt.contracts.project import Configuration, UserConfig
|
|||||||
from dbt.contracts.relation import ComponentName
|
from dbt.contracts.relation import ComponentName
|
||||||
from dbt.dataclass_schema import ValidationError
|
from dbt.dataclass_schema import ValidationError
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
|
ConfigContractBrokenError,
|
||||||
DbtProjectError,
|
DbtProjectError,
|
||||||
RuntimeException,
|
NonUniquePackageNameError,
|
||||||
raise_compiler_error,
|
DbtRuntimeError,
|
||||||
validator_error_message,
|
UninstalledPackagesFoundError,
|
||||||
)
|
)
|
||||||
from dbt.events.functions import warn_or_error
|
from dbt.events.functions import warn_or_error
|
||||||
from dbt.events.types import UnusedResourceConfigPath
|
from dbt.events.types import UnusedResourceConfigPath
|
||||||
@@ -116,6 +117,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
|
|||||||
sources=project.sources,
|
sources=project.sources,
|
||||||
tests=project.tests,
|
tests=project.tests,
|
||||||
metrics=project.metrics,
|
metrics=project.metrics,
|
||||||
|
entities=project.entities,
|
||||||
exposures=project.exposures,
|
exposures=project.exposures,
|
||||||
vars=project.vars,
|
vars=project.vars,
|
||||||
config_version=project.config_version,
|
config_version=project.config_version,
|
||||||
@@ -186,7 +188,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
|
|||||||
try:
|
try:
|
||||||
Configuration.validate(self.serialize())
|
Configuration.validate(self.serialize())
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise DbtProjectError(validator_error_message(e)) from e
|
raise ConfigContractBrokenError(e) from e
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_rendered_profile(
|
def _get_rendered_profile(
|
||||||
@@ -257,7 +259,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
|
|||||||
:param args: The arguments as parsed from the cli.
|
:param args: The arguments as parsed from the cli.
|
||||||
:raises DbtProjectError: If the project is invalid or missing.
|
:raises DbtProjectError: If the project is invalid or missing.
|
||||||
:raises DbtProfileError: If the profile is invalid or missing.
|
:raises DbtProfileError: If the profile is invalid or missing.
|
||||||
:raises ValidationException: If the cli variables are invalid.
|
:raises DbtValidationError: If the cli variables are invalid.
|
||||||
"""
|
"""
|
||||||
project, profile = cls.collect_parts(args)
|
project, profile = cls.collect_parts(args)
|
||||||
|
|
||||||
@@ -311,6 +313,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
|
|||||||
"sources": self._get_config_paths(self.sources),
|
"sources": self._get_config_paths(self.sources),
|
||||||
"tests": self._get_config_paths(self.tests),
|
"tests": self._get_config_paths(self.tests),
|
||||||
"metrics": self._get_config_paths(self.metrics),
|
"metrics": self._get_config_paths(self.metrics),
|
||||||
|
"entities": self._get_config_paths(self.entities),
|
||||||
"exposures": self._get_config_paths(self.exposures),
|
"exposures": self._get_config_paths(self.exposures),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,22 +355,15 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
|
|||||||
count_packages_specified = len(self.packages.packages) # type: ignore
|
count_packages_specified = len(self.packages.packages) # type: ignore
|
||||||
count_packages_installed = len(tuple(self._get_project_directories()))
|
count_packages_installed = len(tuple(self._get_project_directories()))
|
||||||
if count_packages_specified > count_packages_installed:
|
if count_packages_specified > count_packages_installed:
|
||||||
raise_compiler_error(
|
raise UninstalledPackagesFoundError(
|
||||||
f"dbt found {count_packages_specified} package(s) "
|
count_packages_specified,
|
||||||
f"specified in packages.yml, but only "
|
count_packages_installed,
|
||||||
f"{count_packages_installed} package(s) installed "
|
self.packages_install_path,
|
||||||
f'in {self.packages_install_path}. Run "dbt deps" to '
|
|
||||||
f"install package dependencies."
|
|
||||||
)
|
)
|
||||||
project_paths = itertools.chain(internal_packages, self._get_project_directories())
|
project_paths = itertools.chain(internal_packages, self._get_project_directories())
|
||||||
for project_name, project in self.load_projects(project_paths):
|
for project_name, project in self.load_projects(project_paths):
|
||||||
if project_name in all_projects:
|
if project_name in all_projects:
|
||||||
raise_compiler_error(
|
raise NonUniquePackageNameError(project_name)
|
||||||
f"dbt found more than one package with the name "
|
|
||||||
f'"{project_name}" included in this project. Package '
|
|
||||||
f"names must be unique in a project. Please rename "
|
|
||||||
f"one of these packages."
|
|
||||||
)
|
|
||||||
all_projects[project_name] = project
|
all_projects[project_name] = project
|
||||||
self.dependencies = all_projects
|
self.dependencies = all_projects
|
||||||
return self.dependencies
|
return self.dependencies
|
||||||
@@ -432,7 +428,7 @@ class UnsetProfile(Profile):
|
|||||||
|
|
||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name):
|
||||||
if name in {"profile_name", "target_name", "threads"}:
|
if name in {"profile_name", "target_name", "threads"}:
|
||||||
raise RuntimeException(f'Error: disallowed attribute "{name}" - no profile!')
|
raise DbtRuntimeError(f'Error: disallowed attribute "{name}" - no profile!')
|
||||||
|
|
||||||
return Profile.__getattribute__(self, name)
|
return Profile.__getattribute__(self, name)
|
||||||
|
|
||||||
@@ -459,7 +455,7 @@ class UnsetProfileConfig(RuntimeConfig):
|
|||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name):
|
||||||
# Override __getattribute__ to check that the attribute isn't 'banned'.
|
# Override __getattribute__ to check that the attribute isn't 'banned'.
|
||||||
if name in {"profile_name", "target_name"}:
|
if name in {"profile_name", "target_name"}:
|
||||||
raise RuntimeException(f'Error: disallowed attribute "{name}" - no profile!')
|
raise DbtRuntimeError(f'Error: disallowed attribute "{name}" - no profile!')
|
||||||
|
|
||||||
# avoid every attribute access triggering infinite recursion
|
# avoid every attribute access triggering infinite recursion
|
||||||
return RuntimeConfig.__getattribute__(self, name)
|
return RuntimeConfig.__getattribute__(self, name)
|
||||||
@@ -506,6 +502,7 @@ class UnsetProfileConfig(RuntimeConfig):
|
|||||||
"sources": self.sources,
|
"sources": self.sources,
|
||||||
"tests": self.tests,
|
"tests": self.tests,
|
||||||
"metrics": self.metrics,
|
"metrics": self.metrics,
|
||||||
|
"entities": self.entities,
|
||||||
"exposures": self.exposures,
|
"exposures": self.exposures,
|
||||||
"vars": self.vars.to_dict(),
|
"vars": self.vars.to_dict(),
|
||||||
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
|
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
|
||||||
@@ -568,6 +565,7 @@ class UnsetProfileConfig(RuntimeConfig):
|
|||||||
sources=project.sources,
|
sources=project.sources,
|
||||||
tests=project.tests,
|
tests=project.tests,
|
||||||
metrics=project.metrics,
|
metrics=project.metrics,
|
||||||
|
entities=project.entities,
|
||||||
exposures=project.exposures,
|
exposures=project.exposures,
|
||||||
vars=project.vars,
|
vars=project.vars,
|
||||||
config_version=project.config_version,
|
config_version=project.config_version,
|
||||||
@@ -608,7 +606,7 @@ class UnsetProfileConfig(RuntimeConfig):
|
|||||||
:param args: The arguments as parsed from the cli.
|
:param args: The arguments as parsed from the cli.
|
||||||
:raises DbtProjectError: If the project is invalid or missing.
|
:raises DbtProjectError: If the project is invalid or missing.
|
||||||
:raises DbtProfileError: If the profile is invalid or missing.
|
:raises DbtProfileError: If the profile is invalid or missing.
|
||||||
:raises ValidationException: If the cli variables are invalid.
|
:raises DbtValidationError: If the cli variables are invalid.
|
||||||
"""
|
"""
|
||||||
project, profile = cls.collect_parts(args)
|
project, profile = cls.collect_parts(args)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from dbt.clients.system import (
|
|||||||
resolve_path_from_base,
|
resolve_path_from_base,
|
||||||
)
|
)
|
||||||
from dbt.contracts.selection import SelectorFile
|
from dbt.contracts.selection import SelectorFile
|
||||||
from dbt.exceptions import DbtSelectorsError, RuntimeException
|
from dbt.exceptions import DbtSelectorsError, DbtRuntimeError
|
||||||
from dbt.graph import parse_from_selectors_definition, SelectionSpec
|
from dbt.graph import parse_from_selectors_definition, SelectionSpec
|
||||||
from dbt.graph.selector_spec import SelectionCriteria
|
from dbt.graph.selector_spec import SelectionCriteria
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class SelectorConfig(Dict[str, Dict[str, Union[SelectionSpec, bool]]]):
|
|||||||
f"yaml-selectors",
|
f"yaml-selectors",
|
||||||
result_type="invalid_selector",
|
result_type="invalid_selector",
|
||||||
) from exc
|
) from exc
|
||||||
except RuntimeException as exc:
|
except DbtRuntimeError as exc:
|
||||||
raise DbtSelectorsError(
|
raise DbtSelectorsError(
|
||||||
f"Could not read selector file data: {exc}",
|
f"Could not read selector file data: {exc}",
|
||||||
result_type="invalid_selector",
|
result_type="invalid_selector",
|
||||||
@@ -62,7 +62,7 @@ class SelectorConfig(Dict[str, Dict[str, Union[SelectionSpec, bool]]]):
|
|||||||
) -> "SelectorConfig":
|
) -> "SelectorConfig":
|
||||||
try:
|
try:
|
||||||
rendered = renderer.render_data(data)
|
rendered = renderer.render_data(data)
|
||||||
except (ValidationError, RuntimeException) as exc:
|
except (ValidationError, DbtRuntimeError) as exc:
|
||||||
raise DbtSelectorsError(
|
raise DbtSelectorsError(
|
||||||
f"Could not render selector data: {exc}",
|
f"Could not render selector data: {exc}",
|
||||||
result_type="invalid_selector",
|
result_type="invalid_selector",
|
||||||
@@ -77,7 +77,7 @@ class SelectorConfig(Dict[str, Dict[str, Union[SelectionSpec, bool]]]):
|
|||||||
) -> "SelectorConfig":
|
) -> "SelectorConfig":
|
||||||
try:
|
try:
|
||||||
data = load_yaml_text(load_file_contents(str(path)))
|
data = load_yaml_text(load_file_contents(str(path)))
|
||||||
except (ValidationError, RuntimeException) as exc:
|
except (ValidationError, DbtRuntimeError) as exc:
|
||||||
raise DbtSelectorsError(
|
raise DbtSelectorsError(
|
||||||
f"Could not read selector file: {exc}",
|
f"Could not read selector file: {exc}",
|
||||||
result_type="invalid_selector",
|
result_type="invalid_selector",
|
||||||
|
|||||||
@@ -8,24 +8,24 @@ from dbt.clients import yaml_helper
|
|||||||
from dbt.config import Profile, Project, read_user_config
|
from dbt.config import Profile, Project, read_user_config
|
||||||
from dbt.config.renderer import DbtProjectYamlRenderer, ProfileRenderer
|
from dbt.config.renderer import DbtProjectYamlRenderer, ProfileRenderer
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import InvalidVarsYAML
|
from dbt.events.types import InvalidOptionYAML
|
||||||
from dbt.exceptions import ValidationException, raise_compiler_error
|
from dbt.exceptions import DbtValidationError, OptionNotYamlDictError
|
||||||
|
|
||||||
|
|
||||||
def parse_cli_vars(var_string: str) -> Dict[str, Any]:
|
def parse_cli_vars(var_string: str) -> Dict[str, Any]:
|
||||||
|
return parse_cli_yaml_string(var_string, "vars")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_cli_yaml_string(var_string: str, cli_option_name: str) -> Dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
cli_vars = yaml_helper.load_yaml_text(var_string)
|
cli_vars = yaml_helper.load_yaml_text(var_string)
|
||||||
var_type = type(cli_vars)
|
var_type = type(cli_vars)
|
||||||
if var_type is dict:
|
if var_type is dict:
|
||||||
return cli_vars
|
return cli_vars
|
||||||
else:
|
else:
|
||||||
type_name = var_type.__name__
|
raise OptionNotYamlDictError(var_type, cli_option_name)
|
||||||
raise_compiler_error(
|
except DbtValidationError:
|
||||||
"The --vars argument must be a YAML dictionary, but was "
|
fire_event(InvalidOptionYAML(option_name=cli_option_name))
|
||||||
"of type '{}'".format(type_name)
|
|
||||||
)
|
|
||||||
except ValidationException:
|
|
||||||
fire_event(InvalidVarsYAML())
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,22 @@ from typing import Any, Dict, NoReturn, Optional, Mapping, Iterable, Set, List
|
|||||||
|
|
||||||
from dbt import flags
|
from dbt import flags
|
||||||
from dbt import tracking
|
from dbt import tracking
|
||||||
|
from dbt import utils
|
||||||
from dbt.clients.jinja import get_rendered
|
from dbt.clients.jinja import get_rendered
|
||||||
from dbt.clients.yaml_helper import yaml, safe_load, SafeLoader, Loader, Dumper # noqa: F401
|
from dbt.clients.yaml_helper import yaml, safe_load, SafeLoader, Loader, Dumper # noqa: F401
|
||||||
from dbt.constants import SECRET_ENV_PREFIX, DEFAULT_ENV_PLACEHOLDER
|
from dbt.constants import SECRET_ENV_PREFIX, DEFAULT_ENV_PLACEHOLDER
|
||||||
from dbt.contracts.graph.compiled import CompiledResource
|
from dbt.contracts.graph.nodes import Resource
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
CompilationException,
|
SecretEnvVarLocationError,
|
||||||
|
EnvVarMissingError,
|
||||||
MacroReturn,
|
MacroReturn,
|
||||||
raise_compiler_error,
|
RequiredVarNotFoundError,
|
||||||
raise_parsing_error,
|
SetStrictWrongTypeError,
|
||||||
disallow_secret_env_var,
|
ZipStrictWrongTypeError,
|
||||||
)
|
)
|
||||||
from dbt.events.functions import fire_event, get_invocation_id
|
from dbt.events.functions import fire_event, get_invocation_id
|
||||||
from dbt.events.types import JinjaLogInfo, JinjaLogDebug
|
from dbt.events.types import JinjaLogInfo, JinjaLogDebug
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from dbt.version import __version__ as dbt_version
|
from dbt.version import __version__ as dbt_version
|
||||||
|
|
||||||
# These modules are added to the context. Consider alternative
|
# These modules are added to the context. Consider alternative
|
||||||
@@ -126,18 +129,17 @@ class ContextMeta(type):
|
|||||||
|
|
||||||
|
|
||||||
class Var:
|
class Var:
|
||||||
UndefinedVarError = "Required var '{}' not found in config:\nVars supplied to {} = {}"
|
|
||||||
_VAR_NOTSET = object()
|
_VAR_NOTSET = object()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
context: Mapping[str, Any],
|
context: Mapping[str, Any],
|
||||||
cli_vars: Mapping[str, Any],
|
cli_vars: Mapping[str, Any],
|
||||||
node: Optional[CompiledResource] = None,
|
node: Optional[Resource] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._context: Mapping[str, Any] = context
|
self._context: Mapping[str, Any] = context
|
||||||
self._cli_vars: Mapping[str, Any] = cli_vars
|
self._cli_vars: Mapping[str, Any] = cli_vars
|
||||||
self._node: Optional[CompiledResource] = node
|
self._node: Optional[Resource] = node
|
||||||
self._merged: Mapping[str, Any] = self._generate_merged()
|
self._merged: Mapping[str, Any] = self._generate_merged()
|
||||||
|
|
||||||
def _generate_merged(self) -> Mapping[str, Any]:
|
def _generate_merged(self) -> Mapping[str, Any]:
|
||||||
@@ -151,10 +153,7 @@ class Var:
|
|||||||
return "<Configuration>"
|
return "<Configuration>"
|
||||||
|
|
||||||
def get_missing_var(self, var_name):
|
def get_missing_var(self, var_name):
|
||||||
dct = {k: self._merged[k] for k in self._merged}
|
raise RequiredVarNotFoundError(var_name, self._merged, self._node)
|
||||||
pretty_vars = json.dumps(dct, sort_keys=True, indent=4)
|
|
||||||
msg = self.UndefinedVarError.format(var_name, self.node_name, pretty_vars)
|
|
||||||
raise_compiler_error(msg, self._node)
|
|
||||||
|
|
||||||
def has_var(self, var_name: str):
|
def has_var(self, var_name: str):
|
||||||
return var_name in self._merged
|
return var_name in self._merged
|
||||||
@@ -298,7 +297,7 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
"""
|
"""
|
||||||
return_value = None
|
return_value = None
|
||||||
if var.startswith(SECRET_ENV_PREFIX):
|
if var.startswith(SECRET_ENV_PREFIX):
|
||||||
disallow_secret_env_var(var)
|
raise SecretEnvVarLocationError(var)
|
||||||
if var in os.environ:
|
if var in os.environ:
|
||||||
return_value = os.environ[var]
|
return_value = os.environ[var]
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
@@ -313,8 +312,7 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
|
|
||||||
return return_value
|
return return_value
|
||||||
else:
|
else:
|
||||||
msg = f"Env var required but not provided: '{var}'"
|
raise EnvVarMissingError(var)
|
||||||
raise_parsing_error(msg)
|
|
||||||
|
|
||||||
if os.environ.get("DBT_MACRO_DEBUGGING"):
|
if os.environ.get("DBT_MACRO_DEBUGGING"):
|
||||||
|
|
||||||
@@ -495,7 +493,7 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
try:
|
try:
|
||||||
return set(value)
|
return set(value)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise CompilationException(e)
|
raise SetStrictWrongTypeError(e)
|
||||||
|
|
||||||
@contextmember("zip")
|
@contextmember("zip")
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -539,7 +537,7 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
try:
|
try:
|
||||||
return zip(*args)
|
return zip(*args)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise CompilationException(e)
|
raise ZipStrictWrongTypeError(e)
|
||||||
|
|
||||||
@contextmember
|
@contextmember
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -557,9 +555,9 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
{% endmacro %}"
|
{% endmacro %}"
|
||||||
"""
|
"""
|
||||||
if info:
|
if info:
|
||||||
fire_event(JinjaLogInfo(msg=msg))
|
fire_event(JinjaLogInfo(msg=msg, node_info=get_node_info()))
|
||||||
else:
|
else:
|
||||||
fire_event(JinjaLogDebug(msg=msg))
|
fire_event(JinjaLogDebug(msg=msg, node_info=get_node_info()))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@contextproperty
|
@contextproperty
|
||||||
@@ -636,9 +634,8 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
This supports all flags defined in flags submodule (core/dbt/flags.py)
|
This supports all flags defined in flags submodule (core/dbt/flags.py)
|
||||||
TODO: Replace with object that provides read-only access to flag values
|
|
||||||
"""
|
"""
|
||||||
return flags
|
return flags.get_flag_obj()
|
||||||
|
|
||||||
@contextmember
|
@contextmember
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -687,6 +684,19 @@ class BaseContext(metaclass=ContextMeta):
|
|||||||
dict_diff.update({k: dict_a[k]})
|
dict_diff.update({k: dict_a[k]})
|
||||||
return dict_diff
|
return dict_diff
|
||||||
|
|
||||||
|
@contextmember
|
||||||
|
@staticmethod
|
||||||
|
def local_md5(value: str) -> str:
|
||||||
|
"""Calculates an MD5 hash of the given string.
|
||||||
|
It's called "local_md5" to emphasize that it runs locally in dbt (in jinja context) and not an MD5 SQL command.
|
||||||
|
|
||||||
|
:param value: The value to hash
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
{% set value_hash = local_md5("hello world") %}
|
||||||
|
"""
|
||||||
|
return utils.md5(value)
|
||||||
|
|
||||||
|
|
||||||
def generate_base_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]:
|
def generate_base_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
ctx = BaseContext(cli_vars)
|
ctx = BaseContext(cli_vars)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from dbt.utils import MultiDict
|
|||||||
|
|
||||||
from dbt.context.base import contextproperty, contextmember, Var
|
from dbt.context.base import contextproperty, contextmember, Var
|
||||||
from dbt.context.target import TargetContext
|
from dbt.context.target import TargetContext
|
||||||
from dbt.exceptions import raise_parsing_error, disallow_secret_env_var
|
from dbt.exceptions import EnvVarMissingError, SecretEnvVarLocationError
|
||||||
|
|
||||||
|
|
||||||
class ConfiguredContext(TargetContext):
|
class ConfiguredContext(TargetContext):
|
||||||
@@ -86,7 +86,7 @@ class SchemaYamlContext(ConfiguredContext):
|
|||||||
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
||||||
return_value = None
|
return_value = None
|
||||||
if var.startswith(SECRET_ENV_PREFIX):
|
if var.startswith(SECRET_ENV_PREFIX):
|
||||||
disallow_secret_env_var(var)
|
raise SecretEnvVarLocationError(var)
|
||||||
if var in os.environ:
|
if var in os.environ:
|
||||||
return_value = os.environ[var]
|
return_value = os.environ[var]
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
@@ -104,8 +104,7 @@ class SchemaYamlContext(ConfiguredContext):
|
|||||||
|
|
||||||
return return_value
|
return return_value
|
||||||
else:
|
else:
|
||||||
msg = f"Env var required but not provided: '{var}'"
|
raise EnvVarMissingError(var)
|
||||||
raise_parsing_error(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class MacroResolvingContext(ConfiguredContext):
|
class MacroResolvingContext(ConfiguredContext):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import List, Iterator, Dict, Any, TypeVar, Generic
|
|||||||
|
|
||||||
from dbt.config import RuntimeConfig, Project, IsFQNResource
|
from dbt.config import RuntimeConfig, Project, IsFQNResource
|
||||||
from dbt.contracts.graph.model_config import BaseConfig, get_config_for, _listify
|
from dbt.contracts.graph.model_config import BaseConfig, get_config_for, _listify
|
||||||
from dbt.exceptions import InternalException
|
from dbt.exceptions import DbtInternalError
|
||||||
from dbt.node_types import NodeType
|
from dbt.node_types import NodeType
|
||||||
from dbt.utils import fqn_search
|
from dbt.utils import fqn_search
|
||||||
|
|
||||||
@@ -45,6 +45,8 @@ class UnrenderedConfig(ConfigSource):
|
|||||||
model_configs = unrendered.get("tests")
|
model_configs = unrendered.get("tests")
|
||||||
elif resource_type == NodeType.Metric:
|
elif resource_type == NodeType.Metric:
|
||||||
model_configs = unrendered.get("metrics")
|
model_configs = unrendered.get("metrics")
|
||||||
|
elif resource_type == NodeType.Entity:
|
||||||
|
model_configs = unrendered.get("entities")
|
||||||
elif resource_type == NodeType.Exposure:
|
elif resource_type == NodeType.Exposure:
|
||||||
model_configs = unrendered.get("exposures")
|
model_configs = unrendered.get("exposures")
|
||||||
else:
|
else:
|
||||||
@@ -70,6 +72,8 @@ class RenderedConfig(ConfigSource):
|
|||||||
model_configs = self.project.tests
|
model_configs = self.project.tests
|
||||||
elif resource_type == NodeType.Metric:
|
elif resource_type == NodeType.Metric:
|
||||||
model_configs = self.project.metrics
|
model_configs = self.project.metrics
|
||||||
|
elif resource_type == NodeType.Entity:
|
||||||
|
model_configs = self.project.entities
|
||||||
elif resource_type == NodeType.Exposure:
|
elif resource_type == NodeType.Exposure:
|
||||||
model_configs = self.project.exposures
|
model_configs = self.project.exposures
|
||||||
else:
|
else:
|
||||||
@@ -89,7 +93,7 @@ class BaseContextConfigGenerator(Generic[T]):
|
|||||||
return self._active_project
|
return self._active_project
|
||||||
dependencies = self._active_project.load_dependencies()
|
dependencies = self._active_project.load_dependencies()
|
||||||
if project_name not in dependencies:
|
if project_name not in dependencies:
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
f"Project name {project_name} not found in dependencies "
|
f"Project name {project_name} not found in dependencies "
|
||||||
f"(found {list(dependencies)})"
|
f"(found {list(dependencies)})"
|
||||||
)
|
)
|
||||||
@@ -287,14 +291,14 @@ class ContextConfig:
|
|||||||
|
|
||||||
elif k in BaseConfig.mergebehavior["update"]:
|
elif k in BaseConfig.mergebehavior["update"]:
|
||||||
if not isinstance(v, dict):
|
if not isinstance(v, dict):
|
||||||
raise InternalException(f"expected dict, got {v}")
|
raise DbtInternalError(f"expected dict, got {v}")
|
||||||
if k in config_call_dict and isinstance(config_call_dict[k], dict):
|
if k in config_call_dict and isinstance(config_call_dict[k], dict):
|
||||||
config_call_dict[k].update(v)
|
config_call_dict[k].update(v)
|
||||||
else:
|
else:
|
||||||
config_call_dict[k] = v
|
config_call_dict[k] = v
|
||||||
elif k in BaseConfig.mergebehavior["dict_key_append"]:
|
elif k in BaseConfig.mergebehavior["dict_key_append"]:
|
||||||
if not isinstance(v, dict):
|
if not isinstance(v, dict):
|
||||||
raise InternalException(f"expected dict, got {v}")
|
raise DbtInternalError(f"expected dict, got {v}")
|
||||||
if k in config_call_dict: # should always be a dict
|
if k in config_call_dict: # should always be a dict
|
||||||
for key, value in v.items():
|
for key, value in v.items():
|
||||||
extend = False
|
extend = False
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
from typing import Any, Dict, Union
|
from typing import Any, Dict, Union
|
||||||
|
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
doc_invalid_args,
|
DocTargetNotFoundError,
|
||||||
doc_target_not_found,
|
DocArgsError,
|
||||||
)
|
)
|
||||||
from dbt.config.runtime import RuntimeConfig
|
from dbt.config.runtime import RuntimeConfig
|
||||||
from dbt.contracts.graph.compiled import CompileResultNode
|
|
||||||
from dbt.contracts.graph.manifest import Manifest
|
from dbt.contracts.graph.manifest import Manifest
|
||||||
from dbt.contracts.graph.parsed import ParsedMacro
|
from dbt.contracts.graph.nodes import Macro, ResultNode
|
||||||
|
|
||||||
from dbt.context.base import contextmember
|
from dbt.context.base import contextmember
|
||||||
from dbt.context.configured import SchemaYamlContext
|
from dbt.context.configured import SchemaYamlContext
|
||||||
@@ -17,7 +16,7 @@ class DocsRuntimeContext(SchemaYamlContext):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
node: Union[ParsedMacro, CompileResultNode],
|
node: Union[Macro, ResultNode],
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
current_project: str,
|
current_project: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -53,9 +52,9 @@ class DocsRuntimeContext(SchemaYamlContext):
|
|||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
doc_package_name, doc_name = args
|
doc_package_name, doc_name = args
|
||||||
else:
|
else:
|
||||||
doc_invalid_args(self.node, args)
|
raise DocArgsError(self.node, args)
|
||||||
|
|
||||||
# ParsedDocumentation
|
# Documentation
|
||||||
target_doc = self.manifest.resolve_doc(
|
target_doc = self.manifest.resolve_doc(
|
||||||
doc_name,
|
doc_name,
|
||||||
doc_package_name,
|
doc_package_name,
|
||||||
@@ -69,7 +68,9 @@ class DocsRuntimeContext(SchemaYamlContext):
|
|||||||
# TODO CT-211
|
# TODO CT-211
|
||||||
source_file.add_node(self.node.unique_id) # type: ignore[union-attr]
|
source_file.add_node(self.node.unique_id) # type: ignore[union-attr]
|
||||||
else:
|
else:
|
||||||
doc_target_not_found(self.node, doc_name, doc_package_name)
|
raise DocTargetNotFoundError(
|
||||||
|
node=self.node, target_doc_name=doc_name, target_doc_package=doc_package_name
|
||||||
|
)
|
||||||
|
|
||||||
return target_doc.block_contents
|
return target_doc.block_contents
|
||||||
|
|
||||||
|
|||||||
144
core/dbt/context/exceptions_jinja.py
Normal file
144
core/dbt/context/exceptions_jinja.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import functools
|
||||||
|
from typing import NoReturn
|
||||||
|
|
||||||
|
from dbt.events.functions import warn_or_error
|
||||||
|
from dbt.events.helpers import env_secrets, scrub_secrets
|
||||||
|
from dbt.events.types import JinjaLogWarning
|
||||||
|
|
||||||
|
from dbt.exceptions import (
|
||||||
|
DbtRuntimeError,
|
||||||
|
MissingConfigError,
|
||||||
|
MissingMaterializationError,
|
||||||
|
MissingRelationError,
|
||||||
|
AmbiguousAliasError,
|
||||||
|
AmbiguousCatalogMatchError,
|
||||||
|
CacheInconsistencyError,
|
||||||
|
DataclassNotDictError,
|
||||||
|
CompilationError,
|
||||||
|
DbtDatabaseError,
|
||||||
|
DependencyNotFoundError,
|
||||||
|
DependencyError,
|
||||||
|
DuplicatePatchPathError,
|
||||||
|
DuplicateResourceNameError,
|
||||||
|
PropertyYMLError,
|
||||||
|
NotImplementedError,
|
||||||
|
RelationWrongTypeError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def warn(msg, node=None):
|
||||||
|
warn_or_error(JinjaLogWarning(msg=msg), node=node)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def missing_config(model, name) -> NoReturn:
|
||||||
|
raise MissingConfigError(unique_id=model.unique_id, name=name)
|
||||||
|
|
||||||
|
|
||||||
|
def missing_materialization(model, adapter_type) -> NoReturn:
|
||||||
|
raise MissingMaterializationError(
|
||||||
|
materialization=model.config.materialized, adapter_type=adapter_type
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def missing_relation(relation, model=None) -> NoReturn:
|
||||||
|
raise MissingRelationError(relation, model)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_ambiguous_alias(node_1, node_2, duped_name=None) -> NoReturn:
|
||||||
|
raise AmbiguousAliasError(node_1, node_2, duped_name)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_ambiguous_catalog_match(unique_id, match_1, match_2) -> NoReturn:
|
||||||
|
raise AmbiguousCatalogMatchError(unique_id, match_1, match_2)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_cache_inconsistent(message) -> NoReturn:
|
||||||
|
raise CacheInconsistencyError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_dataclass_not_dict(obj) -> NoReturn:
|
||||||
|
raise DataclassNotDictError(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_compiler_error(msg, node=None) -> NoReturn:
|
||||||
|
raise CompilationError(msg, node)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_database_error(msg, node=None) -> NoReturn:
|
||||||
|
raise DbtDatabaseError(msg, node)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_dep_not_found(node, node_description, required_pkg) -> NoReturn:
|
||||||
|
raise DependencyNotFoundError(node, node_description, required_pkg)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_dependency_error(msg) -> NoReturn:
|
||||||
|
raise DependencyError(scrub_secrets(msg, env_secrets()))
|
||||||
|
|
||||||
|
|
||||||
|
def raise_duplicate_patch_name(patch_1, existing_patch_path) -> NoReturn:
|
||||||
|
raise DuplicatePatchPathError(patch_1, existing_patch_path)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_duplicate_resource_name(node_1, node_2) -> NoReturn:
|
||||||
|
raise DuplicateResourceNameError(node_1, node_2)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_invalid_property_yml_version(path, issue) -> NoReturn:
|
||||||
|
raise PropertyYMLError(path, issue)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_not_implemented(msg) -> NoReturn:
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def relation_wrong_type(relation, expected_type, model=None) -> NoReturn:
|
||||||
|
raise RelationWrongTypeError(relation, expected_type, model)
|
||||||
|
|
||||||
|
|
||||||
|
# Update this when a new function should be added to the
|
||||||
|
# dbt context's `exceptions` key!
|
||||||
|
CONTEXT_EXPORTS = {
|
||||||
|
fn.__name__: fn
|
||||||
|
for fn in [
|
||||||
|
warn,
|
||||||
|
missing_config,
|
||||||
|
missing_materialization,
|
||||||
|
missing_relation,
|
||||||
|
raise_ambiguous_alias,
|
||||||
|
raise_ambiguous_catalog_match,
|
||||||
|
raise_cache_inconsistent,
|
||||||
|
raise_dataclass_not_dict,
|
||||||
|
raise_compiler_error,
|
||||||
|
raise_database_error,
|
||||||
|
raise_dep_not_found,
|
||||||
|
raise_dependency_error,
|
||||||
|
raise_duplicate_patch_name,
|
||||||
|
raise_duplicate_resource_name,
|
||||||
|
raise_invalid_property_yml_version,
|
||||||
|
raise_not_implemented,
|
||||||
|
relation_wrong_type,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# wraps context based exceptions in node info
|
||||||
|
def wrapper(model):
|
||||||
|
def wrap(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except DbtRuntimeError as exc:
|
||||||
|
exc.add_node(model)
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def wrapped_exports(model):
|
||||||
|
wrap = wrapper(model)
|
||||||
|
return {name: wrap(export) for name, export in CONTEXT_EXPORTS.items()}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import Dict, MutableMapping, Optional
|
from typing import Dict, MutableMapping, Optional
|
||||||
from dbt.contracts.graph.parsed import ParsedMacro
|
from dbt.contracts.graph.nodes import Macro
|
||||||
from dbt.exceptions import raise_duplicate_macro_name, raise_compiler_error
|
from dbt.exceptions import DuplicateMacroNameError, PackageNotFoundForMacroError
|
||||||
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
||||||
from dbt.clients.jinja import MacroGenerator
|
from dbt.clients.jinja import MacroGenerator
|
||||||
|
|
||||||
MacroNamespace = Dict[str, ParsedMacro]
|
MacroNamespace = Dict[str, Macro]
|
||||||
|
|
||||||
|
|
||||||
# This class builds the MacroResolver by adding macros
|
# This class builds the MacroResolver by adding macros
|
||||||
@@ -21,7 +21,7 @@ MacroNamespace = Dict[str, ParsedMacro]
|
|||||||
class MacroResolver:
|
class MacroResolver:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
macros: MutableMapping[str, ParsedMacro],
|
macros: MutableMapping[str, Macro],
|
||||||
root_project_name: str,
|
root_project_name: str,
|
||||||
internal_package_names,
|
internal_package_names,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -77,7 +77,7 @@ class MacroResolver:
|
|||||||
def _add_macro_to(
|
def _add_macro_to(
|
||||||
self,
|
self,
|
||||||
package_namespaces: Dict[str, MacroNamespace],
|
package_namespaces: Dict[str, MacroNamespace],
|
||||||
macro: ParsedMacro,
|
macro: Macro,
|
||||||
):
|
):
|
||||||
if macro.package_name in package_namespaces:
|
if macro.package_name in package_namespaces:
|
||||||
namespace = package_namespaces[macro.package_name]
|
namespace = package_namespaces[macro.package_name]
|
||||||
@@ -86,10 +86,10 @@ class MacroResolver:
|
|||||||
package_namespaces[macro.package_name] = namespace
|
package_namespaces[macro.package_name] = namespace
|
||||||
|
|
||||||
if macro.name in namespace:
|
if macro.name in namespace:
|
||||||
raise_duplicate_macro_name(macro, macro, macro.package_name)
|
raise DuplicateMacroNameError(macro, macro, macro.package_name)
|
||||||
package_namespaces[macro.package_name][macro.name] = macro
|
package_namespaces[macro.package_name][macro.name] = macro
|
||||||
|
|
||||||
def add_macro(self, macro: ParsedMacro):
|
def add_macro(self, macro: Macro):
|
||||||
macro_name: str = macro.name
|
macro_name: str = macro.name
|
||||||
|
|
||||||
# internal macros (from plugins) will be processed separately from
|
# internal macros (from plugins) will be processed separately from
|
||||||
@@ -187,7 +187,7 @@ class TestMacroNamespace:
|
|||||||
elif package_name in self.macro_resolver.packages:
|
elif package_name in self.macro_resolver.packages:
|
||||||
macro = self.macro_resolver.packages[package_name].get(name)
|
macro = self.macro_resolver.packages[package_name].get(name)
|
||||||
else:
|
else:
|
||||||
raise_compiler_error(f"Could not find package '{package_name}'")
|
raise PackageNotFoundForMacroError(package_name)
|
||||||
if not macro:
|
if not macro:
|
||||||
return None
|
return None
|
||||||
macro_func = MacroGenerator(macro, self.ctx, self.node, self.thread_ctx)
|
macro_func = MacroGenerator(macro, self.ctx, self.node, self.thread_ctx)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from typing import Any, Dict, Iterable, Union, Optional, List, Iterator, Mapping, Set
|
from typing import Any, Dict, Iterable, Union, Optional, List, Iterator, Mapping, Set
|
||||||
|
|
||||||
from dbt.clients.jinja import MacroGenerator, MacroStack
|
from dbt.clients.jinja import MacroGenerator, MacroStack
|
||||||
from dbt.contracts.graph.parsed import ParsedMacro
|
from dbt.contracts.graph.nodes import Macro
|
||||||
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
||||||
from dbt.exceptions import raise_duplicate_macro_name, raise_compiler_error
|
from dbt.exceptions import DuplicateMacroNameError, PackageNotFoundForMacroError
|
||||||
|
|
||||||
|
|
||||||
FlatNamespace = Dict[str, MacroGenerator]
|
FlatNamespace = Dict[str, MacroGenerator]
|
||||||
@@ -75,7 +75,7 @@ class MacroNamespace(Mapping):
|
|||||||
elif package_name in self.packages:
|
elif package_name in self.packages:
|
||||||
return self.packages[package_name].get(name)
|
return self.packages[package_name].get(name)
|
||||||
else:
|
else:
|
||||||
raise_compiler_error(f"Could not find package '{package_name}'")
|
raise PackageNotFoundForMacroError(package_name)
|
||||||
|
|
||||||
|
|
||||||
# This class builds the MacroNamespace by adding macros to
|
# This class builds the MacroNamespace by adding macros to
|
||||||
@@ -112,7 +112,7 @@ class MacroNamespaceBuilder:
|
|||||||
def _add_macro_to(
|
def _add_macro_to(
|
||||||
self,
|
self,
|
||||||
hierarchy: Dict[str, FlatNamespace],
|
hierarchy: Dict[str, FlatNamespace],
|
||||||
macro: ParsedMacro,
|
macro: Macro,
|
||||||
macro_func: MacroGenerator,
|
macro_func: MacroGenerator,
|
||||||
):
|
):
|
||||||
if macro.package_name in hierarchy:
|
if macro.package_name in hierarchy:
|
||||||
@@ -122,10 +122,10 @@ class MacroNamespaceBuilder:
|
|||||||
hierarchy[macro.package_name] = namespace
|
hierarchy[macro.package_name] = namespace
|
||||||
|
|
||||||
if macro.name in namespace:
|
if macro.name in namespace:
|
||||||
raise_duplicate_macro_name(macro_func.macro, macro, macro.package_name)
|
raise DuplicateMacroNameError(macro_func.macro, macro, macro.package_name)
|
||||||
hierarchy[macro.package_name][macro.name] = macro_func
|
hierarchy[macro.package_name][macro.name] = macro_func
|
||||||
|
|
||||||
def add_macro(self, macro: ParsedMacro, ctx: Dict[str, Any]):
|
def add_macro(self, macro: Macro, ctx: Dict[str, Any]):
|
||||||
macro_name: str = macro.name
|
macro_name: str = macro.name
|
||||||
|
|
||||||
# MacroGenerator is in clients/jinja.py
|
# MacroGenerator is in clients/jinja.py
|
||||||
@@ -147,13 +147,11 @@ class MacroNamespaceBuilder:
|
|||||||
elif macro.package_name == self.root_package:
|
elif macro.package_name == self.root_package:
|
||||||
self.globals[macro_name] = macro_func
|
self.globals[macro_name] = macro_func
|
||||||
|
|
||||||
def add_macros(self, macros: Iterable[ParsedMacro], ctx: Dict[str, Any]):
|
def add_macros(self, macros: Iterable[Macro], ctx: Dict[str, Any]):
|
||||||
for macro in macros:
|
for macro in macros:
|
||||||
self.add_macro(macro, ctx)
|
self.add_macro(macro, ctx)
|
||||||
|
|
||||||
def build_namespace(
|
def build_namespace(self, macros: Iterable[Macro], ctx: Dict[str, Any]) -> MacroNamespace:
|
||||||
self, macros: Iterable[ParsedMacro], ctx: Dict[str, Any]
|
|
||||||
) -> MacroNamespace:
|
|
||||||
self.add_macros(macros, ctx)
|
self.add_macros(macros, ctx)
|
||||||
|
|
||||||
# Iterate in reverse-order and overwrite: the packages that are first
|
# Iterate in reverse-order and overwrite: the packages that are first
|
||||||
|
|||||||
@@ -19,45 +19,51 @@ from dbt.adapters.factory import get_adapter, get_adapter_package_names, get_ada
|
|||||||
from dbt.clients import agate_helper
|
from dbt.clients import agate_helper
|
||||||
from dbt.clients.jinja import get_rendered, MacroGenerator, MacroStack
|
from dbt.clients.jinja import get_rendered, MacroGenerator, MacroStack
|
||||||
from dbt.config import RuntimeConfig, Project
|
from dbt.config import RuntimeConfig, Project
|
||||||
from .base import contextmember, contextproperty, Var
|
|
||||||
from .configured import FQNLookup
|
|
||||||
from .context_config import ContextConfig
|
|
||||||
from dbt.constants import SECRET_ENV_PREFIX, DEFAULT_ENV_PLACEHOLDER
|
from dbt.constants import SECRET_ENV_PREFIX, DEFAULT_ENV_PLACEHOLDER
|
||||||
|
from dbt.context.base import contextmember, contextproperty, Var
|
||||||
|
from dbt.context.configured import FQNLookup
|
||||||
|
from dbt.context.context_config import ContextConfig
|
||||||
|
from dbt.context.exceptions_jinja import wrapped_exports
|
||||||
from dbt.context.macro_resolver import MacroResolver, TestMacroNamespace
|
from dbt.context.macro_resolver import MacroResolver, TestMacroNamespace
|
||||||
from .macros import MacroNamespaceBuilder, MacroNamespace
|
from dbt.context.macros import MacroNamespaceBuilder, MacroNamespace
|
||||||
from .manifest import ManifestContext
|
from dbt.context.manifest import ManifestContext
|
||||||
from dbt.contracts.connection import AdapterResponse
|
from dbt.contracts.connection import AdapterResponse
|
||||||
from dbt.contracts.graph.manifest import Manifest, Disabled
|
from dbt.contracts.graph.manifest import Manifest, Disabled
|
||||||
from dbt.contracts.graph.compiled import (
|
from dbt.contracts.graph.nodes import (
|
||||||
CompiledResource,
|
Macro,
|
||||||
CompiledSeedNode,
|
Exposure,
|
||||||
|
Metric,
|
||||||
|
Entity,
|
||||||
|
SeedNode,
|
||||||
|
SourceDefinition,
|
||||||
|
Resource,
|
||||||
ManifestNode,
|
ManifestNode,
|
||||||
)
|
)
|
||||||
from dbt.contracts.graph.parsed import (
|
|
||||||
ParsedMacro,
|
|
||||||
ParsedExposure,
|
|
||||||
ParsedMetric,
|
|
||||||
ParsedSeedNode,
|
|
||||||
ParsedSourceDefinition,
|
|
||||||
)
|
|
||||||
from dbt.contracts.graph.metrics import MetricReference, ResolvedMetricReference
|
from dbt.contracts.graph.metrics import MetricReference, ResolvedMetricReference
|
||||||
from dbt.events.functions import get_metadata_vars
|
from dbt.events.functions import get_metadata_vars
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
CompilationException,
|
CompilationError,
|
||||||
ParsingException,
|
ConflictingConfigKeysError,
|
||||||
InternalException,
|
SecretEnvVarLocationError,
|
||||||
ValidationException,
|
EnvVarMissingError,
|
||||||
RuntimeException,
|
DbtInternalError,
|
||||||
macro_invalid_dispatch_arg,
|
InlineModelConfigError,
|
||||||
missing_config,
|
NumberSourceArgsError,
|
||||||
raise_compiler_error,
|
PersistDocsValueTypeError,
|
||||||
ref_invalid_args,
|
LoadAgateTableNotSeedError,
|
||||||
metric_invalid_args,
|
LoadAgateTableValueError,
|
||||||
target_not_found,
|
MacroDispatchArgError,
|
||||||
ref_bad_context,
|
MacrosSourcesUnWriteableError,
|
||||||
wrapped_exports,
|
MetricArgsError,
|
||||||
raise_parsing_error,
|
MissingConfigError,
|
||||||
disallow_secret_env_var,
|
OperationsCannotRefEphemeralNodesError,
|
||||||
|
PackageNotInDepsError,
|
||||||
|
ParsingError,
|
||||||
|
RefBadContextError,
|
||||||
|
RefArgsError,
|
||||||
|
DbtRuntimeError,
|
||||||
|
TargetNotFoundError,
|
||||||
|
DbtValidationError,
|
||||||
)
|
)
|
||||||
from dbt.config import IsFQNResource
|
from dbt.config import IsFQNResource
|
||||||
from dbt.node_types import NodeType, ModelLanguage
|
from dbt.node_types import NodeType, ModelLanguage
|
||||||
@@ -139,10 +145,10 @@ class BaseDatabaseWrapper:
|
|||||||
f'`adapter.dispatch("{suggest_macro_name}", '
|
f'`adapter.dispatch("{suggest_macro_name}", '
|
||||||
f'macro_namespace="{suggest_macro_namespace}")`?'
|
f'macro_namespace="{suggest_macro_namespace}")`?'
|
||||||
)
|
)
|
||||||
raise CompilationException(msg)
|
raise CompilationError(msg)
|
||||||
|
|
||||||
if packages is not None:
|
if packages is not None:
|
||||||
raise macro_invalid_dispatch_arg(macro_name)
|
raise MacroDispatchArgError(macro_name)
|
||||||
|
|
||||||
namespace = macro_namespace
|
namespace = macro_namespace
|
||||||
|
|
||||||
@@ -154,7 +160,7 @@ class BaseDatabaseWrapper:
|
|||||||
search_packages = [self.config.project_name, namespace]
|
search_packages = [self.config.project_name, namespace]
|
||||||
else:
|
else:
|
||||||
# Not a string and not None so must be a list
|
# Not a string and not None so must be a list
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"In adapter.dispatch, got a list macro_namespace argument "
|
f"In adapter.dispatch, got a list macro_namespace argument "
|
||||||
f'("{macro_namespace}"), but macro_namespace should be None or a string.'
|
f'("{macro_namespace}"), but macro_namespace should be None or a string.'
|
||||||
)
|
)
|
||||||
@@ -167,8 +173,8 @@ class BaseDatabaseWrapper:
|
|||||||
try:
|
try:
|
||||||
# this uses the namespace from the context
|
# this uses the namespace from the context
|
||||||
macro = self._namespace.get_from_package(package_name, search_name)
|
macro = self._namespace.get_from_package(package_name, search_name)
|
||||||
except CompilationException:
|
except CompilationError:
|
||||||
# Only raise CompilationException if macro is not found in
|
# Only raise CompilationError if macro is not found in
|
||||||
# any package
|
# any package
|
||||||
macro = None
|
macro = None
|
||||||
|
|
||||||
@@ -182,7 +188,7 @@ class BaseDatabaseWrapper:
|
|||||||
|
|
||||||
searched = ", ".join(repr(a) for a in attempts)
|
searched = ", ".join(repr(a) for a in attempts)
|
||||||
msg = f"In dispatch: No macro named '{macro_name}' found\n Searched for: {searched}"
|
msg = f"In dispatch: No macro named '{macro_name}' found\n Searched for: {searched}"
|
||||||
raise CompilationException(msg)
|
raise CompilationError(msg)
|
||||||
|
|
||||||
|
|
||||||
class BaseResolver(metaclass=abc.ABCMeta):
|
class BaseResolver(metaclass=abc.ABCMeta):
|
||||||
@@ -218,12 +224,12 @@ class BaseRefResolver(BaseResolver):
|
|||||||
|
|
||||||
def validate_args(self, name: str, package: Optional[str]):
|
def validate_args(self, name: str, package: Optional[str]):
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"The name argument to ref() must be a string, got {type(name)}"
|
f"The name argument to ref() must be a string, got {type(name)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if package is not None and not isinstance(package, str):
|
if package is not None and not isinstance(package, str):
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"The package argument to ref() must be a string or None, got {type(package)}"
|
f"The package argument to ref() must be a string or None, got {type(package)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -236,7 +242,7 @@ class BaseRefResolver(BaseResolver):
|
|||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
package, name = args
|
package, name = args
|
||||||
else:
|
else:
|
||||||
ref_invalid_args(self.model, args)
|
raise RefArgsError(node=self.model, args=args)
|
||||||
self.validate_args(name, package)
|
self.validate_args(name, package)
|
||||||
return self.resolve(name, package)
|
return self.resolve(name, package)
|
||||||
|
|
||||||
@@ -248,21 +254,19 @@ class BaseSourceResolver(BaseResolver):
|
|||||||
|
|
||||||
def validate_args(self, source_name: str, table_name: str):
|
def validate_args(self, source_name: str, table_name: str):
|
||||||
if not isinstance(source_name, str):
|
if not isinstance(source_name, str):
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"The source name (first) argument to source() must be a "
|
f"The source name (first) argument to source() must be a "
|
||||||
f"string, got {type(source_name)}"
|
f"string, got {type(source_name)}"
|
||||||
)
|
)
|
||||||
if not isinstance(table_name, str):
|
if not isinstance(table_name, str):
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"The table name (second) argument to source() must be a "
|
f"The table name (second) argument to source() must be a "
|
||||||
f"string, got {type(table_name)}"
|
f"string, got {type(table_name)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self, *args: str) -> RelationProxy:
|
def __call__(self, *args: str) -> RelationProxy:
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
raise_compiler_error(
|
raise NumberSourceArgsError(args, node=self.model)
|
||||||
f"source() takes exactly two arguments ({len(args)} given)", self.model
|
|
||||||
)
|
|
||||||
self.validate_args(args[0], args[1])
|
self.validate_args(args[0], args[1])
|
||||||
return self.resolve(args[0], args[1])
|
return self.resolve(args[0], args[1])
|
||||||
|
|
||||||
@@ -279,12 +283,12 @@ class BaseMetricResolver(BaseResolver):
|
|||||||
|
|
||||||
def validate_args(self, name: str, package: Optional[str]):
|
def validate_args(self, name: str, package: Optional[str]):
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"The name argument to metric() must be a string, got {type(name)}"
|
f"The name argument to metric() must be a string, got {type(name)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if package is not None and not isinstance(package, str):
|
if package is not None and not isinstance(package, str):
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
f"The package argument to metric() must be a string or None, got {type(package)}"
|
f"The package argument to metric() must be a string or None, got {type(package)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -297,7 +301,7 @@ class BaseMetricResolver(BaseResolver):
|
|||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
package, name = args
|
package, name = args
|
||||||
else:
|
else:
|
||||||
metric_invalid_args(self.model, args)
|
raise MetricArgsError(node=self.model, args=args)
|
||||||
self.validate_args(name, package)
|
self.validate_args(name, package)
|
||||||
return self.resolve(name, package)
|
return self.resolve(name, package)
|
||||||
|
|
||||||
@@ -318,12 +322,7 @@ class ParseConfigObject(Config):
|
|||||||
if oldkey in config:
|
if oldkey in config:
|
||||||
newkey = oldkey.replace("_", "-")
|
newkey = oldkey.replace("_", "-")
|
||||||
if newkey in config:
|
if newkey in config:
|
||||||
raise_compiler_error(
|
raise ConflictingConfigKeysError(oldkey, newkey, node=self.model)
|
||||||
'Invalid config, has conflicting keys "{}" and "{}"'.format(
|
|
||||||
oldkey, newkey
|
|
||||||
),
|
|
||||||
self.model,
|
|
||||||
)
|
|
||||||
config[newkey] = config.pop(oldkey)
|
config[newkey] = config.pop(oldkey)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@@ -333,14 +332,14 @@ class ParseConfigObject(Config):
|
|||||||
elif len(args) == 0 and len(kwargs) > 0:
|
elif len(args) == 0 and len(kwargs) > 0:
|
||||||
opts = kwargs
|
opts = kwargs
|
||||||
else:
|
else:
|
||||||
raise_compiler_error("Invalid inline model config", self.model)
|
raise InlineModelConfigError(node=self.model)
|
||||||
|
|
||||||
opts = self._transform_config(opts)
|
opts = self._transform_config(opts)
|
||||||
|
|
||||||
# it's ok to have a parse context with no context config, but you must
|
# it's ok to have a parse context with no context config, but you must
|
||||||
# not call it!
|
# not call it!
|
||||||
if self.context_config is None:
|
if self.context_config is None:
|
||||||
raise RuntimeException("At parse time, did not receive a context config")
|
raise DbtRuntimeError("At parse time, did not receive a context config")
|
||||||
self.context_config.add_config_call(opts)
|
self.context_config.add_config_call(opts)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -381,7 +380,7 @@ class RuntimeConfigObject(Config):
|
|||||||
else:
|
else:
|
||||||
result = self.model.config.get(name, default)
|
result = self.model.config.get(name, default)
|
||||||
if result is _MISSING:
|
if result is _MISSING:
|
||||||
missing_config(self.model, name)
|
raise MissingConfigError(unique_id=self.model.unique_id, name=name)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def require(self, name, validator=None):
|
def require(self, name, validator=None):
|
||||||
@@ -403,20 +402,14 @@ class RuntimeConfigObject(Config):
|
|||||||
def persist_relation_docs(self) -> bool:
|
def persist_relation_docs(self) -> bool:
|
||||||
persist_docs = self.get("persist_docs", default={})
|
persist_docs = self.get("persist_docs", default={})
|
||||||
if not isinstance(persist_docs, dict):
|
if not isinstance(persist_docs, dict):
|
||||||
raise_compiler_error(
|
raise PersistDocsValueTypeError(persist_docs)
|
||||||
f"Invalid value provided for 'persist_docs'. Expected dict "
|
|
||||||
f"but received {type(persist_docs)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return persist_docs.get("relation", False)
|
return persist_docs.get("relation", False)
|
||||||
|
|
||||||
def persist_column_docs(self) -> bool:
|
def persist_column_docs(self) -> bool:
|
||||||
persist_docs = self.get("persist_docs", default={})
|
persist_docs = self.get("persist_docs", default={})
|
||||||
if not isinstance(persist_docs, dict):
|
if not isinstance(persist_docs, dict):
|
||||||
raise_compiler_error(
|
raise PersistDocsValueTypeError(persist_docs)
|
||||||
f"Invalid value provided for 'persist_docs'. Expected dict "
|
|
||||||
f"but received {type(persist_docs)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return persist_docs.get("columns", False)
|
return persist_docs.get("columns", False)
|
||||||
|
|
||||||
@@ -475,7 +468,7 @@ class RuntimeRefResolver(BaseRefResolver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if target_model is None or isinstance(target_model, Disabled):
|
if target_model is None or isinstance(target_model, Disabled):
|
||||||
target_not_found(
|
raise TargetNotFoundError(
|
||||||
node=self.model,
|
node=self.model,
|
||||||
target_name=target_name,
|
target_name=target_name,
|
||||||
target_kind="node",
|
target_kind="node",
|
||||||
@@ -497,7 +490,7 @@ class RuntimeRefResolver(BaseRefResolver):
|
|||||||
) -> None:
|
) -> None:
|
||||||
if resolved.unique_id not in self.model.depends_on.nodes:
|
if resolved.unique_id not in self.model.depends_on.nodes:
|
||||||
args = self._repack_args(target_name, target_package)
|
args = self._repack_args(target_name, target_package)
|
||||||
ref_bad_context(self.model, args)
|
raise RefBadContextError(node=self.model, args=args)
|
||||||
|
|
||||||
|
|
||||||
class OperationRefResolver(RuntimeRefResolver):
|
class OperationRefResolver(RuntimeRefResolver):
|
||||||
@@ -512,13 +505,8 @@ class OperationRefResolver(RuntimeRefResolver):
|
|||||||
def create_relation(self, target_model: ManifestNode, name: str) -> RelationProxy:
|
def create_relation(self, target_model: ManifestNode, name: str) -> RelationProxy:
|
||||||
if target_model.is_ephemeral_model:
|
if target_model.is_ephemeral_model:
|
||||||
# In operations, we can't ref() ephemeral nodes, because
|
# In operations, we can't ref() ephemeral nodes, because
|
||||||
# ParsedMacros do not support set_cte
|
# Macros do not support set_cte
|
||||||
raise_compiler_error(
|
raise OperationsCannotRefEphemeralNodesError(target_model.name, node=self.model)
|
||||||
"Operations can not ref() ephemeral nodes, but {} is ephemeral".format(
|
|
||||||
target_model.name
|
|
||||||
),
|
|
||||||
self.model,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return super().create_relation(target_model, name)
|
return super().create_relation(target_model, name)
|
||||||
|
|
||||||
@@ -541,7 +529,7 @@ class RuntimeSourceResolver(BaseSourceResolver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if target_source is None or isinstance(target_source, Disabled):
|
if target_source is None or isinstance(target_source, Disabled):
|
||||||
target_not_found(
|
raise TargetNotFoundError(
|
||||||
node=self.model,
|
node=self.model,
|
||||||
target_name=f"{source_name}.{table_name}",
|
target_name=f"{source_name}.{table_name}",
|
||||||
target_kind="source",
|
target_kind="source",
|
||||||
@@ -568,7 +556,7 @@ class RuntimeMetricResolver(BaseMetricResolver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if target_metric is None or isinstance(target_metric, Disabled):
|
if target_metric is None or isinstance(target_metric, Disabled):
|
||||||
target_not_found(
|
raise TargetNotFoundError(
|
||||||
node=self.model,
|
node=self.model,
|
||||||
target_name=target_name,
|
target_name=target_name,
|
||||||
target_kind="metric",
|
target_kind="metric",
|
||||||
@@ -584,9 +572,9 @@ class ModelConfiguredVar(Var):
|
|||||||
self,
|
self,
|
||||||
context: Dict[str, Any],
|
context: Dict[str, Any],
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
node: CompiledResource,
|
node: Resource,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._node: CompiledResource
|
self._node: Resource
|
||||||
self._config: RuntimeConfig = config
|
self._config: RuntimeConfig = config
|
||||||
super().__init__(context, config.cli_vars, node=node)
|
super().__init__(context, config.cli_vars, node=node)
|
||||||
|
|
||||||
@@ -597,7 +585,7 @@ class ModelConfiguredVar(Var):
|
|||||||
if package_name != self._config.project_name:
|
if package_name != self._config.project_name:
|
||||||
if package_name not in dependencies:
|
if package_name not in dependencies:
|
||||||
# I don't think this is actually reachable
|
# I don't think this is actually reachable
|
||||||
raise_compiler_error(f"Node package named {package_name} not found!", self._node)
|
raise PackageNotInDepsError(package_name, node=self._node)
|
||||||
yield dependencies[package_name]
|
yield dependencies[package_name]
|
||||||
yield self._config
|
yield self._config
|
||||||
|
|
||||||
@@ -687,10 +675,10 @@ class ProviderContext(ManifestContext):
|
|||||||
context_config: Optional[ContextConfig],
|
context_config: Optional[ContextConfig],
|
||||||
) -> None:
|
) -> None:
|
||||||
if provider is None:
|
if provider is None:
|
||||||
raise InternalException(f"Invalid provider given to context: {provider}")
|
raise DbtInternalError(f"Invalid provider given to context: {provider}")
|
||||||
# mypy appeasement - we know it'll be a RuntimeConfig
|
# mypy appeasement - we know it'll be a RuntimeConfig
|
||||||
self.config: RuntimeConfig
|
self.config: RuntimeConfig
|
||||||
self.model: Union[ParsedMacro, ManifestNode] = model
|
self.model: Union[Macro, ManifestNode] = model
|
||||||
super().__init__(config, manifest, model.package_name)
|
super().__init__(config, manifest, model.package_name)
|
||||||
self.sql_results: Dict[str, AttrDict] = {}
|
self.sql_results: Dict[str, AttrDict] = {}
|
||||||
self.context_config: Optional[ContextConfig] = context_config
|
self.context_config: Optional[ContextConfig] = context_config
|
||||||
@@ -764,7 +752,7 @@ class ProviderContext(ManifestContext):
|
|||||||
return
|
return
|
||||||
elif value == arg:
|
elif value == arg:
|
||||||
return
|
return
|
||||||
raise ValidationException(
|
raise DbtValidationError(
|
||||||
'Expected value "{}" to be one of {}'.format(value, ",".join(map(str, args)))
|
'Expected value "{}" to be one of {}'.format(value, ",".join(map(str, args)))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -779,8 +767,8 @@ class ProviderContext(ManifestContext):
|
|||||||
@contextmember
|
@contextmember
|
||||||
def write(self, payload: str) -> str:
|
def write(self, payload: str) -> str:
|
||||||
# macros/source defs aren't 'writeable'.
|
# macros/source defs aren't 'writeable'.
|
||||||
if isinstance(self.model, (ParsedMacro, ParsedSourceDefinition)):
|
if isinstance(self.model, (Macro, SourceDefinition)):
|
||||||
raise_compiler_error('cannot "write" macros or sources')
|
raise MacrosSourcesUnWriteableError(node=self.model)
|
||||||
self.model.build_path = self.model.write_node(self.config.target_path, "run", payload)
|
self.model.build_path = self.model.write_node(self.config.target_path, "run", payload)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -795,21 +783,19 @@ class ProviderContext(ManifestContext):
|
|||||||
try:
|
try:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise_compiler_error(message_if_exception, self.model)
|
raise CompilationError(message_if_exception, self.model)
|
||||||
|
|
||||||
@contextmember
|
@contextmember
|
||||||
def load_agate_table(self) -> agate.Table:
|
def load_agate_table(self) -> agate.Table:
|
||||||
if not isinstance(self.model, (ParsedSeedNode, CompiledSeedNode)):
|
if not isinstance(self.model, SeedNode):
|
||||||
raise_compiler_error(
|
raise LoadAgateTableNotSeedError(self.model.resource_type, node=self.model)
|
||||||
"can only load_agate_table for seeds (got a {})".format(self.model.resource_type)
|
|
||||||
)
|
|
||||||
assert self.model.root_path
|
assert self.model.root_path
|
||||||
path = os.path.join(self.model.root_path, self.model.original_file_path)
|
path = os.path.join(self.model.root_path, self.model.original_file_path)
|
||||||
column_types = self.model.config.column_types
|
column_types = self.model.config.column_types
|
||||||
try:
|
try:
|
||||||
table = agate_helper.from_csv(path, text_columns=column_types)
|
table = agate_helper.from_csv(path, text_columns=column_types)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise_compiler_error(str(e))
|
raise LoadAgateTableValueError(e, node=self.model)
|
||||||
table.original_abspath = os.path.abspath(path)
|
table.original_abspath = os.path.abspath(path)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
@@ -1200,7 +1186,7 @@ class ProviderContext(ManifestContext):
|
|||||||
"https://docs.getdbt.com/reference/dbt-jinja-functions/dispatch)"
|
"https://docs.getdbt.com/reference/dbt-jinja-functions/dispatch)"
|
||||||
" adapter_macro was called for: {macro_name}".format(macro_name=name)
|
" adapter_macro was called for: {macro_name}".format(macro_name=name)
|
||||||
)
|
)
|
||||||
raise CompilationException(msg)
|
raise CompilationError(msg)
|
||||||
|
|
||||||
@contextmember
|
@contextmember
|
||||||
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
||||||
@@ -1211,7 +1197,7 @@ class ProviderContext(ManifestContext):
|
|||||||
"""
|
"""
|
||||||
return_value = None
|
return_value = None
|
||||||
if var.startswith(SECRET_ENV_PREFIX):
|
if var.startswith(SECRET_ENV_PREFIX):
|
||||||
disallow_secret_env_var(var)
|
raise SecretEnvVarLocationError(var)
|
||||||
if var in os.environ:
|
if var in os.environ:
|
||||||
return_value = os.environ[var]
|
return_value = os.environ[var]
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
@@ -1220,7 +1206,13 @@ class ProviderContext(ManifestContext):
|
|||||||
if return_value is not None:
|
if return_value is not None:
|
||||||
# Save the env_var value in the manifest and the var name in the source_file.
|
# Save the env_var value in the manifest and the var name in the source_file.
|
||||||
# If this is compiling, do not save because it's irrelevant to parsing.
|
# If this is compiling, do not save because it's irrelevant to parsing.
|
||||||
if self.model and not hasattr(self.model, "compiled"):
|
compiling = (
|
||||||
|
True
|
||||||
|
if hasattr(self.model, "compiled")
|
||||||
|
and getattr(self.model, "compiled", False) is True
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
if self.model and not compiling:
|
||||||
# If the environment variable is set from a default, store a string indicating
|
# If the environment variable is set from a default, store a string indicating
|
||||||
# that so we can skip partial parsing. Otherwise the file will be scheduled for
|
# that so we can skip partial parsing. Otherwise the file will be scheduled for
|
||||||
# reparsing. If the default changes, the file will have been updated and therefore
|
# reparsing. If the default changes, the file will have been updated and therefore
|
||||||
@@ -1238,8 +1230,7 @@ class ProviderContext(ManifestContext):
|
|||||||
source_file.env_vars.append(var) # type: ignore[union-attr]
|
source_file.env_vars.append(var) # type: ignore[union-attr]
|
||||||
return return_value
|
return return_value
|
||||||
else:
|
else:
|
||||||
msg = f"Env var required but not provided: '{var}'"
|
raise EnvVarMissingError(var)
|
||||||
raise_parsing_error(msg)
|
|
||||||
|
|
||||||
@contextproperty
|
@contextproperty
|
||||||
def selected_resources(self) -> List[str]:
|
def selected_resources(self) -> List[str]:
|
||||||
@@ -1258,7 +1249,7 @@ class ProviderContext(ManifestContext):
|
|||||||
and self.context_macro_stack.call_stack[1] == "macro.dbt.statement"
|
and self.context_macro_stack.call_stack[1] == "macro.dbt.statement"
|
||||||
and "materialization" in self.context_macro_stack.call_stack[0]
|
and "materialization" in self.context_macro_stack.call_stack[0]
|
||||||
):
|
):
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"submit_python_job is not intended to be called here, at model {parsed_model['alias']}, with macro call_stack {self.context_macro_stack.call_stack}."
|
f"submit_python_job is not intended to be called here, at model {parsed_model['alias']}, with macro call_stack {self.context_macro_stack.call_stack}."
|
||||||
)
|
)
|
||||||
return self.adapter.submit_python_job(parsed_model, compiled_code)
|
return self.adapter.submit_python_job(parsed_model, compiled_code)
|
||||||
@@ -1275,7 +1266,7 @@ class MacroContext(ProviderContext):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
model: ParsedMacro,
|
model: Macro,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
@@ -1390,7 +1381,7 @@ def generate_parser_model_context(
|
|||||||
|
|
||||||
|
|
||||||
def generate_generate_name_macro_context(
|
def generate_generate_name_macro_context(
|
||||||
macro: ParsedMacro,
|
macro: Macro,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
@@ -1408,7 +1399,7 @@ def generate_runtime_model_context(
|
|||||||
|
|
||||||
|
|
||||||
def generate_runtime_macro_context(
|
def generate_runtime_macro_context(
|
||||||
macro: ParsedMacro,
|
macro: Macro,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
package_name: Optional[str],
|
package_name: Optional[str],
|
||||||
@@ -1420,7 +1411,7 @@ def generate_runtime_macro_context(
|
|||||||
class ExposureRefResolver(BaseResolver):
|
class ExposureRefResolver(BaseResolver):
|
||||||
def __call__(self, *args) -> str:
|
def __call__(self, *args) -> str:
|
||||||
if len(args) not in (1, 2):
|
if len(args) not in (1, 2):
|
||||||
ref_invalid_args(self.model, args)
|
raise RefArgsError(node=self.model, args=args)
|
||||||
self.model.refs.append(list(args))
|
self.model.refs.append(list(args))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -1428,15 +1419,21 @@ class ExposureRefResolver(BaseResolver):
|
|||||||
class ExposureSourceResolver(BaseResolver):
|
class ExposureSourceResolver(BaseResolver):
|
||||||
def __call__(self, *args) -> str:
|
def __call__(self, *args) -> str:
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
raise_compiler_error(
|
raise NumberSourceArgsError(args, node=self.model)
|
||||||
f"source() takes exactly two arguments ({len(args)} given)", self.model
|
|
||||||
)
|
|
||||||
self.model.sources.append(list(args))
|
self.model.sources.append(list(args))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class ExposureMetricResolver(BaseResolver):
|
||||||
|
def __call__(self, *args) -> str:
|
||||||
|
if len(args) not in (1, 2):
|
||||||
|
raise MetricArgsError(node=self.model, args=args)
|
||||||
|
self.model.metrics.append(list(args))
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def generate_parse_exposure(
|
def generate_parse_exposure(
|
||||||
exposure: ParsedExposure,
|
exposure: Exposure,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
package_name: str,
|
package_name: str,
|
||||||
@@ -1455,6 +1452,12 @@ def generate_parse_exposure(
|
|||||||
project,
|
project,
|
||||||
manifest,
|
manifest,
|
||||||
),
|
),
|
||||||
|
"metric": ExposureMetricResolver(
|
||||||
|
None,
|
||||||
|
exposure,
|
||||||
|
project,
|
||||||
|
manifest,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1466,21 +1469,21 @@ class MetricRefResolver(BaseResolver):
|
|||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
package, name = args
|
package, name = args
|
||||||
else:
|
else:
|
||||||
ref_invalid_args(self.model, args)
|
raise RefArgsError(node=self.model, args=args)
|
||||||
self.validate_args(name, package)
|
self.validate_args(name, package)
|
||||||
self.model.refs.append(list(args))
|
self.model.refs.append(list(args))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def validate_args(self, name, package):
|
def validate_args(self, name, package):
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise ParsingException(
|
raise ParsingError(
|
||||||
f"In a metrics section in {self.model.original_file_path} "
|
f"In a metrics section in {self.model.original_file_path} "
|
||||||
"the name argument to ref() must be a string"
|
"the name argument to ref() must be a string"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_parse_metrics(
|
def generate_parse_metrics(
|
||||||
metric: ParsedMetric,
|
metric: Metric,
|
||||||
config: RuntimeConfig,
|
config: RuntimeConfig,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
package_name: str,
|
package_name: str,
|
||||||
@@ -1502,6 +1505,44 @@ def generate_parse_metrics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EntityRefResolver(BaseResolver):
|
||||||
|
def __call__(self, *args) -> str:
|
||||||
|
package = None
|
||||||
|
if len(args) == 1:
|
||||||
|
name = args[0]
|
||||||
|
elif len(args) == 2:
|
||||||
|
package, name = args
|
||||||
|
else:
|
||||||
|
raise RefArgsError(node=self.model, args=args)
|
||||||
|
self.validate_args(name, package)
|
||||||
|
self.model.refs.append(list(args))
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def validate_args(self, name, package):
|
||||||
|
if not isinstance(name, str):
|
||||||
|
raise ParsingError(
|
||||||
|
f"In the entity associated with {self.model.original_file_path} "
|
||||||
|
"the name argument to ref() must be a string"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_parse_entities(
|
||||||
|
entity: Entity,
|
||||||
|
config: RuntimeConfig,
|
||||||
|
manifest: Manifest,
|
||||||
|
package_name: str,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
project = config.load_dependencies()[package_name]
|
||||||
|
return {
|
||||||
|
"ref": EntityRefResolver(
|
||||||
|
None,
|
||||||
|
entity,
|
||||||
|
project,
|
||||||
|
manifest,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# This class is currently used by the schema parser in order
|
# This class is currently used by the schema parser in order
|
||||||
# to limit the number of macros in the context by using
|
# to limit the number of macros in the context by using
|
||||||
# the TestMacroNamespace
|
# the TestMacroNamespace
|
||||||
@@ -1556,7 +1597,7 @@ class TestContext(ProviderContext):
|
|||||||
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
||||||
return_value = None
|
return_value = None
|
||||||
if var.startswith(SECRET_ENV_PREFIX):
|
if var.startswith(SECRET_ENV_PREFIX):
|
||||||
disallow_secret_env_var(var)
|
raise SecretEnvVarLocationError(var)
|
||||||
if var in os.environ:
|
if var in os.environ:
|
||||||
return_value = os.environ[var]
|
return_value = os.environ[var]
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
@@ -1582,8 +1623,7 @@ class TestContext(ProviderContext):
|
|||||||
source_file.add_env_var(var, yaml_key, name) # type: ignore[union-attr]
|
source_file.add_env_var(var, yaml_key, name) # type: ignore[union-attr]
|
||||||
return return_value
|
return return_value
|
||||||
else:
|
else:
|
||||||
msg = f"Env var required but not provided: '{var}'"
|
raise EnvVarMissingError(var)
|
||||||
raise_parsing_error(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_test_context(
|
def generate_test_context(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
|
|||||||
from .base import BaseContext, contextmember
|
from .base import BaseContext, contextmember
|
||||||
|
|
||||||
from dbt.constants import SECRET_ENV_PREFIX, DEFAULT_ENV_PLACEHOLDER
|
from dbt.constants import SECRET_ENV_PREFIX, DEFAULT_ENV_PLACEHOLDER
|
||||||
from dbt.exceptions import raise_parsing_error
|
from dbt.exceptions import EnvVarMissingError
|
||||||
|
|
||||||
|
|
||||||
SECRET_PLACEHOLDER = "$$$DBT_SECRET_START$$${}$$$DBT_SECRET_END$$$"
|
SECRET_PLACEHOLDER = "$$$DBT_SECRET_START$$${}$$$DBT_SECRET_END$$$"
|
||||||
@@ -50,8 +50,7 @@ class SecretContext(BaseContext):
|
|||||||
self.env_vars[var] = return_value if var in os.environ else DEFAULT_ENV_PLACEHOLDER
|
self.env_vars[var] = return_value if var in os.environ else DEFAULT_ENV_PLACEHOLDER
|
||||||
return return_value
|
return return_value
|
||||||
else:
|
else:
|
||||||
msg = f"Env var required but not provided: '{var}'"
|
raise EnvVarMissingError(var)
|
||||||
raise_parsing_error(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_secret_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]:
|
def generate_secret_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ from typing import (
|
|||||||
List,
|
List,
|
||||||
Callable,
|
Callable,
|
||||||
)
|
)
|
||||||
from dbt.exceptions import InternalException
|
from dbt.exceptions import DbtInternalError
|
||||||
from dbt.utils import translate_aliases
|
from dbt.utils import translate_aliases
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import NewConnectionOpening
|
from dbt.events.types import NewConnectionOpening
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from typing_extensions import Protocol
|
from typing_extensions import Protocol
|
||||||
from dbt.dataclass_schema import (
|
from dbt.dataclass_schema import (
|
||||||
dbtClassMixin,
|
dbtClassMixin,
|
||||||
@@ -93,7 +94,7 @@ class Connection(ExtensibleDbtClassMixin, Replaceable):
|
|||||||
# this will actually change 'self._handle'.
|
# this will actually change 'self._handle'.
|
||||||
self._handle.resolve(self)
|
self._handle.resolve(self)
|
||||||
except RecursionError as exc:
|
except RecursionError as exc:
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
"A connection's open() method attempted to read the handle value"
|
"A connection's open() method attempted to read the handle value"
|
||||||
) from exc
|
) from exc
|
||||||
return self._handle
|
return self._handle
|
||||||
@@ -112,7 +113,9 @@ class LazyHandle:
|
|||||||
self.opener = opener
|
self.opener = opener
|
||||||
|
|
||||||
def resolve(self, connection: Connection) -> Connection:
|
def resolve(self, connection: Connection) -> Connection:
|
||||||
fire_event(NewConnectionOpening(connection_state=connection.state))
|
fire_event(
|
||||||
|
NewConnectionOpening(connection_state=connection.state, node_info=get_node_info())
|
||||||
|
)
|
||||||
return self.opener(connection)
|
return self.opener(connection)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ class SchemaSourceFile(BaseSourceFile):
|
|||||||
sources: List[str] = field(default_factory=list)
|
sources: List[str] = field(default_factory=list)
|
||||||
exposures: List[str] = field(default_factory=list)
|
exposures: List[str] = field(default_factory=list)
|
||||||
metrics: List[str] = field(default_factory=list)
|
metrics: List[str] = field(default_factory=list)
|
||||||
|
entities: List[str] = field(default_factory=list)
|
||||||
# node patches contain models, seeds, snapshots, analyses
|
# node patches contain models, seeds, snapshots, analyses
|
||||||
ndp: List[str] = field(default_factory=list)
|
ndp: List[str] = field(default_factory=list)
|
||||||
# any macro patches in this file by macro unique_id.
|
# any macro patches in this file by macro unique_id.
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
from dbt.contracts.graph.parsed import (
|
|
||||||
HasTestMetadata,
|
|
||||||
ParsedNode,
|
|
||||||
ParsedAnalysisNode,
|
|
||||||
ParsedSingularTestNode,
|
|
||||||
ParsedHookNode,
|
|
||||||
ParsedModelNode,
|
|
||||||
ParsedExposure,
|
|
||||||
ParsedMetric,
|
|
||||||
ParsedResource,
|
|
||||||
ParsedRPCNode,
|
|
||||||
ParsedSqlNode,
|
|
||||||
ParsedGenericTestNode,
|
|
||||||
ParsedSeedNode,
|
|
||||||
ParsedSnapshotNode,
|
|
||||||
ParsedSourceDefinition,
|
|
||||||
SeedConfig,
|
|
||||||
TestConfig,
|
|
||||||
same_seeds,
|
|
||||||
)
|
|
||||||
from dbt.node_types import NodeType
|
|
||||||
from dbt.contracts.util import Replaceable
|
|
||||||
|
|
||||||
from dbt.dataclass_schema import dbtClassMixin
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Optional, List, Union, Dict, Type
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class InjectedCTE(dbtClassMixin, Replaceable):
|
|
||||||
id: str
|
|
||||||
sql: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledNodeMixin(dbtClassMixin):
|
|
||||||
# this is a special mixin class to provide a required argument. If a node
|
|
||||||
# is missing a `compiled` flag entirely, it must not be a CompiledNode.
|
|
||||||
compiled: bool
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledNode(ParsedNode, CompiledNodeMixin):
|
|
||||||
compiled_code: Optional[str] = None
|
|
||||||
extra_ctes_injected: bool = False
|
|
||||||
extra_ctes: List[InjectedCTE] = field(default_factory=list)
|
|
||||||
relation_name: Optional[str] = None
|
|
||||||
_pre_injected_sql: Optional[str] = None
|
|
||||||
|
|
||||||
def set_cte(self, cte_id: str, sql: str):
|
|
||||||
"""This is the equivalent of what self.extra_ctes[cte_id] = sql would
|
|
||||||
do if extra_ctes were an OrderedDict
|
|
||||||
"""
|
|
||||||
for cte in self.extra_ctes:
|
|
||||||
if cte.id == cte_id:
|
|
||||||
cte.sql = sql
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.extra_ctes.append(InjectedCTE(id=cte_id, sql=sql))
|
|
||||||
|
|
||||||
def __post_serialize__(self, dct):
|
|
||||||
dct = super().__post_serialize__(dct)
|
|
||||||
if "_pre_injected_sql" in dct:
|
|
||||||
del dct["_pre_injected_sql"]
|
|
||||||
return dct
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledAnalysisNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Analysis]})
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledHookNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Operation]})
|
|
||||||
index: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledModelNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Model]})
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: rm?
|
|
||||||
@dataclass
|
|
||||||
class CompiledRPCNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.RPCCall]})
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledSqlNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.SqlOperation]})
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledSeedNode(CompiledNode):
|
|
||||||
# keep this in sync with ParsedSeedNode!
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Seed]})
|
|
||||||
config: SeedConfig = field(default_factory=SeedConfig)
|
|
||||||
root_path: Optional[str] = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def empty(self):
|
|
||||||
"""Seeds are never empty"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def same_body(self, other) -> bool:
|
|
||||||
return same_seeds(self, other)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledSnapshotNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Snapshot]})
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledSingularTestNode(CompiledNode):
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Test]})
|
|
||||||
# Was not able to make mypy happy and keep the code working. We need to
|
|
||||||
# refactor the various configs.
|
|
||||||
config: TestConfig = field(default_factory=TestConfig) # type:ignore
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CompiledGenericTestNode(CompiledNode, HasTestMetadata):
|
|
||||||
# keep this in sync with ParsedGenericTestNode!
|
|
||||||
resource_type: NodeType = field(metadata={"restrict": [NodeType.Test]})
|
|
||||||
column_name: Optional[str] = None
|
|
||||||
file_key_name: Optional[str] = None
|
|
||||||
# Was not able to make mypy happy and keep the code working. We need to
|
|
||||||
# refactor the various configs.
|
|
||||||
config: TestConfig = field(default_factory=TestConfig) # type:ignore
|
|
||||||
|
|
||||||
def same_contents(self, other) -> bool:
|
|
||||||
if other is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self.same_config(other) and self.same_fqn(other) and True
|
|
||||||
|
|
||||||
|
|
||||||
CompiledTestNode = Union[CompiledSingularTestNode, CompiledGenericTestNode]
|
|
||||||
|
|
||||||
|
|
||||||
PARSED_TYPES: Dict[Type[CompiledNode], Type[ParsedResource]] = {
|
|
||||||
CompiledAnalysisNode: ParsedAnalysisNode,
|
|
||||||
CompiledModelNode: ParsedModelNode,
|
|
||||||
CompiledHookNode: ParsedHookNode,
|
|
||||||
CompiledRPCNode: ParsedRPCNode,
|
|
||||||
CompiledSqlNode: ParsedSqlNode,
|
|
||||||
CompiledSeedNode: ParsedSeedNode,
|
|
||||||
CompiledSnapshotNode: ParsedSnapshotNode,
|
|
||||||
CompiledSingularTestNode: ParsedSingularTestNode,
|
|
||||||
CompiledGenericTestNode: ParsedGenericTestNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
COMPILED_TYPES: Dict[Type[ParsedResource], Type[CompiledNode]] = {
|
|
||||||
ParsedAnalysisNode: CompiledAnalysisNode,
|
|
||||||
ParsedModelNode: CompiledModelNode,
|
|
||||||
ParsedHookNode: CompiledHookNode,
|
|
||||||
ParsedRPCNode: CompiledRPCNode,
|
|
||||||
ParsedSqlNode: CompiledSqlNode,
|
|
||||||
ParsedSeedNode: CompiledSeedNode,
|
|
||||||
ParsedSnapshotNode: CompiledSnapshotNode,
|
|
||||||
ParsedSingularTestNode: CompiledSingularTestNode,
|
|
||||||
ParsedGenericTestNode: CompiledGenericTestNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# for some types, the compiled type is the parsed type, so make this easy
|
|
||||||
CompiledType = Union[Type[CompiledNode], Type[ParsedResource]]
|
|
||||||
CompiledResource = Union[ParsedResource, CompiledNode]
|
|
||||||
|
|
||||||
|
|
||||||
def compiled_type_for(parsed: ParsedNode) -> CompiledType:
|
|
||||||
if type(parsed) in COMPILED_TYPES:
|
|
||||||
return COMPILED_TYPES[type(parsed)]
|
|
||||||
else:
|
|
||||||
return type(parsed)
|
|
||||||
|
|
||||||
|
|
||||||
def parsed_instance_for(compiled: CompiledNode) -> ParsedResource:
|
|
||||||
cls = PARSED_TYPES.get(type(compiled))
|
|
||||||
if cls is None:
|
|
||||||
# how???
|
|
||||||
raise ValueError("invalid resource_type: {}".format(compiled.resource_type))
|
|
||||||
|
|
||||||
return cls.from_dict(compiled.to_dict(omit_none=True))
|
|
||||||
|
|
||||||
|
|
||||||
NonSourceCompiledNode = Union[
|
|
||||||
CompiledAnalysisNode,
|
|
||||||
CompiledSingularTestNode,
|
|
||||||
CompiledModelNode,
|
|
||||||
CompiledHookNode,
|
|
||||||
CompiledRPCNode,
|
|
||||||
CompiledSqlNode,
|
|
||||||
CompiledGenericTestNode,
|
|
||||||
CompiledSeedNode,
|
|
||||||
CompiledSnapshotNode,
|
|
||||||
]
|
|
||||||
|
|
||||||
NonSourceParsedNode = Union[
|
|
||||||
ParsedAnalysisNode,
|
|
||||||
ParsedSingularTestNode,
|
|
||||||
ParsedHookNode,
|
|
||||||
ParsedModelNode,
|
|
||||||
ParsedRPCNode,
|
|
||||||
ParsedSqlNode,
|
|
||||||
ParsedGenericTestNode,
|
|
||||||
ParsedSeedNode,
|
|
||||||
ParsedSnapshotNode,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# This is anything that can be in manifest.nodes.
|
|
||||||
ManifestNode = Union[
|
|
||||||
NonSourceCompiledNode,
|
|
||||||
NonSourceParsedNode,
|
|
||||||
]
|
|
||||||
|
|
||||||
# We allow either parsed or compiled nodes, or parsed sources, as some
|
|
||||||
# 'compile()' calls in the runner actually just return the original parsed
|
|
||||||
# node they were given.
|
|
||||||
CompileResultNode = Union[
|
|
||||||
ManifestNode,
|
|
||||||
ParsedSourceDefinition,
|
|
||||||
]
|
|
||||||
|
|
||||||
# anything that participates in the graph: sources, exposures, metrics,
|
|
||||||
# or manifest nodes
|
|
||||||
GraphMemberNode = Union[
|
|
||||||
CompileResultNode,
|
|
||||||
ParsedExposure,
|
|
||||||
ParsedMetric,
|
|
||||||
]
|
|
||||||
@@ -16,44 +16,40 @@ from typing import (
|
|||||||
TypeVar,
|
TypeVar,
|
||||||
Callable,
|
Callable,
|
||||||
Generic,
|
Generic,
|
||||||
cast,
|
|
||||||
AbstractSet,
|
AbstractSet,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
)
|
)
|
||||||
from typing_extensions import Protocol
|
from typing_extensions import Protocol
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from dbt.contracts.graph.compiled import (
|
from dbt.contracts.graph.nodes import (
|
||||||
CompileResultNode,
|
Macro,
|
||||||
ManifestNode,
|
Documentation,
|
||||||
NonSourceCompiledNode,
|
SourceDefinition,
|
||||||
GraphMemberNode,
|
GenericTestNode,
|
||||||
)
|
Exposure,
|
||||||
from dbt.contracts.graph.parsed import (
|
Metric,
|
||||||
ParsedMacro,
|
Entity,
|
||||||
ParsedDocumentation,
|
|
||||||
ParsedSourceDefinition,
|
|
||||||
ParsedGenericTestNode,
|
|
||||||
ParsedExposure,
|
|
||||||
ParsedMetric,
|
|
||||||
HasUniqueID,
|
|
||||||
UnpatchedSourceDefinition,
|
UnpatchedSourceDefinition,
|
||||||
ManifestNodes,
|
ManifestNode,
|
||||||
|
GraphMemberNode,
|
||||||
|
ResultNode,
|
||||||
|
BaseNode,
|
||||||
)
|
)
|
||||||
from dbt.contracts.graph.unparsed import SourcePatch
|
from dbt.contracts.graph.unparsed import SourcePatch
|
||||||
from dbt.contracts.files import SourceFile, SchemaSourceFile, FileHash, AnySourceFile
|
from dbt.contracts.files import SourceFile, SchemaSourceFile, FileHash, AnySourceFile
|
||||||
from dbt.contracts.util import BaseArtifactMetadata, SourceKey, ArtifactMixin, schema_version
|
from dbt.contracts.util import BaseArtifactMetadata, SourceKey, ArtifactMixin, schema_version
|
||||||
from dbt.dataclass_schema import dbtClassMixin
|
from dbt.dataclass_schema import dbtClassMixin
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
CompilationException,
|
CompilationError,
|
||||||
raise_duplicate_resource_name,
|
DuplicateResourceNameError,
|
||||||
raise_compiler_error,
|
DuplicateMacroInPackageError,
|
||||||
|
DuplicateMaterializationNameError,
|
||||||
)
|
)
|
||||||
from dbt.helper_types import PathSet
|
from dbt.helper_types import PathSet
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import MergedFromState
|
from dbt.events.types import MergedFromState
|
||||||
from dbt.node_types import NodeType
|
from dbt.node_types import NodeType
|
||||||
from dbt.ui import line_wrap_message
|
|
||||||
from dbt import flags
|
from dbt import flags
|
||||||
from dbt import tracking
|
from dbt import tracking
|
||||||
import dbt.utils
|
import dbt.utils
|
||||||
@@ -96,7 +92,7 @@ class DocLookup(dbtClassMixin):
|
|||||||
return self.perform_lookup(unique_id, manifest)
|
return self.perform_lookup(unique_id, manifest)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_doc(self, doc: ParsedDocumentation):
|
def add_doc(self, doc: Documentation):
|
||||||
if doc.name not in self.storage:
|
if doc.name not in self.storage:
|
||||||
self.storage[doc.name] = {}
|
self.storage[doc.name] = {}
|
||||||
self.storage[doc.name][doc.package_name] = doc.unique_id
|
self.storage[doc.name][doc.package_name] = doc.unique_id
|
||||||
@@ -105,9 +101,9 @@ class DocLookup(dbtClassMixin):
|
|||||||
for doc in manifest.docs.values():
|
for doc in manifest.docs.values():
|
||||||
self.add_doc(doc)
|
self.add_doc(doc)
|
||||||
|
|
||||||
def perform_lookup(self, unique_id: UniqueID, manifest) -> ParsedDocumentation:
|
def perform_lookup(self, unique_id: UniqueID, manifest) -> Documentation:
|
||||||
if unique_id not in manifest.docs:
|
if unique_id not in manifest.docs:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
f"Doc {unique_id} found in cache but not found in manifest"
|
f"Doc {unique_id} found in cache but not found in manifest"
|
||||||
)
|
)
|
||||||
return manifest.docs[unique_id]
|
return manifest.docs[unique_id]
|
||||||
@@ -127,7 +123,7 @@ class SourceLookup(dbtClassMixin):
|
|||||||
return self.perform_lookup(unique_id, manifest)
|
return self.perform_lookup(unique_id, manifest)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_source(self, source: ParsedSourceDefinition):
|
def add_source(self, source: SourceDefinition):
|
||||||
if source.search_name not in self.storage:
|
if source.search_name not in self.storage:
|
||||||
self.storage[source.search_name] = {}
|
self.storage[source.search_name] = {}
|
||||||
|
|
||||||
@@ -138,9 +134,9 @@ class SourceLookup(dbtClassMixin):
|
|||||||
if hasattr(source, "source_name"):
|
if hasattr(source, "source_name"):
|
||||||
self.add_source(source)
|
self.add_source(source)
|
||||||
|
|
||||||
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> ParsedSourceDefinition:
|
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SourceDefinition:
|
||||||
if unique_id not in manifest.sources:
|
if unique_id not in manifest.sources:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
f"Source {unique_id} found in cache but not found in manifest"
|
f"Source {unique_id} found in cache but not found in manifest"
|
||||||
)
|
)
|
||||||
return manifest.sources[unique_id]
|
return manifest.sources[unique_id]
|
||||||
@@ -178,7 +174,7 @@ class RefableLookup(dbtClassMixin):
|
|||||||
|
|
||||||
def perform_lookup(self, unique_id: UniqueID, manifest) -> ManifestNode:
|
def perform_lookup(self, unique_id: UniqueID, manifest) -> ManifestNode:
|
||||||
if unique_id not in manifest.nodes:
|
if unique_id not in manifest.nodes:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
f"Node {unique_id} found in cache but not found in manifest"
|
f"Node {unique_id} found in cache but not found in manifest"
|
||||||
)
|
)
|
||||||
return manifest.nodes[unique_id]
|
return manifest.nodes[unique_id]
|
||||||
@@ -198,7 +194,7 @@ class MetricLookup(dbtClassMixin):
|
|||||||
return self.perform_lookup(unique_id, manifest)
|
return self.perform_lookup(unique_id, manifest)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_metric(self, metric: ParsedMetric):
|
def add_metric(self, metric: Metric):
|
||||||
if metric.search_name not in self.storage:
|
if metric.search_name not in self.storage:
|
||||||
self.storage[metric.search_name] = {}
|
self.storage[metric.search_name] = {}
|
||||||
|
|
||||||
@@ -209,14 +205,47 @@ class MetricLookup(dbtClassMixin):
|
|||||||
if hasattr(metric, "name"):
|
if hasattr(metric, "name"):
|
||||||
self.add_metric(metric)
|
self.add_metric(metric)
|
||||||
|
|
||||||
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> ParsedMetric:
|
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> Metric:
|
||||||
if unique_id not in manifest.metrics:
|
if unique_id not in manifest.metrics:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
f"Metric {unique_id} found in cache but not found in manifest"
|
f"Metric {unique_id} found in cache but not found in manifest"
|
||||||
)
|
)
|
||||||
return manifest.metrics[unique_id]
|
return manifest.metrics[unique_id]
|
||||||
|
|
||||||
|
|
||||||
|
class EntityLookup(dbtClassMixin):
|
||||||
|
def __init__(self, manifest: "Manifest"):
|
||||||
|
self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
|
||||||
|
self.populate(manifest)
|
||||||
|
|
||||||
|
def get_unique_id(self, search_name, package: Optional[PackageName]):
|
||||||
|
return find_unique_id_for_package(self.storage, search_name, package)
|
||||||
|
|
||||||
|
def find(self, search_name, package: Optional[PackageName], manifest: "Manifest"):
|
||||||
|
unique_id = self.get_unique_id(search_name, package)
|
||||||
|
if unique_id is not None:
|
||||||
|
return self.perform_lookup(unique_id, manifest)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_entity(self, entity: Entity):
|
||||||
|
if entity.search_name not in self.storage:
|
||||||
|
self.storage[entity.search_name] = {}
|
||||||
|
|
||||||
|
self.storage[entity.search_name][entity.package_name] = entity.unique_id
|
||||||
|
|
||||||
|
def populate(self, manifest):
|
||||||
|
for entity in manifest.entities.values():
|
||||||
|
if hasattr(entity, "name"):
|
||||||
|
self.add_entity(entity)
|
||||||
|
|
||||||
|
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> Entity:
|
||||||
|
if unique_id not in manifest.entities:
|
||||||
|
raise dbt.exceptions.DbtInternalError(
|
||||||
|
f"Entity {unique_id} found in cache but not found in manifest"
|
||||||
|
)
|
||||||
|
return manifest.entities[unique_id]
|
||||||
|
|
||||||
|
|
||||||
# This handles both models/seeds/snapshots and sources/metrics/exposures
|
# This handles both models/seeds/snapshots and sources/metrics/exposures
|
||||||
class DisabledLookup(dbtClassMixin):
|
class DisabledLookup(dbtClassMixin):
|
||||||
def __init__(self, manifest: "Manifest"):
|
def __init__(self, manifest: "Manifest"):
|
||||||
@@ -325,7 +354,7 @@ def _sort_values(dct):
|
|||||||
|
|
||||||
|
|
||||||
def build_node_edges(nodes: List[ManifestNode]):
|
def build_node_edges(nodes: List[ManifestNode]):
|
||||||
"""Build the forward and backward edges on the given list of ParsedNodes
|
"""Build the forward and backward edges on the given list of ManifestNodes
|
||||||
and return them as two separate dictionaries, each mapping unique IDs to
|
and return them as two separate dictionaries, each mapping unique IDs to
|
||||||
lists of edges.
|
lists of edges.
|
||||||
"""
|
"""
|
||||||
@@ -343,10 +372,10 @@ def build_node_edges(nodes: List[ManifestNode]):
|
|||||||
# Build a map of children of macros and generic tests
|
# Build a map of children of macros and generic tests
|
||||||
def build_macro_edges(nodes: List[Any]):
|
def build_macro_edges(nodes: List[Any]):
|
||||||
forward_edges: Dict[str, List[str]] = {
|
forward_edges: Dict[str, List[str]] = {
|
||||||
n.unique_id: [] for n in nodes if n.unique_id.startswith("macro") or n.depends_on.macros
|
n.unique_id: [] for n in nodes if n.unique_id.startswith("macro") or n.depends_on_macros
|
||||||
}
|
}
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
for unique_id in node.depends_on.macros:
|
for unique_id in node.depends_on_macros:
|
||||||
if unique_id in forward_edges.keys():
|
if unique_id in forward_edges.keys():
|
||||||
forward_edges[unique_id].append(node.unique_id)
|
forward_edges[unique_id].append(node.unique_id)
|
||||||
return _sort_values(forward_edges)
|
return _sort_values(forward_edges)
|
||||||
@@ -365,7 +394,7 @@ class Locality(enum.IntEnum):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class MacroCandidate:
|
class MacroCandidate:
|
||||||
locality: Locality
|
locality: Locality
|
||||||
macro: ParsedMacro
|
macro: Macro
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, MacroCandidate):
|
if not isinstance(other, MacroCandidate):
|
||||||
@@ -403,12 +432,7 @@ class MaterializationCandidate(MacroCandidate):
|
|||||||
return NotImplemented
|
return NotImplemented
|
||||||
equal = self.specificity == other.specificity and self.locality == other.locality
|
equal = self.specificity == other.specificity and self.locality == other.locality
|
||||||
if equal:
|
if equal:
|
||||||
raise_compiler_error(
|
raise DuplicateMaterializationNameError(self.macro, other)
|
||||||
"Found two materializations with the name {} (packages {} and "
|
|
||||||
"{}). dbt cannot resolve this ambiguity".format(
|
|
||||||
self.macro.name, self.macro.package_name, other.macro.package_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return equal
|
return equal
|
||||||
|
|
||||||
@@ -430,16 +454,14 @@ M = TypeVar("M", bound=MacroCandidate)
|
|||||||
|
|
||||||
|
|
||||||
class CandidateList(List[M]):
|
class CandidateList(List[M]):
|
||||||
def last(self) -> Optional[ParsedMacro]:
|
def last(self) -> Optional[Macro]:
|
||||||
if not self:
|
if not self:
|
||||||
return None
|
return None
|
||||||
self.sort()
|
self.sort()
|
||||||
return self[-1].macro
|
return self[-1].macro
|
||||||
|
|
||||||
|
|
||||||
def _get_locality(
|
def _get_locality(macro: Macro, root_project_name: str, internal_packages: Set[str]) -> Locality:
|
||||||
macro: ParsedMacro, root_project_name: str, internal_packages: Set[str]
|
|
||||||
) -> Locality:
|
|
||||||
if macro.package_name == root_project_name:
|
if macro.package_name == root_project_name:
|
||||||
return Locality.Root
|
return Locality.Root
|
||||||
elif macro.package_name in internal_packages:
|
elif macro.package_name in internal_packages:
|
||||||
@@ -465,16 +487,19 @@ class Disabled(Generic[D]):
|
|||||||
target: D
|
target: D
|
||||||
|
|
||||||
|
|
||||||
MaybeMetricNode = Optional[Union[ParsedMetric, Disabled[ParsedMetric]]]
|
MaybeMetricNode = Optional[Union[Metric, Disabled[Metric]]]
|
||||||
|
|
||||||
|
|
||||||
MaybeDocumentation = Optional[ParsedDocumentation]
|
MaybeEntityNode = Optional[Union[Entity, Disabled[Entity]]]
|
||||||
|
|
||||||
|
|
||||||
|
MaybeDocumentation = Optional[Documentation]
|
||||||
|
|
||||||
|
|
||||||
MaybeParsedSource = Optional[
|
MaybeParsedSource = Optional[
|
||||||
Union[
|
Union[
|
||||||
ParsedSourceDefinition,
|
SourceDefinition,
|
||||||
Disabled[ParsedSourceDefinition],
|
Disabled[SourceDefinition],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -492,13 +517,13 @@ def _update_into(dest: MutableMapping[str, T], new_item: T):
|
|||||||
"""
|
"""
|
||||||
unique_id = new_item.unique_id
|
unique_id = new_item.unique_id
|
||||||
if unique_id not in dest:
|
if unique_id not in dest:
|
||||||
raise dbt.exceptions.RuntimeException(
|
raise dbt.exceptions.DbtRuntimeError(
|
||||||
f"got an update_{new_item.resource_type} call with an "
|
f"got an update_{new_item.resource_type} call with an "
|
||||||
f"unrecognized {new_item.resource_type}: {new_item.unique_id}"
|
f"unrecognized {new_item.resource_type}: {new_item.unique_id}"
|
||||||
)
|
)
|
||||||
existing = dest[unique_id]
|
existing = dest[unique_id]
|
||||||
if new_item.original_file_path != existing.original_file_path:
|
if new_item.original_file_path != existing.original_file_path:
|
||||||
raise dbt.exceptions.RuntimeException(
|
raise dbt.exceptions.DbtRuntimeError(
|
||||||
f"cannot update a {new_item.resource_type} to have a new file path!"
|
f"cannot update a {new_item.resource_type} to have a new file path!"
|
||||||
)
|
)
|
||||||
dest[unique_id] = new_item
|
dest[unique_id] = new_item
|
||||||
@@ -514,7 +539,7 @@ class MacroMethods:
|
|||||||
|
|
||||||
def find_macro_by_name(
|
def find_macro_by_name(
|
||||||
self, name: str, root_project_name: str, package: Optional[str]
|
self, name: str, root_project_name: str, package: Optional[str]
|
||||||
) -> Optional[ParsedMacro]:
|
) -> Optional[Macro]:
|
||||||
"""Find a macro in the graph by its name and package name, or None for
|
"""Find a macro in the graph by its name and package name, or None for
|
||||||
any package. The root project name is used to determine priority:
|
any package. The root project name is used to determine priority:
|
||||||
- locally defined macros come first
|
- locally defined macros come first
|
||||||
@@ -537,7 +562,7 @@ class MacroMethods:
|
|||||||
|
|
||||||
def find_generate_macro_by_name(
|
def find_generate_macro_by_name(
|
||||||
self, component: str, root_project_name: str
|
self, component: str, root_project_name: str
|
||||||
) -> Optional[ParsedMacro]:
|
) -> Optional[Macro]:
|
||||||
"""
|
"""
|
||||||
The `generate_X_name` macros are similar to regular ones, but ignore
|
The `generate_X_name` macros are similar to regular ones, but ignore
|
||||||
imported packages.
|
imported packages.
|
||||||
@@ -606,11 +631,12 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
# is added it must all be added in the __reduce_ex__ method in the
|
# is added it must all be added in the __reduce_ex__ method in the
|
||||||
# args tuple in the right position.
|
# args tuple in the right position.
|
||||||
nodes: MutableMapping[str, ManifestNode] = field(default_factory=dict)
|
nodes: MutableMapping[str, ManifestNode] = field(default_factory=dict)
|
||||||
sources: MutableMapping[str, ParsedSourceDefinition] = field(default_factory=dict)
|
sources: MutableMapping[str, SourceDefinition] = field(default_factory=dict)
|
||||||
macros: MutableMapping[str, ParsedMacro] = field(default_factory=dict)
|
macros: MutableMapping[str, Macro] = field(default_factory=dict)
|
||||||
docs: MutableMapping[str, ParsedDocumentation] = field(default_factory=dict)
|
docs: MutableMapping[str, Documentation] = field(default_factory=dict)
|
||||||
exposures: MutableMapping[str, ParsedExposure] = field(default_factory=dict)
|
exposures: MutableMapping[str, Exposure] = field(default_factory=dict)
|
||||||
metrics: MutableMapping[str, ParsedMetric] = field(default_factory=dict)
|
metrics: MutableMapping[str, Metric] = field(default_factory=dict)
|
||||||
|
entities: MutableMapping[str, Entity] = field(default_factory=dict)
|
||||||
selectors: MutableMapping[str, Any] = field(default_factory=dict)
|
selectors: MutableMapping[str, Any] = field(default_factory=dict)
|
||||||
files: MutableMapping[str, AnySourceFile] = field(default_factory=dict)
|
files: MutableMapping[str, AnySourceFile] = field(default_factory=dict)
|
||||||
metadata: ManifestMetadata = field(default_factory=ManifestMetadata)
|
metadata: ManifestMetadata = field(default_factory=ManifestMetadata)
|
||||||
@@ -632,6 +658,9 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
_metric_lookup: Optional[MetricLookup] = field(
|
_metric_lookup: Optional[MetricLookup] = field(
|
||||||
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
|
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
|
||||||
)
|
)
|
||||||
|
_entity_lookup: Optional[EntityLookup] = field(
|
||||||
|
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
|
||||||
|
)
|
||||||
_disabled_lookup: Optional[DisabledLookup] = field(
|
_disabled_lookup: Optional[DisabledLookup] = field(
|
||||||
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
|
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
|
||||||
)
|
)
|
||||||
@@ -658,7 +687,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
obj._lock = flags.MP_CONTEXT.Lock()
|
obj._lock = flags.MP_CONTEXT.Lock()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def sync_update_node(self, new_node: NonSourceCompiledNode) -> NonSourceCompiledNode:
|
def sync_update_node(self, new_node: ManifestNode) -> ManifestNode:
|
||||||
"""update the node with a lock. The only time we should want to lock is
|
"""update the node with a lock. The only time we should want to lock is
|
||||||
when compiling an ephemeral ancestor of a node at runtime, because
|
when compiling an ephemeral ancestor of a node at runtime, because
|
||||||
multiple threads could be just-in-time compiling the same ephemeral
|
multiple threads could be just-in-time compiling the same ephemeral
|
||||||
@@ -671,21 +700,24 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
existing = self.nodes[new_node.unique_id]
|
existing = self.nodes[new_node.unique_id]
|
||||||
if getattr(existing, "compiled", False):
|
if getattr(existing, "compiled", False):
|
||||||
# already compiled -> must be a NonSourceCompiledNode
|
# already compiled
|
||||||
return cast(NonSourceCompiledNode, existing)
|
return existing
|
||||||
_update_into(self.nodes, new_node)
|
_update_into(self.nodes, new_node)
|
||||||
return new_node
|
return new_node
|
||||||
|
|
||||||
def update_exposure(self, new_exposure: ParsedExposure):
|
def update_exposure(self, new_exposure: Exposure):
|
||||||
_update_into(self.exposures, new_exposure)
|
_update_into(self.exposures, new_exposure)
|
||||||
|
|
||||||
def update_metric(self, new_metric: ParsedMetric):
|
def update_metric(self, new_metric: Metric):
|
||||||
_update_into(self.metrics, new_metric)
|
_update_into(self.metrics, new_metric)
|
||||||
|
|
||||||
|
def update_entity(self, new_entity: Entity):
|
||||||
|
_update_into(self.entities, new_entity)
|
||||||
|
|
||||||
def update_node(self, new_node: ManifestNode):
|
def update_node(self, new_node: ManifestNode):
|
||||||
_update_into(self.nodes, new_node)
|
_update_into(self.nodes, new_node)
|
||||||
|
|
||||||
def update_source(self, new_source: ParsedSourceDefinition):
|
def update_source(self, new_source: SourceDefinition):
|
||||||
_update_into(self.sources, new_source)
|
_update_into(self.sources, new_source)
|
||||||
|
|
||||||
def build_flat_graph(self):
|
def build_flat_graph(self):
|
||||||
@@ -697,6 +729,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self.flat_graph = {
|
self.flat_graph = {
|
||||||
"exposures": {k: v.to_dict(omit_none=False) for k, v in self.exposures.items()},
|
"exposures": {k: v.to_dict(omit_none=False) for k, v in self.exposures.items()},
|
||||||
"metrics": {k: v.to_dict(omit_none=False) for k, v in self.metrics.items()},
|
"metrics": {k: v.to_dict(omit_none=False) for k, v in self.metrics.items()},
|
||||||
|
"entities": {k: v.to_dict(omit_none=False) for k, v in self.entities.items()},
|
||||||
"nodes": {k: v.to_dict(omit_none=False) for k, v in self.nodes.items()},
|
"nodes": {k: v.to_dict(omit_none=False) for k, v in self.nodes.items()},
|
||||||
"sources": {k: v.to_dict(omit_none=False) for k, v in self.sources.items()},
|
"sources": {k: v.to_dict(omit_none=False) for k, v in self.sources.items()},
|
||||||
}
|
}
|
||||||
@@ -738,7 +771,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
|
|
||||||
def find_materialization_macro_by_name(
|
def find_materialization_macro_by_name(
|
||||||
self, project_name: str, materialization_name: str, adapter_type: str
|
self, project_name: str, materialization_name: str, adapter_type: str
|
||||||
) -> Optional[ParsedMacro]:
|
) -> Optional[Macro]:
|
||||||
candidates: CandidateList = CandidateList(
|
candidates: CandidateList = CandidateList(
|
||||||
chain.from_iterable(
|
chain.from_iterable(
|
||||||
self._materialization_candidates_for(
|
self._materialization_candidates_for(
|
||||||
@@ -759,6 +792,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self.nodes.values(),
|
self.nodes.values(),
|
||||||
self.sources.values(),
|
self.sources.values(),
|
||||||
self.metrics.values(),
|
self.metrics.values(),
|
||||||
|
self.entities.values(),
|
||||||
)
|
)
|
||||||
for resource in all_resources:
|
for resource in all_resources:
|
||||||
resource_type_plural = resource.resource_type.pluralize()
|
resource_type_plural = resource.resource_type.pluralize()
|
||||||
@@ -787,6 +821,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
docs={k: _deepcopy(v) for k, v in self.docs.items()},
|
docs={k: _deepcopy(v) for k, v in self.docs.items()},
|
||||||
exposures={k: _deepcopy(v) for k, v in self.exposures.items()},
|
exposures={k: _deepcopy(v) for k, v in self.exposures.items()},
|
||||||
metrics={k: _deepcopy(v) for k, v in self.metrics.items()},
|
metrics={k: _deepcopy(v) for k, v in self.metrics.items()},
|
||||||
|
entities={k: _deepcopy(v) for k, v in self.entities.items()},
|
||||||
selectors={k: _deepcopy(v) for k, v in self.selectors.items()},
|
selectors={k: _deepcopy(v) for k, v in self.selectors.items()},
|
||||||
metadata=self.metadata,
|
metadata=self.metadata,
|
||||||
disabled={k: _deepcopy(v) for k, v in self.disabled.items()},
|
disabled={k: _deepcopy(v) for k, v in self.disabled.items()},
|
||||||
@@ -803,6 +838,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self.sources.values(),
|
self.sources.values(),
|
||||||
self.exposures.values(),
|
self.exposures.values(),
|
||||||
self.metrics.values(),
|
self.metrics.values(),
|
||||||
|
self.entities.values(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
forward_edges, backward_edges = build_node_edges(edge_members)
|
forward_edges, backward_edges = build_node_edges(edge_members)
|
||||||
@@ -828,6 +864,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
docs=self.docs,
|
docs=self.docs,
|
||||||
exposures=self.exposures,
|
exposures=self.exposures,
|
||||||
metrics=self.metrics,
|
metrics=self.metrics,
|
||||||
|
entities=self.entities,
|
||||||
selectors=self.selectors,
|
selectors=self.selectors,
|
||||||
metadata=self.metadata,
|
metadata=self.metadata,
|
||||||
disabled=self.disabled,
|
disabled=self.disabled,
|
||||||
@@ -849,9 +886,11 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
return self.exposures[unique_id]
|
return self.exposures[unique_id]
|
||||||
elif unique_id in self.metrics:
|
elif unique_id in self.metrics:
|
||||||
return self.metrics[unique_id]
|
return self.metrics[unique_id]
|
||||||
|
elif unique_id in self.entities:
|
||||||
|
return self.entities[unique_id]
|
||||||
else:
|
else:
|
||||||
# something terrible has happened
|
# something terrible has happened
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
"Expected node {} not found in manifest".format(unique_id)
|
"Expected node {} not found in manifest".format(unique_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -885,6 +924,12 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self._metric_lookup = MetricLookup(self)
|
self._metric_lookup = MetricLookup(self)
|
||||||
return self._metric_lookup
|
return self._metric_lookup
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entity_lookup(self) -> EntityLookup:
|
||||||
|
if self._entity_lookup is None:
|
||||||
|
self._entity_lookup = EntityLookup(self)
|
||||||
|
return self._entity_lookup
|
||||||
|
|
||||||
def rebuild_ref_lookup(self):
|
def rebuild_ref_lookup(self):
|
||||||
self._ref_lookup = RefableLookup(self)
|
self._ref_lookup = RefableLookup(self)
|
||||||
|
|
||||||
@@ -943,8 +988,8 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
search_name = f"{target_source_name}.{target_table_name}"
|
search_name = f"{target_source_name}.{target_table_name}"
|
||||||
candidates = _search_packages(current_project, node_package)
|
candidates = _search_packages(current_project, node_package)
|
||||||
|
|
||||||
source: Optional[ParsedSourceDefinition] = None
|
source: Optional[SourceDefinition] = None
|
||||||
disabled: Optional[List[ParsedSourceDefinition]] = None
|
disabled: Optional[List[SourceDefinition]] = None
|
||||||
|
|
||||||
for pkg in candidates:
|
for pkg in candidates:
|
||||||
source = self.source_lookup.find(search_name, pkg, self)
|
source = self.source_lookup.find(search_name, pkg, self)
|
||||||
@@ -968,8 +1013,8 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
node_package: str,
|
node_package: str,
|
||||||
) -> MaybeMetricNode:
|
) -> MaybeMetricNode:
|
||||||
|
|
||||||
metric: Optional[ParsedMetric] = None
|
metric: Optional[Metric] = None
|
||||||
disabled: Optional[List[ParsedMetric]] = None
|
disabled: Optional[List[Metric]] = None
|
||||||
|
|
||||||
candidates = _search_packages(current_project, node_package, target_metric_package)
|
candidates = _search_packages(current_project, node_package, target_metric_package)
|
||||||
for pkg in candidates:
|
for pkg in candidates:
|
||||||
@@ -985,6 +1030,31 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
return Disabled(disabled[0])
|
return Disabled(disabled[0])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def resolve_entity(
|
||||||
|
self,
|
||||||
|
target_entity_name: str,
|
||||||
|
target_entity_package: Optional[str],
|
||||||
|
current_project: str,
|
||||||
|
node_package: str,
|
||||||
|
) -> MaybeEntityNode:
|
||||||
|
|
||||||
|
entity: Optional[Entity] = None
|
||||||
|
disabled: Optional[List[Entity]] = None
|
||||||
|
|
||||||
|
candidates = _search_packages(current_project, node_package, target_entity_package)
|
||||||
|
for pkg in candidates:
|
||||||
|
entity = self.entity_lookup.find(target_entity_name, pkg, self)
|
||||||
|
|
||||||
|
if entity is not None and entity.config.enabled:
|
||||||
|
return entity
|
||||||
|
|
||||||
|
# it's possible that the node is disabled
|
||||||
|
if disabled is None:
|
||||||
|
disabled = self.disabled_lookup.find(f"{target_entity_name}", pkg)
|
||||||
|
if disabled:
|
||||||
|
return Disabled(disabled[0])
|
||||||
|
return None
|
||||||
|
|
||||||
# Called by DocsRuntimeContext.doc
|
# Called by DocsRuntimeContext.doc
|
||||||
def resolve_doc(
|
def resolve_doc(
|
||||||
self,
|
self,
|
||||||
@@ -992,7 +1062,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
package: Optional[str],
|
package: Optional[str],
|
||||||
current_project: str,
|
current_project: str,
|
||||||
node_package: str,
|
node_package: str,
|
||||||
) -> Optional[ParsedDocumentation]:
|
) -> Optional[Documentation]:
|
||||||
"""Resolve the given documentation. This follows the same algorithm as
|
"""Resolve the given documentation. This follows the same algorithm as
|
||||||
resolve_ref except the is_enabled checks are unnecessary as docs are
|
resolve_ref except the is_enabled checks are unnecessary as docs are
|
||||||
always enabled.
|
always enabled.
|
||||||
@@ -1044,29 +1114,10 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
|
|
||||||
# Methods that were formerly in ParseResult
|
# Methods that were formerly in ParseResult
|
||||||
|
|
||||||
def add_macro(self, source_file: SourceFile, macro: ParsedMacro):
|
def add_macro(self, source_file: SourceFile, macro: Macro):
|
||||||
if macro.unique_id in self.macros:
|
if macro.unique_id in self.macros:
|
||||||
# detect that the macro exists and emit an error
|
# detect that the macro exists and emit an error
|
||||||
other_path = self.macros[macro.unique_id].original_file_path
|
raise DuplicateMacroInPackageError(macro=macro, macro_mapping=self.macros)
|
||||||
# subtract 2 for the "Compilation Error" indent
|
|
||||||
# note that the line wrap eats newlines, so if you want newlines,
|
|
||||||
# this is the result :(
|
|
||||||
msg = line_wrap_message(
|
|
||||||
f"""\
|
|
||||||
dbt found two macros named "{macro.name}" in the project
|
|
||||||
"{macro.package_name}".
|
|
||||||
|
|
||||||
|
|
||||||
To fix this error, rename or remove one of the following
|
|
||||||
macros:
|
|
||||||
|
|
||||||
- {macro.original_file_path}
|
|
||||||
|
|
||||||
- {other_path}
|
|
||||||
""",
|
|
||||||
subtract=2,
|
|
||||||
)
|
|
||||||
raise_compiler_error(msg)
|
|
||||||
|
|
||||||
self.macros[macro.unique_id] = macro
|
self.macros[macro.unique_id] = macro
|
||||||
source_file.macros.append(macro.unique_id)
|
source_file.macros.append(macro.unique_id)
|
||||||
@@ -1086,34 +1137,39 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self.sources[source.unique_id] = source # type: ignore
|
self.sources[source.unique_id] = source # type: ignore
|
||||||
source_file.sources.append(source.unique_id)
|
source_file.sources.append(source.unique_id)
|
||||||
|
|
||||||
def add_node_nofile(self, node: ManifestNodes):
|
def add_node_nofile(self, node: ManifestNode):
|
||||||
# nodes can't be overwritten!
|
# nodes can't be overwritten!
|
||||||
_check_duplicates(node, self.nodes)
|
_check_duplicates(node, self.nodes)
|
||||||
self.nodes[node.unique_id] = node
|
self.nodes[node.unique_id] = node
|
||||||
|
|
||||||
def add_node(self, source_file: AnySourceFile, node: ManifestNodes, test_from=None):
|
def add_node(self, source_file: AnySourceFile, node: ManifestNode, test_from=None):
|
||||||
self.add_node_nofile(node)
|
self.add_node_nofile(node)
|
||||||
if isinstance(source_file, SchemaSourceFile):
|
if isinstance(source_file, SchemaSourceFile):
|
||||||
if isinstance(node, ParsedGenericTestNode):
|
if isinstance(node, GenericTestNode):
|
||||||
assert test_from
|
assert test_from
|
||||||
source_file.add_test(node.unique_id, test_from)
|
source_file.add_test(node.unique_id, test_from)
|
||||||
if isinstance(node, ParsedMetric):
|
if isinstance(node, Metric):
|
||||||
source_file.metrics.append(node.unique_id)
|
source_file.metrics.append(node.unique_id)
|
||||||
if isinstance(node, ParsedExposure):
|
if isinstance(node, Exposure):
|
||||||
source_file.exposures.append(node.unique_id)
|
source_file.exposures.append(node.unique_id)
|
||||||
else:
|
else:
|
||||||
source_file.nodes.append(node.unique_id)
|
source_file.nodes.append(node.unique_id)
|
||||||
|
|
||||||
def add_exposure(self, source_file: SchemaSourceFile, exposure: ParsedExposure):
|
def add_exposure(self, source_file: SchemaSourceFile, exposure: Exposure):
|
||||||
_check_duplicates(exposure, self.exposures)
|
_check_duplicates(exposure, self.exposures)
|
||||||
self.exposures[exposure.unique_id] = exposure
|
self.exposures[exposure.unique_id] = exposure
|
||||||
source_file.exposures.append(exposure.unique_id)
|
source_file.exposures.append(exposure.unique_id)
|
||||||
|
|
||||||
def add_metric(self, source_file: SchemaSourceFile, metric: ParsedMetric):
|
def add_metric(self, source_file: SchemaSourceFile, metric: Metric):
|
||||||
_check_duplicates(metric, self.metrics)
|
_check_duplicates(metric, self.metrics)
|
||||||
self.metrics[metric.unique_id] = metric
|
self.metrics[metric.unique_id] = metric
|
||||||
source_file.metrics.append(metric.unique_id)
|
source_file.metrics.append(metric.unique_id)
|
||||||
|
|
||||||
|
def add_entity(self, source_file: SchemaSourceFile, entity: Entity):
|
||||||
|
_check_duplicates(entity, self.entities)
|
||||||
|
self.entities[entity.unique_id] = entity
|
||||||
|
source_file.entities.append(entity.unique_id)
|
||||||
|
|
||||||
def add_disabled_nofile(self, node: GraphMemberNode):
|
def add_disabled_nofile(self, node: GraphMemberNode):
|
||||||
# There can be multiple disabled nodes for the same unique_id
|
# There can be multiple disabled nodes for the same unique_id
|
||||||
if node.unique_id in self.disabled:
|
if node.unique_id in self.disabled:
|
||||||
@@ -1121,20 +1177,22 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
else:
|
else:
|
||||||
self.disabled[node.unique_id] = [node]
|
self.disabled[node.unique_id] = [node]
|
||||||
|
|
||||||
def add_disabled(self, source_file: AnySourceFile, node: CompileResultNode, test_from=None):
|
def add_disabled(self, source_file: AnySourceFile, node: ResultNode, test_from=None):
|
||||||
self.add_disabled_nofile(node)
|
self.add_disabled_nofile(node)
|
||||||
if isinstance(source_file, SchemaSourceFile):
|
if isinstance(source_file, SchemaSourceFile):
|
||||||
if isinstance(node, ParsedGenericTestNode):
|
if isinstance(node, GenericTestNode):
|
||||||
assert test_from
|
assert test_from
|
||||||
source_file.add_test(node.unique_id, test_from)
|
source_file.add_test(node.unique_id, test_from)
|
||||||
if isinstance(node, ParsedMetric):
|
if isinstance(node, Metric):
|
||||||
source_file.metrics.append(node.unique_id)
|
source_file.metrics.append(node.unique_id)
|
||||||
if isinstance(node, ParsedExposure):
|
if isinstance(node, Entity):
|
||||||
|
source_file.entities.append(node.unique_id)
|
||||||
|
if isinstance(node, Exposure):
|
||||||
source_file.exposures.append(node.unique_id)
|
source_file.exposures.append(node.unique_id)
|
||||||
else:
|
else:
|
||||||
source_file.nodes.append(node.unique_id)
|
source_file.nodes.append(node.unique_id)
|
||||||
|
|
||||||
def add_doc(self, source_file: SourceFile, doc: ParsedDocumentation):
|
def add_doc(self, source_file: SourceFile, doc: Documentation):
|
||||||
_check_duplicates(doc, self.docs)
|
_check_duplicates(doc, self.docs)
|
||||||
self.docs[doc.unique_id] = doc
|
self.docs[doc.unique_id] = doc
|
||||||
source_file.docs.append(doc.unique_id)
|
source_file.docs.append(doc.unique_id)
|
||||||
@@ -1156,6 +1214,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self.docs,
|
self.docs,
|
||||||
self.exposures,
|
self.exposures,
|
||||||
self.metrics,
|
self.metrics,
|
||||||
|
self.entities,
|
||||||
self.selectors,
|
self.selectors,
|
||||||
self.files,
|
self.files,
|
||||||
self.metadata,
|
self.metadata,
|
||||||
@@ -1168,6 +1227,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
|
|||||||
self._source_lookup,
|
self._source_lookup,
|
||||||
self._ref_lookup,
|
self._ref_lookup,
|
||||||
self._metric_lookup,
|
self._metric_lookup,
|
||||||
|
self._entity_lookup,
|
||||||
self._disabled_lookup,
|
self._disabled_lookup,
|
||||||
self._analysis_lookup,
|
self._analysis_lookup,
|
||||||
)
|
)
|
||||||
@@ -1192,27 +1252,30 @@ class WritableManifest(ArtifactMixin):
|
|||||||
nodes: Mapping[UniqueID, ManifestNode] = field(
|
nodes: Mapping[UniqueID, ManifestNode] = field(
|
||||||
metadata=dict(description=("The nodes defined in the dbt project and its dependencies"))
|
metadata=dict(description=("The nodes defined in the dbt project and its dependencies"))
|
||||||
)
|
)
|
||||||
sources: Mapping[UniqueID, ParsedSourceDefinition] = field(
|
sources: Mapping[UniqueID, SourceDefinition] = field(
|
||||||
metadata=dict(description=("The sources defined in the dbt project and its dependencies"))
|
metadata=dict(description=("The sources defined in the dbt project and its dependencies"))
|
||||||
)
|
)
|
||||||
macros: Mapping[UniqueID, ParsedMacro] = field(
|
macros: Mapping[UniqueID, Macro] = field(
|
||||||
metadata=dict(description=("The macros defined in the dbt project and its dependencies"))
|
metadata=dict(description=("The macros defined in the dbt project and its dependencies"))
|
||||||
)
|
)
|
||||||
docs: Mapping[UniqueID, ParsedDocumentation] = field(
|
docs: Mapping[UniqueID, Documentation] = field(
|
||||||
metadata=dict(description=("The docs defined in the dbt project and its dependencies"))
|
metadata=dict(description=("The docs defined in the dbt project and its dependencies"))
|
||||||
)
|
)
|
||||||
exposures: Mapping[UniqueID, ParsedExposure] = field(
|
exposures: Mapping[UniqueID, Exposure] = field(
|
||||||
metadata=dict(
|
metadata=dict(
|
||||||
description=("The exposures defined in the dbt project and its dependencies")
|
description=("The exposures defined in the dbt project and its dependencies")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
metrics: Mapping[UniqueID, ParsedMetric] = field(
|
metrics: Mapping[UniqueID, Metric] = field(
|
||||||
metadata=dict(description=("The metrics defined in the dbt project and its dependencies"))
|
metadata=dict(description=("The metrics defined in the dbt project and its dependencies"))
|
||||||
)
|
)
|
||||||
|
entities: Mapping[UniqueID, Entity] = field(
|
||||||
|
metadata=dict(description=("The entities defined in the dbt project and its dependencies"))
|
||||||
|
)
|
||||||
selectors: Mapping[UniqueID, Any] = field(
|
selectors: Mapping[UniqueID, Any] = field(
|
||||||
metadata=dict(description=("The selectors defined in selectors.yml"))
|
metadata=dict(description=("The selectors defined in selectors.yml"))
|
||||||
)
|
)
|
||||||
disabled: Optional[Mapping[UniqueID, List[CompileResultNode]]] = field(
|
disabled: Optional[Mapping[UniqueID, List[ResultNode]]] = field(
|
||||||
metadata=dict(description="A mapping of the disabled nodes in the target")
|
metadata=dict(description="A mapping of the disabled nodes in the target")
|
||||||
)
|
)
|
||||||
parent_map: Optional[NodeEdgeMap] = field(
|
parent_map: Optional[NodeEdgeMap] = field(
|
||||||
@@ -1233,7 +1296,8 @@ class WritableManifest(ArtifactMixin):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def compatible_previous_versions(self):
|
def compatible_previous_versions(self):
|
||||||
return [("manifest", 4), ("manifest", 5), ("manifest", 6), ("manifest", 7)]
|
# return [("manifest", 4), ("manifest", 5), ("manifest", 6), ("manifest", 7)]
|
||||||
|
return []
|
||||||
|
|
||||||
def __post_serialize__(self, dct):
|
def __post_serialize__(self, dct):
|
||||||
for unique_id, node in dct["nodes"].items():
|
for unique_id, node in dct["nodes"].items():
|
||||||
@@ -1242,9 +1306,9 @@ class WritableManifest(ArtifactMixin):
|
|||||||
return dct
|
return dct
|
||||||
|
|
||||||
|
|
||||||
def _check_duplicates(value: HasUniqueID, src: Mapping[str, HasUniqueID]):
|
def _check_duplicates(value: BaseNode, src: Mapping[str, BaseNode]):
|
||||||
if value.unique_id in src:
|
if value.unique_id in src:
|
||||||
raise_duplicate_resource_name(value, src[value.unique_id])
|
raise DuplicateResourceNameError(value, src[value.unique_id])
|
||||||
|
|
||||||
|
|
||||||
K_T = TypeVar("K_T")
|
K_T = TypeVar("K_T")
|
||||||
@@ -1253,7 +1317,7 @@ V_T = TypeVar("V_T")
|
|||||||
|
|
||||||
def _expect_value(key: K_T, src: Mapping[K_T, V_T], old_file: SourceFile, name: str) -> V_T:
|
def _expect_value(key: K_T, src: Mapping[K_T, V_T], old_file: SourceFile, name: str) -> V_T:
|
||||||
if key not in src:
|
if key not in src:
|
||||||
raise CompilationException(
|
raise CompilationError(
|
||||||
'Expected to find "{}" in cached "result.{}" based '
|
'Expected to find "{}" in cached "result.{}" based '
|
||||||
"on cached file information: {}!".format(key, name, old_file)
|
"on cached file information: {}!".format(key, name, old_file)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class MetricReference(object):
|
|||||||
|
|
||||||
class ResolvedMetricReference(MetricReference):
|
class ResolvedMetricReference(MetricReference):
|
||||||
"""
|
"""
|
||||||
Simple proxy over a ParsedMetric which delegates property
|
Simple proxy over a Metric which delegates property
|
||||||
lookups to the underlying node. Also adds helper functions
|
lookups to the underlying node. Also adds helper functions
|
||||||
for working with metrics (ie. __str__ and templating functions)
|
for working with metrics (ie. __str__ and templating functions)
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from dbt.dataclass_schema import (
|
|||||||
)
|
)
|
||||||
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed, Docs
|
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed, Docs
|
||||||
from dbt.contracts.graph.utils import validate_color
|
from dbt.contracts.graph.utils import validate_color
|
||||||
from dbt.exceptions import InternalException, CompilationException
|
from dbt.exceptions import DbtInternalError, CompilationError
|
||||||
from dbt.contracts.util import Replaceable, list_str
|
from dbt.contracts.util import Replaceable, list_str
|
||||||
from dbt import hooks
|
from dbt import hooks
|
||||||
from dbt.node_types import NodeType
|
from dbt.node_types import NodeType
|
||||||
@@ -30,7 +30,7 @@ def _get_meta_value(cls: Type[M], fld: Field, key: str, default: Any) -> M:
|
|||||||
try:
|
try:
|
||||||
return cls(value)
|
return cls(value)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise InternalException(f"Invalid {cls} value: {value}") from exc
|
raise DbtInternalError(f"Invalid {cls} value: {value}") from exc
|
||||||
|
|
||||||
|
|
||||||
def _set_meta_value(obj: M, key: str, existing: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
def _set_meta_value(obj: M, key: str, existing: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
@@ -140,17 +140,17 @@ def _merge_field_value(
|
|||||||
return _listify(self_value) + _listify(other_value)
|
return _listify(self_value) + _listify(other_value)
|
||||||
elif merge_behavior == MergeBehavior.Update:
|
elif merge_behavior == MergeBehavior.Update:
|
||||||
if not isinstance(self_value, dict):
|
if not isinstance(self_value, dict):
|
||||||
raise InternalException(f"expected dict, got {self_value}")
|
raise DbtInternalError(f"expected dict, got {self_value}")
|
||||||
if not isinstance(other_value, dict):
|
if not isinstance(other_value, dict):
|
||||||
raise InternalException(f"expected dict, got {other_value}")
|
raise DbtInternalError(f"expected dict, got {other_value}")
|
||||||
value = self_value.copy()
|
value = self_value.copy()
|
||||||
value.update(other_value)
|
value.update(other_value)
|
||||||
return value
|
return value
|
||||||
elif merge_behavior == MergeBehavior.DictKeyAppend:
|
elif merge_behavior == MergeBehavior.DictKeyAppend:
|
||||||
if not isinstance(self_value, dict):
|
if not isinstance(self_value, dict):
|
||||||
raise InternalException(f"expected dict, got {self_value}")
|
raise DbtInternalError(f"expected dict, got {self_value}")
|
||||||
if not isinstance(other_value, dict):
|
if not isinstance(other_value, dict):
|
||||||
raise InternalException(f"expected dict, got {other_value}")
|
raise DbtInternalError(f"expected dict, got {other_value}")
|
||||||
new_dict = {}
|
new_dict = {}
|
||||||
for key in self_value.keys():
|
for key in self_value.keys():
|
||||||
new_dict[key] = _listify(self_value[key])
|
new_dict[key] = _listify(self_value[key])
|
||||||
@@ -172,7 +172,7 @@ def _merge_field_value(
|
|||||||
return new_dict
|
return new_dict
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise InternalException(f"Got an invalid merge_behavior: {merge_behavior}")
|
raise DbtInternalError(f"Got an invalid merge_behavior: {merge_behavior}")
|
||||||
|
|
||||||
|
|
||||||
def insensitive_patterns(*patterns: str):
|
def insensitive_patterns(*patterns: str):
|
||||||
@@ -227,7 +227,7 @@ class BaseConfig(AdditionalPropertiesAllowed, Replaceable):
|
|||||||
msg = (
|
msg = (
|
||||||
'Error, tried to delete config key "{}": Cannot delete ' "built-in keys"
|
'Error, tried to delete config key "{}": Cannot delete ' "built-in keys"
|
||||||
).format(key)
|
).format(key)
|
||||||
raise CompilationException(msg)
|
raise CompilationError(msg)
|
||||||
else:
|
else:
|
||||||
del self._extra[key]
|
del self._extra[key]
|
||||||
|
|
||||||
@@ -368,6 +368,11 @@ class MetricConfig(BaseConfig):
|
|||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EntityConfig(BaseConfig):
|
||||||
|
enabled: bool = True
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ExposureConfig(BaseConfig):
|
class ExposureConfig(BaseConfig):
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
@@ -604,6 +609,7 @@ class SnapshotConfig(EmptySnapshotConfig):
|
|||||||
|
|
||||||
RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
|
RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
|
||||||
NodeType.Metric: MetricConfig,
|
NodeType.Metric: MetricConfig,
|
||||||
|
NodeType.Entity: EntityConfig,
|
||||||
NodeType.Exposure: ExposureConfig,
|
NodeType.Exposure: ExposureConfig,
|
||||||
NodeType.Source: SourceConfig,
|
NodeType.Source: SourceConfig,
|
||||||
NodeType.Seed: SeedConfig,
|
NodeType.Seed: SeedConfig,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user