Compare commits

...

170 Commits

Author SHA1 Message Date
MichelleArk
cf4384da38 uncomment dbt-postgres-testing 2025-12-12 17:03:06 -05:00
Michelle Ark
71a6e53102 delete deprecated create_adapter_plugins script (#12280) 2025-12-12 17:01:18 -05:00
Michelle Ark
c4dc80dcd2 delete duplicate adapters tests (#12275) 2025-12-12 16:53:04 -05:00
Michelle Ark
8097a34726 remove snowplow telemetry schemas for < 0.11.0 versions (#12279) 2025-12-12 14:54:40 -05:00
MichelleArk
b66dff7278 move custom-hooks to scripts 2025-12-12 14:24:07 -05:00
Michelle Ark
22d21edb4b Reorganize docs/arch (#12270) 2025-12-12 10:26:23 -05:00
Michelle Ark
bef7928e22 remove unused performance CI framework (#12278) 2025-12-12 10:26:06 -05:00
Michelle Ark
c573131d91 cleanup stale test migration docs (#12274) 2025-12-11 12:19:35 -05:00
Michelle Ark
f10d84d05e move setup_db.sh to scripts, remove test dir (#12273) 2025-12-11 12:19:22 -05:00
Michelle Ark
79a4c8969e improve error message clarity when detecting nodes with spaces in name (#12272) 2025-12-11 12:15:06 -05:00
Gerda Shank
9a80308fcf Implementation of meta_get and meta_require (#12267) 2025-12-10 22:57:28 -05:00
Quigley Malcolm
7a13d08376 Ensure all recent deprecation warnings include the name in the message (#12265)
* Add event name to `message` of recently added deprecations

* Make it harder to not supply the event name to deprecation messages

* Add changie doc

* Fixup import naming
2025-12-10 13:03:24 -06:00
Colin Rogers
9e9f5b8e57 add add_catalog_integration call even if we have a pre-existing manifest (#12262)
* add add_catalog_integration call even if we have a pre-existing manifest

* add changelog
2025-12-10 09:35:39 -08:00
Michelle Ark
9cd6a23eba add compile test for batch context vars (#12261) 2025-12-09 12:08:43 -08:00
Emily Rockman
e46c37cf07 fix target file for dbt-common CI (#12258) 2025-12-08 17:15:24 -05:00
Michelle Ark
df23f398a6 set unit test config.enabled to False if it is testing a disabled model (#12251) 2025-12-08 13:27:41 -08:00
Emily Rockman
97df9278c0 Move to hatch for build tooling (#12192)
* initial hatch implmentation

* cleanup docs

* replacing makefile

* cleanup hatch commands to match adapters

reorganize more to match adapters setup

script comment

dont pip install

fix test commands

* changelog

improve changelog

* CI fix

* fix for env

* use a standard version file

* remove odd license logic

* fix bumpversion

* remove sha input

* more cleanup

* fix legacy build path

* define version for pyproject.toml

* use hatch hook for license

* remove tox

* ensure tests are split

* remove temp file for testing

* explicitly match old verion in pyproject.toml

* fix up testing

* get rid of bumpversion

* put dev_dependencies.txtin hatch

* setup.py is now dead

* set python version for local dev

* local dev fixes

* temp script to compare wheels

* parity with existing wheel builds

* Revert "temp script to compare wheels"

This reverts commit c31417a092.

* fix docker test file
2025-12-05 21:59:44 -05:00
Edgar Ramírez Mondragón
748d352b6b Address Click 8.2+ deprecation warning by using type-checking imports (#12039) 2025-12-05 13:13:25 -08:00
Michelle Ark
bbd8fa02f1 fix flaky invocation context + warn error settings in parser unit tests (#12256) 2025-12-05 10:19:08 -05:00
Emily Rockman
61009f6ba7 Tweak release for unused fields (#12209)
* point to branch:

* remove unused code paths

* make release backwards compatible

hardcode

* use correct types

* put main back
2025-12-04 09:11:59 -05:00
Emily Rockman
ee7ecdc29f Improve --add-package duplicate detection (#12239)
* optimize name matches

* changelog

* Apply suggestion from @emmyoop
2025-12-03 12:49:57 -05:00
Matt Burke
d74b58a137 Fix partial parsing bug with singular tests (#12224) 2025-12-02 14:30:47 -05:00
Michelle Ark
12b04e7d2f avoid raising custom-key-in-config-deprecation for pre/post-hook model SQL config validation (#12244) 2025-12-02 14:22:02 -05:00
Michelle Ark
5d56a052a7 Turn on jsonschema-based deprecations by default, based on adapter support (#12240) 2025-12-02 12:37:37 -05:00
Emily Rockman
62a8ea05a6 stop excluding the core team from changelogs (#12241) 2025-12-02 09:38:24 -05:00
Emily Rockman
1219bd49aa Merge pull request #12238 from dbt-labs/revert-merge
Revert merge on main
2025-12-01 14:05:11 -05:00
Emily Rockman
791d1ebdcd Revert "changelog"
This reverts commit 8ff86d35ea.
2025-12-01 13:27:03 -05:00
Emily Rockman
148b9b41a5 Revert "optimize name matches"
This reverts commit 087f8167ec.
2025-12-01 13:27:02 -05:00
Emily Rockman
d096a6776e Revert "deal with bool"
This reverts commit bcb07ceb7b.
2025-12-01 13:26:58 -05:00
Emily Rockman
8ff86d35ea changelog 2025-12-01 13:12:30 -05:00
Emily Rockman
087f8167ec optimize name matches 2025-12-01 13:12:29 -05:00
Emily Rockman
bcb07ceb7b deal with bool 2025-12-01 13:12:25 -05:00
Emily Rockman
c559848044 Fix --add-package when warn-unpinned: false present (#12233)
* add tests

fix tests

* deal with bool

* fix bug

* changelog
2025-12-01 13:03:37 -05:00
Emily Rockman
3de0160b00 stop codecov CI checks (#12235) 2025-12-01 10:25:20 -05:00
Michelle Ark
2c7f49a71e Support unit testing models that depend on source with the same name (#12220) 2025-11-28 14:32:15 -05:00
Michelle Ark
518c360a29 Avoid retrying successful run operation (#12227) 2025-11-28 14:31:53 -05:00
Emily Rockman
8cf51fddba fix local test failure (#12228)
fix comments
2025-11-28 13:28:19 -05:00
Emily Rockman
8e128eee8e Fix: Data type size/precision/scale changes incorrectly flagged as breaking for versioned models (#12219)
* add failing test

* deal with precision

partition

* changelog

* accoutn for case sensitivity

* better testing
2025-11-28 09:45:56 -05:00
Emily Rockman
94b69b1578 Fix stack traces shown for incompatible package version errors (#12218)
* add test, fix exception hierarchy

* changelog
2025-11-28 09:45:33 -05:00
Michelle Ark
0216e32c7f Add test for unit testing model that depends on input model with alias (#12217) 2025-11-26 18:25:10 -05:00
Andrey Siunov
bbd078089e Fix generation of deprecations summary (#12202) 2025-11-25 23:15:55 -06:00
Trilok Ramakrishna
575bac3172 Allow dbt deps to run when vars lack defaults in dbt_project.yml (#12171)
* Allow dbt deps to run when vars lack defaults in dbt_project.yml

* Added Changelog entry

* fixed integration tests

* fixed mypy error

* Fix: Use strict var validation by default, lenient only for dbt deps to show helpful errors

* Fixed Integration tests

* fixed nit review comments

* addressed review comments and cleaned up tests

* addressed review comments and cleaned up tests
2025-11-26 10:38:37 +05:30
Quigley Malcolm
bca2211246 Stop emitting NoNodesForSelectionCriteria three times during build command (#12204)
* Add test checking that `NoNodesForSelectionCriteria` is only fired once per invocation

* Stop emitting `NoNodesForSelectionCriteria` three times during `build` command

* update changelog

---------

Co-authored-by: Michelle Ark <MichelleArk@users.noreply.github.com>
2025-11-25 13:28:52 -06:00
Michelle Ark
0015e35a1b Correctly reference foreign key references when deferring (#12199) 2025-11-25 13:37:07 -05:00
Emily Rockman
09bce7af63 Add exception when using --state and referring to a removed test (#12203)
* add test using repro from issue

fix test

more test fixes

fix test

* error on none

* changelog

* use correct fixture pattern
2025-11-25 13:26:50 -05:00
Quigley Malcolm
cb7c4a7dce Partial Parsing support for function nodes (#12074)
* Explicitly support functions during partial parsing

* Emit a `Note` event when partial parsing is skipped due to there being no changes

* Begin testing partial parsing support of function nodes

* Add changie doc

* Move test_pp_functions to use `EventCatcher` from dbt-common

* Remove from `functions` instead of `nodes` during partial parsing function deletion

* Fix the partial parsing scheduling of function sql and yaml files

Previously we were treating the partial parsing scheduling of function
files as if they were only defined by YAML files. However functions consist
of a "raw code file" (typically a .sql file) and a YAML file. We needed
to update the the deletion handling + scheduling of functions during partial
parsing to act more similar to "mssat" files in order to achieve this.
This work was primarily done agentically, but then simplified by myself
afterwards.

* Test that changing the alias of a function doesn't require reparsing of the downstream nodes that reference it
2025-11-24 16:53:17 -06:00
Quigley Malcolm
5555a3dd25 Ensure schemas of function nodes are created when in DAG during build command (#12198)
* Add test to check that functions with not default schemas get their schemas created

* Ensure schemas of function nodes are created when in DAG during `build` command

* Add changie doc for function schema bug fix
2025-11-24 16:31:03 -06:00
Michelle Ark
0e30db4e82 restore DuplicateResourceNameError exception within a package (#12183) 2025-11-24 14:39:47 -05:00
Quigley Malcolm
b2ff6ab5a7 Fix bug function wasn't specifiable with --exlcude-resource-type flag (#12187) 2025-11-21 09:19:43 -06:00
Michelle Ark
48218be274 update jsonschemas (#12180) 2025-11-19 13:10:55 -05:00
Trilok Ramakrishna
500208c009 Use EventCatcher from dbt-common (#12177)
* removed event catcher from utils

* Remove empty utils.py after EventCatcher migration to dbt-common
2025-11-19 22:17:25 +05:30
rckahlert-posedio
0162b71e94 Prevent applying the "community" label to bot PRs (#11912) (#12173)
Bypass check as I manually checked this does not have artifact changes
2025-11-18 20:34:51 -05:00
Quigley Malcolm
811e4ee955 Support default arguments for udfs (#12175)
* Add tests to check parsing of function argument default values

* Begin allowing the specification of `default_value` on function arguments

* Validate that non-default function arguments don't come _after_ default function arguments

* Add changie doc
2025-11-18 12:23:55 -06:00
FishtownBuildBot
b79ec3c33b Cleanup main after cutting new 1.11.latest branch (#12179)
* Clean up changelog on main

* Bumping version to 1.12.0a1

* Code quality cleanup

* Update CHANGELOG.md

---------

Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
2025-11-18 13:13:12 -05:00
Emily Rockman
e32718e666 move full workflow into core since no one else uses it (#12178) 2025-11-18 13:12:43 -05:00
Michelle Ark
f6e0793d00 Fix 10157: macro composition in unit tests (#12168) 2025-11-18 11:34:48 -05:00
Patrick Yost
39cf2ec94f Add test coverage for SL metric parsing (#12169) 2025-11-17 17:49:42 -06:00
ddk-dbt
3caddd0b65 Pin GitHub Actions to specific SHAs (53 actions in 11 files) (#12133)
Updated action references from tags/branches to specific commit SHAs for improved security and reproducibility.
2025-11-14 10:21:55 -05:00
Patrick Yost
5dd516608b Stop allowing hyphens in metric names and add name validation tests (#12158) 2025-11-10 18:53:37 -06:00
Quigley Malcolm
11ada88e48 Stop compiling Python UDFs like they're Python Models (#12154)
* Only do special python model compilation for python models (not other python nodes)

* Test jinja compilation of python udfs

* Add changie doc
2025-11-06 18:47:47 -06:00
William Deng
b9e5c144a9 Properly propagate config for metric when created from measure (#12135)
* Propagate measure.config to metric.config when specified during create_metric:True

* changelog

* Update the metric.expr to be populated correctly according to DSI rules
2025-11-06 16:37:00 -06:00
Michelle Ark
001e729664 support DBT_ENGINE prefix for DBT_RECORDER env vars (#12149)
* support DBT_ENGINE prefix for DBT_RECORDER env vars

* changelog entry
2025-11-06 11:12:26 -06:00
Emily Rockman
7e10fc72d5 Move from setup.py to pyproject.toml (#12129)
* convert setup.py to pyproject.toml

* move dev requirements into pyproject.toml

* with setup.py gone we can install from root

* lint

cleanrly state intention to remove

* convert precommit to use dev deps

* consolidate version to pyproject.toml

* editable req

get rid of editable-req

* docs updates

* tweak configs for builds

* fix script

* changelog

* fixes to build

* revert unnecesary changes

more simplification

revert linting

more simplification

fix

don’t need it
2025-11-06 09:08:00 -05:00
Emily Rockman
c170211ce3 remove unreferenced png (#12141) 2025-11-05 12:20:44 -05:00
Emily Rockman
8e800cee4c Consolidate pre-commit config (#12140)
* use single version of tools

* isort
2025-11-05 12:20:29 -05:00
Michelle Ark
1bd81f5025 fix falsy evaluation of build_after.count (#12136) 2025-11-03 14:09:04 -05:00
Quigley Malcolm
65a122b34a Revert "Upgrade DSI to 0.10.x (#12110)" (#12132)
This reverts commit 2190fa64a3.
2025-10-30 11:02:02 -05:00
Michelle Ark
785304732f add dbt/jsonschemas to recursive-include (#12126) 2025-10-28 16:56:50 -04:00
Quigley Malcolm
4693918a0f Drop python 3.9 support (#12120)
* Update `setup.py` to drop support for python 3.9

* Update github issue templates to not use python 3.9 as an example

* Update github workflows to no longer depend on or test python 3.9

* Drop python 3.9 from the test dockerfile

* Update `CONTRIBUTING.md` to correctly list what python versions we test

* Update comment about some code specifically needed for a python 3.9.7 issue

* Update pre-commit python version comment

* Add changie doc

* Update imports from click as upgrading to python 3.10 changed some click items
2025-10-28 15:10:03 -05:00
FishtownBuildBot
96738d5edc [Automated] Merged prep-release/1.11.0b4_18884421623 into target main during release process 2025-10-28 14:32:15 -04:00
Github Build Bot
780544dc7f Bumping version to 1.11.0b4 and generate changelog 2025-10-28 18:00:58 +00:00
William Deng
2190fa64a3 Upgrade DSI to 0.10.x (#12110) 2025-10-28 10:56:00 -04:00
Michelle Ark
deb2f3e890 move jsonschemas out of dbt/include (#12121) 2025-10-28 10:31:52 -04:00
Quigley Malcolm
34f0190a14 Support the parsing of python UDFs (#12111)
* Add test to check that python UDFs can be parsed

* Add `entry_point` and `runtime_version` to function node config

These two configs are required for python UDFs in some warehouses and
may also be required for other UDF languages moving forward. The specific
adapters implementation will enforce the requirement. By default both
configs will be `None` unless set.

* Begin searching for `.py` files in `functions` directory

* Switch to using `SimpleParser` for functions

Previously we were using `SimpleSQLParser` and we were _only_ parsing
SQL files. However, we're now also parsing python files for functions.
As such it makes sense to switch to the `SimpleParser`. Functionally there
is no change bceause we re-added the `parse_file` override that `SimpleSQLParser`
had (there was nothing sql specific about it). Hence this is mostly a
symbolic change.

* Add changie doc
2025-10-27 15:14:47 -05:00
Quigley Malcolm
7f9449660e Support setting function node configs from dbt_project.yml (#12104)
* Add test which checks that function nodes can be configured from dbt_project.yml

* Support setting function node configs from dbt_project.yml

* add changie doc

* Fix unit tests to expect `functions` as part of project
2025-10-22 16:55:28 -05:00
Quigley Malcolm
ea172aa668 Make function node type a config (#12102)
* Update function node tests to look for `type` on function config

* Update `function` node to have `type` on config

* Update parsing of `function` nodes to expect `type` on the config

* Add changie doc
2025-10-21 15:36:48 -05:00
Quigley Malcolm
f0d3b8a51d Allow for configuration of function volatility (#12100)
* Add test to check that a function's volatility is configurable

* Define the `FunctionVolatility` enum type

* Add `volatility` as a configuration on function nodes

* Add changie doc
2025-10-21 14:54:01 -05:00
Quigley Malcolm
c6afa4d0f2 Test that UDFs support sources (#12079) 2025-10-21 14:52:43 -05:00
Michelle Ark
98a1b6e272 fix unit test to accept param names without DBT_ENGINE_ prefix (#12099) 2025-10-21 10:22:25 -07:00
Quigley Malcolm
d5071fa135 Tidy first fix jsonschema deprecation warning tests (#12085)
* Ensure jsonschema validation tests aren't skipping validation because postgres isn't technically supported

* Blanket accept `functions` as top level yaml key as temp fix

We for the moment can't sync over the full jsonschema from fusion,
as such this is a stop gap simply so that we don't raise deprecation
warnings if people start specifying functions.

* Move model column `meta` and `tags` into the column's config in happy path fixture
2025-10-08 11:26:05 -05:00
FishtownBuildBot
db284a14c1 [Automated] Merged prep-release/1.11.0b3_18317430095 into target main during release process 2025-10-07 11:51:09 -04:00
Github Build Bot
4017802800 Bumping version to 1.11.0b3 and generate changelog 2025-10-07 15:19:13 +00:00
Quigley Malcolm
17a8816ee3 Fix using refs in function nodes (#12078)
* Test that functions can have static refs in them

* Ensure refs are properly populated on function nodes

* Add changie doc
2025-10-06 18:32:33 -05:00
Quigley Malcolm
3bd425fdc9 Upper case function status in logs (#12077)
* Uppercase function success status in logs

* Add changie doc
2025-10-06 18:09:12 -05:00
Quigley Malcolm
db9a6e10c1 Re-comment out test generation line that I uncommented and accidentally committed (#12070) 2025-10-03 09:26:03 -05:00
Quigley Malcolm
4a78a78c2b Ability to use functions in unit tests (#12068)
* Test that functions work properly when unit testing models

* Ensure that functions properly get propagated to the `manifest` and `depends_on` of the `unit_test` node

* Update comment about `RuntimeUnitTestFunctionResolver`

* Add changie doc
2025-10-02 19:06:43 -05:00
FishtownBuildBot
5ee5bf4129 [Automated] Merged prep-release/1.11.0b2_18206467188 into target main during release process 2025-10-02 18:19:23 -04:00
Github Build Bot
ac445ca1fd Bumping version to 1.11.0b2 and generate changelog 2025-10-02 21:46:28 +00:00
Quigley Malcolm
1258728d9a Fix rendering of function relaltions when filters are at play (#12067)
* Add test to ensure that using a function with `--empty` works

* Ensure relations for functions are created with a `type` set to `function`

Previously on creation of function relations we weren't passing a `type`
value. This was problematic because in dbt-adapters we call `is_function`
(which uses the relation `type`) to determine whether a relation can be
filtered when filtering options (like `empty` or `event_time`) are present.
Because `type` wasn't set for function relations, `is_function` would
return `False` and thus in the present of a filter, we would attempt to
filter it. This would raise an error because functions can't be filtered.
Setting the type on the relation solves the issue.

* add changie doc
2025-10-02 16:30:02 -05:00
Quigley Malcolm
15722264aa Correct Function Node Property Names (#12065)
* Fix function node property names

`return_type` -> `returns`
`return_type.type` -> `returns.data_type`
`arguments[x].type` -> `arguments[x].data_type`

* Add changie doc
2025-10-02 13:46:57 -05:00
FishtownBuildBot
a6d4091b6b [Automated] Merged prep-release/1.11.0b1_18172738761 into target main during release process 2025-10-01 15:41:43 -04:00
Github Build Bot
b1b3839715 Bumping version to 1.11.0b1 and generate changelog 2025-10-01 19:08:34 +00:00
Gerda Shank
963251df4e Fix schema file patch collection (#12055) 2025-09-29 12:34:32 -04:00
Quigley Malcolm
8c929c337e Add type property to function nodes (#12057)
* Add `FunctionType` enum

* Add `type` property to `Function` resource

* Add `type` property to `ParsedFunctionPatch` and `UnparsedFunctionUpdate`

* Begin populating a function's `type` during patch parsing

* Regnerate v12 manifest to include function `type` property

* Add changie doc

* Begin testing that function node `type` property is setable and accessible

* Move comment about triggering the PathEncoder back to its proper place
2025-09-26 15:29:08 -05:00
Michelle Ark
e949d1a6f9 Validate {{ config }} in SQL for models that don't statically parse (#12053) 2025-09-26 14:58:16 -04:00
Quigley Malcolm
538de17f78 Initial Implementation of UDFs (#12054)
* Allow for the defining of basic SQL UDFs (#11957)

* Add initial definiton of the `Function` resource

* Add FunctionNode definition to graph contracts

* Add test which checks whether basic UDFs can be parsed

This test fails right now, which is intentional. This is test driven
development. Now I do work to maket the test pass :)

* Add basic function sql parser for UDFs, and plumb it through parsing code paths

* Begin populating `functions` in the ref lookup

* Begin patching `function` nodes with their yaml definitions

Of note, presently `arguments` and `return_type` aren't populating properly.
It's likely that we'll have to do additional work to the FunctionPatchParser
to get this _fully_ working.

* Increase responsibility of FunctionPatchParser to handle entire `parse_patch` of function nodes

* Fix testing suite to accomodate addition of new `function` node

* Add changie doc for new `function` node type

* Minor refactoring of `NodePatchParser.parse_patch` to reduce code duplication in `FunctionPatchParser`

* Ability to list and select function nodes (#11968)

* Begin listing `function` nodes in `list` command

* Add ability to run `list` specifying the `function` resource type

* Function nodes are support selection via: name, file path, and resource type

* Add changie doc

* Core handles lifecycle of function nodes (#12008)

* Add basic test to check that UDFs get created in data warehouse

* Add functions to the runner map of \ operation

* Add basic stub of `FunctionRunner` modeled after `SeedRunner`

* Begin using `FunctionRunner` for running `function` nodes

* Add stubbing of things to implement on `FunctionRunner`

* Initial implementation of execution of function nodes

This is largely a copy of the execution of model nodes (in run.py) but
with some abstractions into helper methods to make the body of the
`execute` function easier to follow. Of note, right now this appears to
be getting the incorrect macro from the adapter. This is likely because
for some reason the node's materialization config is being set to `view`
by default.

* Ensure parsed function nodes get the correct materialization type

* Begin generating context for `function` materialization macro

* Stub out adapter response in node result as it was causing some failures

* Correct the adapter response in the run result for functions

* Begin logging `LogFunctionResult` event for completed function nodes

* Add changie doc

* Temp update dev reqs to point at branch of dbt-adapters

* Add test `LogFunctionResult` event to serialization test

* Add `function` nodes to the `WritableManifest`

* Fix tests

* Remove no longer relevant `TODO`s from `function.py`

* Add a new macro `function()` to the jinja context for using functions (#12031)

* Update function tests to look for `functions` under `manifest.functions`

* Begin storing funciton nodes in `Manifest.functions` instead of `Manifest.nodes`

* Ensure function nodes are still included in nodes to run during `build`

* Add ability to lookup functions on the manifest

* Update patch parsing of function YAML files now that functions live on `Manifest.functions`

* Mark function nodes as no longer refable

* Ensure function nodes are still selectable

* Add `function` macro!

* Ensure functions nodes are correctly linked in the DAG

* Update jinja context tests to expect `function` macro to exist

* Fix unit tests in test suite to expect function nodes

* Add changie doc

* regen v12.json jsonschema

* Fix test `TestVerifyArtifacts::test_run_and_generate`

* Fix test `TestVerifyArtifactsReferences::test_references`

* Fix test `TestVerifyArtifactsVersions::test_versions`

* Regen manifest artifact for `TestPreviousVersionState::test_compare_state_current`

* Update `_iterate_selected_nodes` to support function nodes

* Ensure we process node functions to ensure they get added to the `depends_on`

* Take functions into account for state modified

* Regen data for `TestModifiedStateSchemaEvolution::test_modified_state_schema_evolution` test

* Default `functions` property on `WritableManifest` to a dict

I'm not sure if this is actually how we want to do this. However, without
doing this the `WritableManifest` will break on loading of older manifests
that don't have `functions`. The alternative to this would be to bump
the schema version (v12 -> v13) and create an upgrade in `upgrade_manifest.py`.

* Update UDF tests to use a more general purpose function

* Add tests ensuring UDFs can be used in models and `--inline` queries

* Correct `ParseFunctionResolver` so that the name isn't added twice to the function args spec

* Drop `functions` from `Exposure` and `Metric` definitions

* Regen v12 manifest schema

* Remove unnecessary string interpolation

* Point dev reqs back to dbt-adapters@main

* Empty commit
2025-09-26 13:41:45 -05:00
Emily Rockman
96c9d80f83 fix matrix for windows CI (#12052)
* fix matrix

* skip flaky test on windows
2025-09-25 15:06:45 -04:00
Michelle Ark
2f842055f0 Add run_started_at to manifest.json metadata (#12047) 2025-09-25 11:56:12 -04:00
Quigley Malcolm
faeee357b1 Fix script for setting up postgres development database (#12035)
* Increase shared memory size for postgres docker container

I recently started getting errors that look like
```
E           dbt_common.exceptions.base.DbtDatabaseError: Database Error
E             could not resize shared memory segment "/PostgreSQL.3814850474" to 2097152 bytes: No space left on device
```
At first I thought this was a lack of memory, disk space, or ulimit file descriptors. However
increasing all of those things did not solve the problem. I eventually found, by exec-ing into
the container and running `df -h /dev/shm && ls -lh /dev/shm` that the container only had 64MB
of memory available to it. This change increases the memory available to the container to 1GB,
which resolved the issue.

* Use `docker compose` instead of `docker-compose`

The later was docker v1, and no longer works. Use `docker compose` instead.

* Only run homebrew postgres in `setup_db.sh` if `SKIP_HOMEBREW` is not passed

Our github actions use homebrew, but our local dev uses docker. When we
were doing local development and running `make setup-db` suddenly there would
be _two_ postgres instances running. One via homebrew, and another in docker.
This was breaking the setup. Now when running `make setup-db` we skip the
homebrew relevant portions of `setup_db.sh`.

* Set more PG environment variables in `setup_db.sh`
2025-09-19 15:39:08 -05:00
Michelle Ark
bca5c4d454 Support configuring model.config.freshness.build_after.update_on without period or count (#12027) 2025-09-18 13:27:51 -04:00
Courtney Holcomb
b3d059e427 Fix legacy time spine deprecation warning logic (#12018) 2025-09-17 11:06:33 -04:00
Michelle Ark
b783c97eff propagate meta/tags on columns to top-level and config (#11992) 2025-09-15 14:53:11 -04:00
Michelle Ark
5add25db0c Guarantee instantiation result and thread_exception prior to access (#12013) 2025-09-12 15:34:34 -04:00
Michelle Ark
ad6ea20277 skip initial render of loaded_at_query when specified as config (#12010) 2025-09-12 12:53:58 -04:00
Michelle Ark
472b8057a9 manifest schema upgrade framework for state:modified (#11945) 2025-09-11 13:27:01 -04:00
Michelle Ark
2915c3e284 dbt-semantic-interfaces>=0.9.0 (#12005) 2025-09-11 10:11:41 -04:00
Michelle Ark
537daa8476 Add catalogs.yml parsing to parse, test, and snapshot (#12002) 2025-09-10 15:01:48 -04:00
dependabot[bot]
b48ad8282b Bump actions/setup-python from 5 to 6 (#11993)
* Bump actions/setup-python from 5 to 6

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Add automated changelog yaml from template for bot PR

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Github Build Bot <buildbot@fishtownanalytics.com>
2025-09-10 10:42:21 -04:00
Michelle Ark
7cab753863 implement checked_agg_time_dimension_for_simple_metric on SemanticModel (#11995) 2025-09-10 09:57:22 -04:00
Emily Rockman
19393a8080 build git from source (#11991) 2025-09-08 14:00:28 -04:00
Taylor Brudos
1e61e3bfc6 [SECENG-10952] Add OpenSSF badge to README (#11994) 2025-09-08 10:41:11 -05:00
Michelle Ark
a9dae5cac1 update to latest jsonschemas (#11987) 2025-09-04 10:04:55 -04:00
Aksel Stokseth
15010f1e6b Support quoting.snowflake_ignore_case (#11942) 2025-09-03 17:33:22 -04:00
Michelle Ark
2564b3d1f9 Restore record functional tests (#11979) 2025-09-03 10:28:30 -04:00
Pablo Martín Calvo
34bb3f94dd fix: Properly quote event_time column names in sample mode filters (#11859)
* fix: Properly quote event_time column names in sample mode filters

When using the --sample flag with models that have camel case or
spaced column names as their event_time field, the generated SQL
would fail because column names weren't properly quoted.

This fix introduces a robust quoting system that:
- Checks column-level quote configuration first (highest precedence)
- Falls back to source-level quoting settings
- Uses the existing Column class for proper quote handling
- Centralizes the logic in a dedicated method to eliminate duplication
- Ensures sample mode works with PostgreSQL and other databases that
  require quoted identifiers for column names with spaces or special characters

Fixes issue where --sample flag fails with camel case or spaced
event_time column names.

* returning the same path that was used earlier for the event_time filed

* adding changelog

* verify cla agreement

* test: Add comprehensive tests for _resolve_event_time_field_name method

This commit adds extensive test coverage for the _resolve_event_time_field_name
method to address the PR review feedback requesting tests.

Changes:
- Add 28 parametrized test cases covering all quoting scenarios
- Test column-level vs source-level quote precedence
- Test edge cases: missing columns, empty columns dict, no quoting attributes
- Test camel case, snake case, and spaced column names
- Test both quoted and unquoted column name scenarios
- Improve method robustness with better error handling

The tests ensure the method correctly handles:
- Column-level quote settings taking precedence over source-level
- Proper fallback to source-level quoting when column-level is not set
- Edge cases where columns don't exist or have no quoting attributes
- Various column name formats (simple, camelCase, snake_case, spaced)

Fixes: Addresses PR review feedback requesting comprehensive test coverage

* style: Apply code formatting from pre-commit hooks

- Apply black formatting to providers.py and test_providers.py
- Fix trailing whitespace issues
- Add proper type guards for event_time attribute access
- Ensure all tests continue to pass after formatting changes
2025-08-27 15:34:19 -05:00
Quigley Malcolm
593a151220 Unhide sample mode CLI flag (#11960) 2025-08-27 14:10:32 -05:00
Quigley Malcolm
1a251ee081 [Tidy First] Don't allow for the direct import of versioned artifact resources in dbt-core's modules (#11952)
* Create custom hook for checking for improper imports of artifact resources

* Fix return value of `has_bad_artifact_resource_imports.py::main`

* Regex match versioned resource imports and give import in pre-commit error

* (Tidy First): Fix imports of artifact resources to not import direct versioned resources

* Add changie doc
2025-08-25 09:48:49 -05:00
Michelle Ark
9b7cf25c33 Add path MissingArgumentsPropertyInGenericTestDeprecation message (#11940) 2025-08-21 13:17:53 +02:00
Tyler Rouze
26333f7f21 feat: support nested key traversal in dbt list output (#11920)
* feat: support nested key traversal in dbt list output

* feat: support nested key traversal in dbt list output

* feat: support nested key traversal in dbt list output

* feat: support nested key traversal in dbt list output

* feat: support nested key traversal in dbt list output

* feat: support nested key traversal in dbt list output

* feat: support nested key traversal in dbt list output
2025-08-20 07:30:03 -05:00
Quigley Malcolm
9bc7333e19 Fix Dockerfile used for dockerized release image (#11937)
* Update version for libpq-dev in Dockerfile

The previous version we had for libpq-dev stopped being listed. As such
we need to change to installing a version that is still listed. Hence
we now install version 13.22-0+deb11u1

* Fix `FromAsCasing` warning in Docker file

Our docker file was raising the warning
`FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 27)`
because we were using `FROM` and `as`, and docker wants those words
to have the same casing. As such, the `as` instances have become `AS`.

* Add changie doc
2025-08-19 12:49:55 -05:00
Quigley Malcolm
ee8884731b Bump dbt adapters minimum to 1.16.5 (#11933)
* Bump `dbt-adapters` minimum to `1.16.5`

* Add changie doc
2025-08-19 09:25:30 -05:00
Colin Rogers
f1106ad61e upgrade protobuf to 6.0 (#11916)
* upgrade protobuf to 6.0

* upgrade protobuf to 6.0

* signing commits

* update dev-requirements.txt

* add changelog
2025-08-13 17:28:33 -05:00
Michelle Ark
ada5d3b82a flip require_generic_test_arguments_property behavior change flag (#11911)
* flip require_generic_test_arguments_property

* fix deprecations functional tests

* fix test_modified_state

* fix retry project

* changelog entry

* improve changelog
2025-08-11 15:09:42 -04:00
Adolfo Rodriguez
64b58ec628 Default parse-time nodes' raw_code property to "" (empty string) to comply with strict str type (#11884)
* Default parse-time nodes' raw_code property to "" (empty string) to comply with strict str type

* Changelog entry

---------

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>
2025-08-08 12:49:03 -04:00
Adolfo Rodriguez
1e713db2fa Avoid redundant node patch removal during partial parsing (#11887)
* Avoid redundant node patch removal during partial parsing

* Changelog entry

---------

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>
2025-08-08 12:48:51 -04:00
Emily Rockman
6b7b1ad74b dont use default token (#11895)
* dont use default token

swap token

* use base sha, fix status
2025-08-08 11:37:12 -04:00
Colin Rogers
3e31117ba1 call adapter.add_catalog_integration during parse_manifest (#11889)
* call adapter.add_catalog_integration during parse_manifest

* formatting

* updat retry.py

* add changie

* remove unneeded call to adapter.add_catalog_integrations
2025-08-06 10:12:45 -07:00
Michelle Ark
451b745aea Remove duplicative PropertyMovedToConfigDeprecation source freshness config move (#11880) 2025-08-05 11:10:41 -04:00
Michelle Ark
d27232a946 Raise PropertyMovedToConfigDeprecation instead of CustomTopLevelKeyDeprecation when additional attribute is a valid config (#11879) 2025-08-05 09:57:07 -04:00
Michelle Ark
b1705fb6f3 add argument values to ArgumentsPropertyInGenericTestDeprecation message (#11872) 2025-07-29 11:05:30 -04:00
Michelle Ark
0e50851fa6 deprecate modules.itertools usage (#11734) 2025-07-29 10:12:57 -04:00
Taylor Brudos
b75d5e701e Update contributing guide for signed commits requirement (#11857) 2025-07-24 13:41:00 -06:00
Michelle Ark
f8b1a6dcd1 safer handling of pre-existing config.tags on sources/tables (#11854) 2025-07-24 11:05:32 -04:00
Michelle Ark
9010537499 accept generic test args under 'args' (#11840) 2025-07-21 18:08:01 -04:00
Michelle Ark
56d3c9318b config.meta and config.tags propagation to top-level on sources and tables (#11839) 2025-07-21 14:22:01 -04:00
Michelle Ark
1fcce443ba raise MissingPlusPrefixDeprecation when missing plus-prefix, otherwise skip type-related issues in dbt_project.yml (#11825) 2025-07-15 12:41:02 -04:00
Quigley Malcolm
de03d6f44f Gate jsonschema validations by adapter support (#11828) 2025-07-15 09:55:00 -04:00
Quigley Malcolm
5db78ca6dd Some additional SL JSONSchema improvements/fixes (#11821)
* Pull in latest jsonschemas, primarily for improved SL definitions

* Improve metric definitions in happy path test fixture to be more expansive

* Add changie doc

* Fix test_list to know about new happy path fixture metrics
2025-07-14 16:12:29 -07:00
Michelle Ark
ada9e63c13 skip type checking deprecation_date on the basis of jsonschemas (#11823)
* skip type checking deprecation_date on the basis of jsonschemas

* update test
2025-07-14 16:12:15 -07:00
Colin Rogers
69d19eb5fc add merge_group event (#11824) 2025-07-14 15:09:47 -07:00
Michelle Ark
55bb3c304a update semantic layer jsonschemas (#11817) 2025-07-11 11:58:53 -04:00
Quigley Malcolm
693564de40 Make GenericJSONSchemaValidationDeprecation a "preview" deprecation (#11815)
* Make `GenericJSONSchemaValidationDeprecation` a "preview" deprecation

Making the deprecation a preview will:
1. Remove it from the summary
2. Emit it as a Note event instead of the actual deprecation event
  a. This does mean it'll still be in the logs (but as info level instead of warning)

* Update message of `GenericJSONSchemaValidationDeprecation` to state it's only possibly a deprecation

* Add changie doc

* fix GenericJSONSchemaValidationDeprecation related tests

* Add more details to `GenericJSONSchemaValidationDeprecation` message

* Fix tests related to GenericJSONSchemaValidationDeprecation
2025-07-11 09:27:07 -05:00
Quigley Malcolm
04a3df7324 Create and protect dbt engine environment variable namespace via prefix DBT_ENGINE (#11795)
* Bump dbt-protos dep min to get new env var namespace deprecation event

* Define new EnvironmentVariableNamespaceDeprecation event in core

* Add new deprecation class for EnvironmentVariableNamespaceDeprecation

* Bump dbt-common dep min to get new env var prefix definiton

* Add new `env_vars` module with function for validating dbt engine env vars

* Add changie doc

* Begin keeping a list of env vars associated with cli params

* Begin validating that only allowed engine environment variables exist

* Add some extra engine env vars found throughout the project to the known list

* Begin cross propagating dbt engine env vars with old names

If the old env var name is present, and the new one is not, set the
new one to have the value of the old one. Else, if the new one is set,
set/override old name to have the value of the new one.

There are some drawbacks to this approach. Namely, click only validates
environment variable types for the environment variables it is aware of.
Thus by using the new environment variable naming scheme for existing
environment variables (not newly added ones), we actually lose type guarantees.
This might require a rework.

* Add test for validate_engine_env_vars method

* Add unit test ensuring new engine env vars get added correctly

* Add integration test for environment variable namespace deprecation

* Move logic for propagating engine env vars to pre-flight env var setting

Previously we were attempting to set it on the flags context, but that is
_not_ the environment variable context. Instead what appears to happen is
that the environment variable context is loaded, click takes this into
consideration, and then the flags are set from click's understanding of
passed cli params + env vars.

* Get the env vars from the invocation context in `validate_engine_env_vars`

* Move `_create_engine_env_var` to `__init__` of `EngineEnvVar` data class

* Fix error type in __init__ of EngineEnvVar dataclass

* Correct grammar of EnvironmentVariableNamespaceDeprecation message
2025-07-10 13:33:37 -05:00
Courtney Holcomb
31d974f5eb Upgrade to DSI 0.9.0 for more robust saved query support (#11808)
* Upgrade to DSI 0.9.0

Note this new version has some breaking changes (changes to class names). This won't impact semantic manifest parsing. The changes in the new version will be used to support order_by and limit on saved queries.

* Changelog

* Update test saved query
2025-07-09 16:32:16 -05:00
Quigley Malcolm
c1f64e216f Move source overrides deprecation to jsonschema (#11801)
* Improve deprecation message for SourceOverrideDeprecation

* Move SourceOverrideDeprecation to jsonschema validation code path

* Update test for checking SourceOverrideDeprecation
2025-07-07 15:37:26 -05:00
Quigley Malcolm
8fa6e037d0 Update json schemas (#11800)
* Update dbt_project.yml jsonschema spec to handle nested config defs

Additionally adds some more cloud configs

* Update schema files jsonschema definition to not have `overrides` for sources

Additionally add some cloud definitions

* Add changie doc

* Update happy_path fixture to include nested config specifations in dbt_project.yml
2025-07-07 15:11:55 -05:00
Michelle Ark
e1c98e8123 debug log when node.schema is set to None (#11797) 2025-07-07 11:19:36 -04:00
Michelle Ark
9955ea760a update ModelParamUsageDeprecation message (#11793) 2025-07-02 20:18:00 -04:00
Quigley Malcolm
fdd0546700 Bump dbt-common minimum to 1.25.1 (#11790) 2025-07-02 15:02:42 -05:00
Taylor Brudos
45f21a7cda Update contributing guide for comment resolution requirement (#11787) 2025-07-02 09:53:00 -05:00
Peter Webb
f250b503d5 Source Override Deprecation (#11636)
* First draft of SourceOverrideDeprecation warning.

* Refinements and test

* Back out unneeded change`

* Fix unit test.

* add changie doc

* Bump minimum dbt-protos to 1.0.335

---------

Co-authored-by: Quigley Malcolm <quigley.malcolm@dbtlabs.com>
2025-07-01 17:08:41 -05:00
GarmashAlex
aa42ff8986 Fix broken adapters documentation link in ARCHITECTURE.md (#11777) 2025-06-30 16:33:11 -05:00
Quigley Malcolm
3c2fdfe735 Stop dynamically setting ubuntu version for main.yml and structured logging actions (#11783)
* Stop dynamically setting ubuntu version for `main.yml` and structured logging actions

These actions are important to run on community PRs. However these workflows
use `on: pull_request` instead of `on: pull_request_target`. That is intentional,
as `on: pull_request` doesn't give access to variables or secrets, and we need
to keep it that way for security purposes. The these actions were trying to access
a variable, which they don't have access to. This was a nicety for us, because
sometimes we'd delay moving to github's `ubuntu-latest`. However, the security
concern is more important, and thus we lose the variable for these workflows.

* Change `runs_on` of `artifact-reviews.yml`

* Stop dynamically setting mac and windows versions in main.yml
2025-06-30 14:34:46 -05:00
Colin Rogers
303c63ccc8 use rename instead of select api for normalizing agate table column casing (#11778)
* Revert "bump dbt-common (#11640)"

This reverts commit c6b7655b65.

* update freshness model config handling

* lower case all columns when processing unit test results

* add changelog

* swap .columns for .column_names

* use rename instead of select api for normalizing agate table column casing
2025-06-26 10:56:48 -07:00
Michelle Ark
17ec11ad30 bring in latest properties yaml and dbt project yaml jsonschemas (#11765) 2025-06-26 12:09:35 -04:00
Quigley Malcolm
65598f3dc6 Validate model sql file provided config (#11768)
* Add helper to validate model configs via jsonschema

* Store jsonschemas as module vars instead of reloading everytime

Every time we were calling a jsonschema validation, we were _reloading_
from file the underlying jsonschema. As a one off, this isn't too costly.
However, for large projects it starts to add up. By only loading each json
schema once we can save a lot of time. Calling one of the functions which
loads a jsonschema 10,000 times was costing ~3.7215 seconds. By switching
to this module var paradigm we reduced that to ~0.3743 seconds.

* Begin validating configs from model `.sql` files

It was a bit of a hunt to figure out where to do this. We couldn't do
the validating in `calculate_node_config` because that function is called
4 times per node (which is an issue of itself, but out of scope for this
work). We also couldn't do the validation where `_config_call_dict` is set
because it turns out there are multiple avenues for setting
`_config_call_dict`, which is a fun rabbit hole.

* Ensure .sql configs are validated only once

It turns out that that `update_parsed_node_config` can potentially be
called twice per model. It'll be called from either `ModelParser.render_update`
or `ModelParser.populate`, and it can additionally be called from
`PatchParser.patch_node_config` if there is a .yml definition for the
model. We only want to validate the config once, and we aren't guaranteed
to have a `PatchParser` if there is no patch for the model. Thus, we've
updated `ModelParser.populate` and `ModelParser.render_update` to
request the config validation (which by default doesn't run unless requested).

* Split out the model config specific validation from general jsonschema validation

We're validating model configs from sql files via a subschema of the main
resources jsonschema, different case logic for detecting the different
types of deprecation warnings present. Thus `validate_model_config` cannot
call `jsonschema_validate`. We could have had both logic paths exist in
`jsonschema_validate`, but it would have added another later of if/elses
and bloated the function substantially.

* Handle additional properties of sub config objects

* Give better key path information for .sql config jsonschema issues

* Add tests for validate_model_config

* Add changie doc

* Fix jsonschemas unittests to avoid catching irrelevant issues
2025-06-25 16:50:43 -05:00
Colin Rogers
240a6056fb Handle upper cased unit test results (#11769)
* Revert "bump dbt-common (#11640)"

This reverts commit c6b7655b65.

* update freshness model config handling

* lower case all columns when processing unit test results

* add changelog

* swap .columns for .column_names
2025-06-25 08:47:51 -07:00
Michelle Ark
7cd8935b13 skip health check on flaky test (#11767) 2025-06-24 15:51:59 -04:00
Michelle Ark
cd5d4be7ab bump dbt-common to 1.25.0 to access WarnErrorOptionsV2 (#11762) 2025-06-24 09:58:34 -04:00
Michelle Ark
5a23894584 add loaded_at_query and loaded_at_field to SourceConfig (#11759) 2025-06-23 16:58:30 -04:00
Quigley Malcolm
70ad9319d2 Bring back pydantic 2 support (#11756)
* Loosen pydantic maximum to <3 (allowing for pydantic 2)

* Add an internal pydantic shim for getting pydantic BaseSettings reguardless of pydantic v1 vs v2

* Add changie doc
2025-06-20 13:34:10 -05:00
Michelle Ark
8873581c5a bring in latest properties yaml and dbt project yaml jsonschemas (#11745) 2025-06-17 16:06:55 -04:00
Quigley Malcolm
1ffd059442 Bump minimum jsonschema version to 4.19.1 (#11741)
In 1.10.0 we began utilizing `jsonschema._keywords`. However, the submodule
`_keywords` wasn't added until jsonschema `4.19.1` which came out September
20th, 2023. Our jsonschema requirement was being set transitively via
dbt-common as `>=4.0,<5`. This mean people doing a _non_ fresh install of
dbt-core `1.10.0` could end up with a broken system if their existing
jsonschema dependency was anywhere in the range `>=4.0,<4.19.1`. By bumping the
minimum jsonschema version we make it such that anyone install dbt-core 1.10.1 will
automatically get there jsonschema updated (assuming they don't have an exclusionary
pin)
2025-06-16 15:00:47 -05:00
Quigley Malcolm
091ba5fe0b drop model freshness as top level model property (in favor of config freshness) (#11731)
* Begin testing that model freshness can't be set as a top level model property

* Remove ability to specify freshness as top level property of models

* Add come comments to calculate_node_config for better readability

* Drop `freshness` as a top level property of models, and let `patch_node_config` handle merging config freshness

Model freshness hasn't been released in a minor release yet, not been documented. Thus
it is safe to remove the top level property of freshness on models. Freshness will instead
be set, and gotten, from the model config. Additionally our way of calculating the
config model freshness only got the top level `+freshness` from dbt_project.yml (ignoring
any path specific definitions). By instead using the built in `calculate_node_config` (which
is eventually called by `patch_node_config`), we get all path specific freshness config handling
and it also handles the precedence of `dbt_project.yml` specification, schema file specification,
and sql file specification.

* add changie doc
2025-06-16 09:18:42 -05:00
Michelle Ark
6bbcce1f1c deprecation warnings for --models, --model, -m (#11729) 2025-06-16 10:02:37 -04:00
Michelle Ark
0fff5760ff move TestConfig.post_init logic to finalize_and_validate to respect hierarchical configs (#11730) 2025-06-12 17:01:45 -04:00
Colin Rogers
f4988c62e3 handle inline model freshness config (#11728)
* Revert "bump dbt-common (#11640)"

This reverts commit c6b7655b65.

* update freshness model config handling

* make sure ModelConfig.__pre_deserialize__ returns all nested items as dicts

* add changie
2025-06-11 08:56:30 -07:00
Quigley Malcolm
2e6d4f493d Ensure source node .freshness is equal to node's .config.freshness (#11719)
* Ensure source node `.freshness` is equal to node's `.config.freshness`

* Default source config freshness to empty spec if no freshenss spec is given

* Update contract tests for source nodes
2025-06-10 09:59:22 -05:00
Quigley Malcolm
3e593600e0 Ensure build_after is present in model freshness in parsing, otherwise skip freshness definition (#11711)
* Ensure `build_after` is present in model freshness in parsing, otherwise skip freshness definition

* add freshness model config test

* add changelog

---------

Co-authored-by: Colin <colin.rogers@dbtlabs.com>
2025-06-05 15:02:17 -05:00
Quigley Malcolm
87584c73b0 Fix null handling of source freshness and dbt_project vs schema spec precedence (#11698)
* Handle explicit setting of null for source freshness config

* Abstract out the creation of the target config

This is useful because it makes that portion of code more re-usable/portable
and makes the work we are about to do easier.

* Fix bug in `merge_source_freshness` where empty freshness was preferenced over `None`

The issue was that during merging of freshnesses, an "empty freshness", one
where all values are `None`, was being preferenced over `None`. This was
problematic because an "empty freshness" indicates that a freshness was not
specified at that level. While `None` means that the freshness was _explicitly_
set to `None`. As such we should preference the thing that was specifically set.

* Properly get dbt_project defined freshness and don't merge with schema defined freshness

Previously we were only getting the "top level" freshness from the
dbt_project.yaml. This was ignoring freshness settings for the direct,
source, and table set in the dbt_project.yaml. Additionally, we were
merging the dbt_project.yaml freshness into the schema freshness. Long
term this merging would be desireably, however before we do that we need
to ensure freshness at diffrent levels within the dbt_project.yml get
properly merged (currently the different levels clobber each other). Fixing
that is a larger issue though. So for the time being, the schema defintion
of freshness will clobber any dbt_project.yml definition of freshness.

* Add changie doc

* Fix whitespace to make code quality happy

* Set the parsed source freshness to an empty FreshnessThreshold if None

This maintains backwards compatibility
2025-05-30 10:31:40 -05:00
4472 changed files with 42222 additions and 78290 deletions

View File

@@ -1,37 +0,0 @@
[bumpversion]
current_version = 1.11.0a1
parse = (?P<major>[\d]+) # major version number
\.(?P<minor>[\d]+) # minor version number
\.(?P<patch>[\d]+) # patch version number
(?P<prerelease> # optional pre-release - ex: a1, b2, rc25
(?P<prekind>a|b|rc) # pre-release type
(?P<num>[\d]+) # pre-release version number
)?
( # optional nightly release indicator
\.(?P<nightly>dev[0-9]+) # ex: .dev02142023
)? # expected matches: `1.15.0`, `1.5.0a11`, `1.5.0a1.dev123`, `1.5.0.dev123457`, expected failures: `1`, `1.5`, `1.5.2-a1`, `text1.5.0`
serialize =
{major}.{minor}.{patch}{prekind}{num}.{nightly}
{major}.{minor}.{patch}.{nightly}
{major}.{minor}.{patch}{prekind}{num}
{major}.{minor}.{patch}
commit = False
tag = False
[bumpversion:part:prekind]
first_value = a
optional_value = final
values =
a
b
rc
final
[bumpversion:part:num]
first_value = 1
[bumpversion:part:nightly]
[bumpversion:file:core/setup.py]
[bumpversion:file:core/dbt/version.py]

View File

@@ -0,0 +1,6 @@
kind: Dependencies
body: Use EventCatcher from dbt-common instead of maintaining a local copy
time: 2025-11-18T15:53:54.284561+05:30
custom:
Author: 3loka
Issue: "12124"

View File

@@ -1,6 +0,0 @@
kind: Features
body: Add file_format to catalog integration config
time: 2025-05-29T08:53:11.64904-07:00
custom:
Author: colin-rogers-dbt
Issue: "11695"

View File

@@ -0,0 +1,6 @@
kind: Features
body: Support partial parsing for function nodes
time: 2025-10-06T14:03:52.258104-05:00
custom:
Author: QMalcolm
Issue: "12072"

View File

@@ -0,0 +1,6 @@
kind: Features
body: Allow for defining funciton arguments with default values
time: 2025-11-17T14:10:53.860178-06:00
custom:
Author: QMalcolm
Issue: "12044"

View File

@@ -0,0 +1,6 @@
kind: Features
body: Raise jsonschema-based deprecation warnings by default
time: 2025-12-01T16:52:09.354436-05:00
custom:
Author: michelleark
Issue: 12240

View File

@@ -0,0 +1,6 @@
kind: Features
body: ':bug: :snowman: Disable unit tests whose model is disabled'
time: 2025-12-03T12:29:26.209248-05:00
custom:
Author: michelleark
Issue: "10540"

View File

@@ -0,0 +1,6 @@
kind: Features
body: Implement config.meta_get and config.meta_require
time: 2025-12-10T20:20:01.354288-05:00
custom:
Author: gshank
Issue: "12012"

View File

@@ -1,6 +0,0 @@
kind: Fixes
body: Don't warn for metricflow_time_spine with non-day grain
time: 2025-05-28T09:20:55.866514-07:00
custom:
Author: courtneyholcomb
Issue: "11690"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Address Click 8.2+ deprecation warning
time: 2025-09-22T15:17:26.983151-06:00
custom:
Author: edgarrmondragon
Issue: "12038"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Include macros in unit test parsing
time: 2025-11-17T14:06:49.518566-05:00
custom:
Author: michelleark nathanskone
Issue: "10157"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Allow dbt deps to run when vars lack defaults in dbt_project.yml
time: 2025-11-17T18:50:25.759091+05:30
custom:
Author: 3loka
Issue: "8913"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Restore DuplicateResourceNameError for intra-project node name duplication, behind behavior flag `require_unique_project_resource_names`
time: 2025-11-18T17:11:06.454784-05:00
custom:
Author: michelleark
Issue: "12152"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Allow the usage of `function` with `--exclude-resource-type` flag
time: 2025-11-19T19:50:34.703236-06:00
custom:
Author: QMalcolm
Issue: "12143"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Fix bug where schemas of functions weren't guaranteed to exist
time: 2025-11-24T15:56:29.467004-06:00
custom:
Author: QMalcolm
Issue: "12142"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Fix generation of deprecations summary
time: 2025-11-24T15:57:56.544123-08:00
custom:
Author: asiunov
Issue: "12146"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: ':bug: :snowman: Correctly reference foreign key references when --defer and --state provided'
time: 2025-11-24T17:08:55.387946-05:00
custom:
Author: michellark
Issue: "11885"

View File

@@ -0,0 +1,7 @@
kind: Fixes
body: ':bug: :snowman: Add exception when using --state and referring to a removed
test'
time: 2025-11-25T12:02:46.635026-05:00
custom:
Author: emmyoop
Issue: "10630"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: ':bug: :snowman: Stop emitting `NoNodesForSelectionCriteria` three times during `build` command'
time: 2025-11-25T12:20:20.132379-06:00
custom:
Author: QMalcolm
Issue: "11627"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: ":bug: :snowman: Fix long Python stack traces appearing when package dependencies have incompatible version requirements"
time: 2025-11-27T14:13:08.082542-05:00
custom:
Author: emmyoop
Issue: "12049"

View File

@@ -0,0 +1,7 @@
kind: Fixes
body: ':bug: :snowman: Fixed issue where changing data type size/precision/scale (e.g.,
varchar(3) to varchar(10)) incorrectly triggered a breaking change error fo'
time: 2025-11-27T14:59:29.256274-05:00
custom:
Author: emmyoop
Issue: "11186"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: ':bug: :snowman: Support unit testing models that depend on sources with the same name'
time: 2025-11-27T17:01:24.193516-05:00
custom:
Author: michelleark
Issue: 11975 10433

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Fix bug in partial parsing when updating a model with a schema file that is referenced by a singular test
time: 2025-11-28T10:21:29.911147Z
custom:
Author: mattogburke
Issue: "12223"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: ':bug: :snowman: Avoid retrying successful run-operation commands'
time: 2025-11-28T12:28:38.546261-05:00
custom:
Author: michelleark
Issue: "11850"

View File

@@ -0,0 +1,7 @@
kind: Fixes
body: ':bug: :snowman: Fix `dbt deps --add-package` crash when packages.yml contains `warn-unpinned:
false`'
time: 2025-11-28T16:19:37.608722-05:00
custom:
Author: emmyoop
Issue: "9104"

View File

@@ -0,0 +1,7 @@
kind: Fixes
body: ':bug: :snowman: Improve `dbt deps --add-package` duplicate detection with better
cross-source matching and word boundaries'
time: 2025-11-28T16:31:44.344099-05:00
custom:
Author: emmyoop
Issue: "12239"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: ':bug: :snowman: Fix false positive deprecation warning of pre/post-hook SQL configs'
time: 2025-12-02T13:37:05.012112-05:00
custom:
Author: michelleark
Issue: "12244"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Ensure recent deprecation warnings include event name in message
time: 2025-12-09T17:50:31.334618-06:00
custom:
Author: QMalcolm
Issue: "12264"

View File

@@ -0,0 +1,6 @@
kind: Fixes
body: Improve error message clarity when detecting nodes with space in name
time: 2025-12-10T14:39:35.107841-08:00
custom:
Author: michelleark
Issue: "11835"

View File

@@ -1,6 +0,0 @@
kind: Under the Hood
body: Prevent overcounting PropertyMovedToConfigDeprecation for source freshness
time: 2025-05-27T16:21:36.551426+01:00
custom:
Author: aranke
Issue: "11660"

View File

@@ -0,0 +1,6 @@
kind: Under the Hood
body: Update jsonschemas for schema.yml and dbt_project.yml deprecations
time: 2025-11-19T11:01:10.616676-05:00
custom:
Author: michelleark
Issue: "12180"

View File

@@ -0,0 +1,6 @@
kind: Under the Hood
body: Replace setuptools and tox with hatch for build, test, and environment management.
time: 2025-11-21T14:05:15.838252-05:00
custom:
Author: emmyoop
Issue: "12151"

View File

@@ -0,0 +1,6 @@
kind: Under the Hood
body: Add add_catalog_integration call even if we have a pre-existing manifest
time: 2025-12-09T13:18:57.043254-08:00
custom:
Author: colin-rogers-dbt
Issue: "12262"

View File

@@ -52,21 +52,15 @@ custom:
footerFormat: | footerFormat: |
{{- $contributorDict := dict }} {{- $contributorDict := dict }}
{{- /* ensure all names in this list are all lowercase for later matching purposes */}} {{- /* ensure we always skip snyk and dependabot */}}
{{- $core_team := splitList " " .Env.CORE_TEAM }} {{- $bots := list "dependabot[bot]" "snyk-bot"}}
{{- /* ensure we always skip snyk and dependabot in addition to the core team */}}
{{- $maintainers := list "dependabot[bot]" "snyk-bot"}}
{{- range $team_member := $core_team }}
{{- $team_member_lower := lower $team_member }}
{{- $maintainers = append $maintainers $team_member_lower }}
{{- end }}
{{- range $change := .Changes }} {{- range $change := .Changes }}
{{- $authorList := splitList " " $change.Custom.Author }} {{- $authorList := splitList " " $change.Custom.Author }}
{{- /* loop through all authors for a single changelog */}} {{- /* 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-bot contributors */}}
{{- if not (has $authorLower $maintainers)}} {{- if not (has $authorLower $bots)}}
{{- $changeList := splitList " " $change.Custom.Author }} {{- $changeList := splitList " " $change.Custom.Author }}
{{- $IssueList := list }} {{- $IssueList := list }}
{{- $changeLink := $change.Kind }} {{- $changeLink := $change.Kind }}

View File

@@ -10,6 +10,5 @@ ignore =
E704 # makes Flake8 work like black E704 # 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/
per-file-ignores = per-file-ignores =
*/__init__.py: F401 */__init__.py: F401

View File

@@ -62,7 +62,7 @@ body:
description: | description: |
examples: examples:
- **OS**: Ubuntu 24.04 - **OS**: Ubuntu 24.04
- **Python**: 3.9.12 (`python3 --version`) - **Python**: 3.10.12 (`python3 --version`)
- **dbt-core**: 1.1.1 (`dbt --version`) - **dbt-core**: 1.1.1 (`dbt --version`)
value: | value: |
- OS: - OS:

View File

@@ -56,7 +56,7 @@ body:
description: | description: |
examples: examples:
- **OS**: Ubuntu 24.04 - **OS**: Ubuntu 24.04
- **Python**: 3.9.12 (`python3 --version`) - **Python**: 3.10.12 (`python3 --version`)
- **dbt-core (working version)**: 1.1.1 (`dbt --version`) - **dbt-core (working version)**: 1.1.1 (`dbt --version`)
- **dbt-core (regression version)**: 1.2.0 (`dbt --version`) - **dbt-core (regression version)**: 1.2.0 (`dbt --version`)
value: | value: |

View File

@@ -1,9 +1,10 @@
import os import os
from packaging.version import Version, parse
import requests
import sys import sys
from typing import List from typing import List
import requests
from packaging.version import Version, parse
def main(): def main():
package_name: str = os.environ["INPUT_PACKAGE_NAME"] package_name: str = os.environ["INPUT_PACKAGE_NAME"]

View File

@@ -1 +1 @@
../../../test/setup_db.sh ../../../scripts/setup_db.sh

169
.github/dbt-postgres-testing.yml vendored Normal file
View File

@@ -0,0 +1,169 @@
# **what?**
# Runs all tests in dbt-postgres with this branch of dbt-core to ensure nothing is broken
# **why?**
# Ensure dbt-core changes do not break dbt-postgres, as a basic proxy for other adapters
# **when?**
# This will run when trying to merge a PR into main.
# It can also be manually triggered.
# This workflow can be skipped by adding the "Skip Postgres Testing" label to the PR. This is
# useful when making a change in both `dbt-postgres` and `dbt-core` where the changes are dependant
# and cause the other repository to break.
name: "dbt-postgres Tests"
run-name: >-
${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')
&& format('dbt-postgres@{0} with dbt-core@{1}', inputs.dbt-postgres-ref, inputs.dbt-core-ref)
|| 'dbt-postgres@main with dbt-core branch' }}
on:
push:
branches:
- "main"
- "*.latest"
- "releases/*"
pull_request:
merge_group:
types: [checks_requested]
workflow_dispatch:
inputs:
dbt-postgres-ref:
description: "The branch of dbt-postgres to test against"
default: "main"
dbt-core-ref:
description: "The branch of dbt-core to test against"
default: "main"
workflow_call:
inputs:
dbt-postgres-ref:
description: "The branch of dbt-postgres to test against"
type: string
required: true
default: "main"
dbt-core-ref:
description: "The branch of dbt-core to test against"
type: string
required: true
default: "main"
permissions: read-all
# will cancel previous workflows triggered by the same event
# and for the same ref for PRs/merges or same SHA otherwise
# and for the same inputs on workflow_dispatch or workflow_call
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ contains(fromJson('["pull_request", "merge_group"]'), github.event_name) && github.event.pull_request.head.ref || github.sha }}-${{ contains(fromJson('["workflow_call", "workflow_dispatch"]'), github.event_name) && github.event.inputs.dbt-postgres-ref && github.event.inputs.dbt-core-ref || github.sha }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
job-prep:
# This allow us to run the workflow on pull_requests as well so we can always run unit tests
# and only run integration tests on merge for time purposes
name: Setup Repo Refs
runs-on: ubuntu-latest
outputs:
dbt-postgres-ref: ${{ steps.core-ref.outputs.ref }}
dbt-core-ref: ${{ steps.common-ref.outputs.ref }}
steps:
- name: "Input Refs"
id: job-inputs
run: |
echo "inputs.dbt-postgres-ref=${{ inputs.dbt-postgres-ref }}"
echo "inputs.dbt-core-ref=${{ inputs.dbt-core-ref }}"
- name: "Determine dbt-postgres ref"
id: core-ref
run: |
if [[ -z "${{ inputs.dbt-postgres-ref }}" ]]; then
REF="main"
else
REF=${{ inputs.dbt-postgres-ref }}
fi
echo "ref=$REF" >> $GITHUB_OUTPUT
- name: "Determine dbt-core ref"
id: common-ref
run: |
if [[ -z "${{ inputs.dbt-core-ref }}" ]]; then
# these will be commits instead of branches
if [[ "${{ github.event_name }}" == "merge_group" ]]; then
REF=${{ github.event.merge_group.head_sha }}
else
REF=${{ github.event.pull_request.base.sha }}
fi
else
REF=${{ inputs.dbt-core-ref }}
fi
echo "ref=$REF" >> $GITHUB_OUTPUT
- name: "Final Refs"
run: |
echo "dbt-postgres-ref=${{ steps.core-ref.outputs.ref }}"
echo "dbt-core-ref=${{ steps.common-ref.outputs.ref }}"
integration-tests-postgres:
name: "dbt-postgres integration tests"
needs: [job-prep]
runs-on: ubuntu-latest
defaults:
run:
working-directory: "./dbt-postgres"
environment:
name: "dbt-postgres"
env:
POSTGRES_TEST_HOST: ${{ vars.POSTGRES_TEST_HOST }}
POSTGRES_TEST_PORT: ${{ vars.POSTGRES_TEST_PORT }}
POSTGRES_TEST_USER: ${{ vars.POSTGRES_TEST_USER }}
POSTGRES_TEST_PASS: ${{ secrets.POSTGRES_TEST_PASS }}
POSTGRES_TEST_DATABASE: ${{ vars.POSTGRES_TEST_DATABASE }}
POSTGRES_TEST_THREADS: ${{ vars.POSTGRES_TEST_THREADS }}
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- ${{ vars.POSTGRES_TEST_PORT }}:5432
steps:
- name: "Check out dbt-adapters@${{ needs.job-prep.outputs.dbt-postgres-ref }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with:
repository: dbt-labs/dbt-adapters
ref: ${{ needs.job-prep.outputs.dbt-postgres-ref }}
- name: "Set up Python"
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: "Set environment variables"
run: |
echo "HATCH_PYTHON=${{ inputs.python-version }}" >> $GITHUB_ENV
echo "PIP_ONLY_BINARY=psycopg2-binary" >> $GITHUB_ENV
- name: "Setup test database"
run: psql -f ./scripts/setup_test_database.sql
env:
PGHOST: ${{ vars.POSTGRES_TEST_HOST }}
PGPORT: ${{ vars.POSTGRES_TEST_PORT }}
PGUSER: postgres
PGPASSWORD: postgres
PGDATABASE: postgres
- name: "Install hatch"
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # pypa/hatch@install
- name: "Run integration tests"
run: hatch run ${{ inputs.hatch-env }}:integration-tests

View File

@@ -34,7 +34,7 @@ env:
jobs: jobs:
check-reviews: check-reviews:
name: "Validate Additional Reviews" name: "Validate Additional Reviews"
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
steps: steps:
- name: "Get list of changed files" - name: "Get list of changed files"
id: changed_files id: changed_files
@@ -114,7 +114,7 @@ jobs:
- name: "Find Comment" - name: "Find Comment"
if: steps.artifact_files_changed.outputs.artifact_changes == 'true' && steps.check_approvals.outputs.CORE_APPROVALS < env.required_approvals if: steps.artifact_files_changed.outputs.artifact_changes == 'true' && steps.check_approvals.outputs.CORE_APPROVALS < env.required_approvals
uses: peter-evans/find-comment@v2 uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # peter-evans/find-comment@v2
id: find-comment id: find-comment
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
@@ -123,7 +123,7 @@ jobs:
- name: "Create Comment" - name: "Create Comment"
if: steps.artifact_files_changed.outputs.artifact_changes == 'true' && steps.find-comment.outputs.comment-id == '' && steps.check_approvals.outputs.CORE_APPROVALS < env.required_approvals if: steps.artifact_files_changed.outputs.artifact_changes == 'true' && steps.find-comment.outputs.comment-id == '' && steps.check_approvals.outputs.CORE_APPROVALS < env.required_approvals
uses: peter-evans/create-or-update-comment@v3 uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # peter-evans/create-or-update-comment@v3
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
body: | body: |
@@ -164,16 +164,23 @@ jobs:
fi fi
- name: "Post Event" - name: "Post Event"
# This step posts the status of the check because the workflow is triggered by multiple events
# and we need to ensure the check is always updated. Otherwise we would end up with duplicate
# checks in the GitHub UI.
run: | run: |
if [[ "${{ steps.status_check.outputs.current_status }}" == "success" ]]; then
state="success"
else
state="failure"
fi
gh api \ gh api \
--method POST \ --method POST \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/check-runs \ /repos/${{ github.repository }}/statuses/${{ github.event.pull_request.base.sha }} \
-f name='Artifact Review Check' \ -f state="$state" \
-f head_sha=${{ github.event.pull_request_target.head.sha || github.event.pull_request.head.sha }} \ -f description="Artifact Review Check" \
-f status='completed' \ -f context="Artifact Review Check" \
-f conclusion='${{ steps.status_check.outputs.current_status }}' \ -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
-f force=true \
-f details_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.FISHTOWN_BOT_PAT }}

View File

@@ -35,6 +35,6 @@ jobs:
github.event.pull_request.merged github.event.pull_request.merged
&& contains(github.event.label.name, 'backport') && contains(github.event.label.name, 'backport')
steps: steps:
- uses: tibdex/backport@v2.0.4 - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # tibdex/backport@v2.0.4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -48,7 +48,7 @@ jobs:
- name: Create and commit changelog on bot PR - name: Create and commit changelog on bot PR
if: ${{ contains(github.event.pull_request.labels.*.name, matrix.label) }} if: ${{ contains(github.event.pull_request.labels.*.name, matrix.label) }}
id: bot_changelog id: bot_changelog
uses: emmyoop/changie_bot@v1.1.0 uses: emmyoop/changie_bot@22b70618b13d0d1c64ea95212bafca2d2bf6b764 # emmyoop/changie_bot@v1.1.0
with: with:
GITHUB_TOKEN: ${{ secrets.FISHTOWN_BOT_PAT }} GITHUB_TOKEN: ${{ secrets.FISHTOWN_BOT_PAT }}
commit_author_name: "Github Build Bot" commit_author_name: "Github Build Bot"

View File

@@ -4,7 +4,8 @@ on:
pull_request: pull_request:
types: [ opened, reopened, labeled, unlabeled, synchronize ] types: [ opened, reopened, labeled, unlabeled, synchronize ]
paths-ignore: [ '.changes/**', '.github/**', 'tests/**', '**.md', '**.yml' ] paths-ignore: [ '.changes/**', '.github/**', 'tests/**', '**.md', '**.yml' ]
merge_group:
types: [checks_requested]
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@@ -16,13 +17,13 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'artifact_minor_upgrade') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'artifact_minor_upgrade') }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Check for changes in core/dbt/artifacts - name: Check for changes in core/dbt/artifacts
# https://github.com/marketplace/actions/paths-changes-filter # https://github.com/marketplace/actions/paths-changes-filter
uses: dorny/paths-filter@v3 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # dorny/paths-filter@v3
id: check_artifact_changes id: check_artifact_changes
with: with:
filters: | filters: |

View File

@@ -7,7 +7,6 @@
# **when?** # **when?**
# When a PR is opened, not in draft or moved from draft to ready for review # When a PR is opened, not in draft or moved from draft to ready for review
name: Label community PRs name: Label community PRs
on: on:
@@ -29,9 +28,15 @@ jobs:
# If this PR is opened and not draft, determine if it needs to be labeled # If this PR is opened and not draft, determine if it needs to be labeled
# if the PR is converted out of draft, determine if it needs to be labeled # if the PR is converted out of draft, determine if it needs to be labeled
if: | if: |
(!contains(github.event.pull_request.labels.*.name, 'community') && (
(github.event.action == 'opened' && github.event.pull_request.draft == false ) || !contains(github.event.pull_request.labels.*.name, 'community')
github.event.action == 'ready_for_review' ) && (
(github.event.action == 'opened' && github.event.pull_request.draft == false)
|| github.event.action == 'ready_for_review'
)
&& github.event.pull_request.user.type != 'Bot'
&& github.event.pull_request.user.login != 'dependabot[bot]'
)
uses: dbt-labs/actions/.github/workflows/label-community.yml@main uses: dbt-labs/actions/.github/workflows/label-community.yml@main
with: with:
github_team: 'core-group' github_team: 'core-group'

View File

@@ -1,25 +1,44 @@
# **what?** # **what?**
# Cuts a new `*.latest` branch # Cuts the `*.latest` branch, bumps dependencies on it, cleans up all files in `.changes/unreleased`
# Also cleans up all files in `.changes/unreleased` and `.changes/previous verion on # and `.changes/previous verion on main and bumps main to the input version.
# `main` and bumps `main` to the input version.
# **why?** # **why?**
# Generally reduces the workload of engineers and reduces error. Allow automation. # Clean up the main branch after a release branch is cut and automate cutting the release branch.
# Generally reduces the workload of engineers and reducing error.
# **when?** # **when?**
# This will run when called manually. # This will run when called manually or when triggered in another workflow.
# Example Usage including required permissions: TODO: update once finalized
# permissions:
# contents: read
# pull-requests: write
#
# name: Cut Release Branch
# jobs:
# changelog:
# uses: dbt-labs/actions/.github/workflows/cut-release-branch.yml@main
# with:
# new_branch_name: 1.7.latest
# PR_title: "Cleanup main after cutting new 1.7.latest branch"
# PR_body: "All adapter PRs will fail CI until the dbt-core PR has been merged due to release version conflicts."
# secrets:
# FISHTOWN_BOT_PAT: ${{ secrets.FISHTOWN_BOT_PAT }}
# TODOs
# add note to eventually commit changes directly and bypass checks - same as release - when we move to this model run test action after merge
name: Cut new release branch name: Cut new release branch
run-name: "Cutting New Branch: ${{ inputs.new_branch_name }}"
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version_to_bump_main:
description: 'The alpha version main should bump to (ex. 1.6.0a1)'
required: true
new_branch_name: new_branch_name:
description: 'The full name of the new branch (ex. 1.5.latest)' description: "The full name of the new branch (ex. 1.5.latest)"
required: true required: true
type: string
defaults: defaults:
run: run:
@@ -27,15 +46,346 @@ defaults:
permissions: permissions:
contents: write contents: write
pull-requests: write
env:
PYTHON_TARGET_VERSION: "3.10"
PR_TITLE: "Cleanup main after cutting new ${{ inputs.new_branch_name }} branch"
PR_BODY: "All adapter PRs will fail CI until the dbt-core PR has been merged due to release version conflicts."
jobs: jobs:
cut_branch: prep_work:
name: "Cut branch and clean up main for dbt-core" name: "Prep Work"
uses: dbt-labs/actions/.github/workflows/cut-release-branch.yml@main runs-on: ubuntu-latest
steps:
- name: "[DEBUG] Print Inputs"
run: |
echo "new_branch_name: ${{ inputs.new_branch_name }}"
echo "PR_title: ${{ env.PR_TITLE }}"
echo "PR_body: ${{ env.PR_BODY }}"
create_temp_branch:
name: "Create Temp branch off main"
runs-on: ubuntu-latest
outputs:
temp_branch_name: ${{ steps.variables.outputs.BRANCH_NAME }}
steps:
- name: "Set Branch Value"
id: variables
run: |
echo "BRANCH_NAME=cutting_release_branch/main_cleanup_$GITHUB_RUN_ID" >> $GITHUB_OUTPUT
- name: "Checkout ${{ github.repository }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
version_to_bump_main: ${{ inputs.version_to_bump_main }} ref: "main"
new_branch_name: ${{ inputs.new_branch_name }} token: ${{ secrets.FISHTOWN_BOT_PAT }}
PR_title: "Cleanup main after cutting new ${{ inputs.new_branch_name }} branch"
PR_body: "All adapter PRs will fail CI until the dbt-core PR has been merged due to release version conflicts." - name: "Create PR Branch"
secrets: run: |
FISHTOWN_BOT_PAT: ${{ secrets.FISHTOWN_BOT_PAT }} user="Github Build Bot"
email="buildbot@fishtownanalytics.com"
git config user.name "$user"
git config user.email "$email"
git checkout -b ${{ steps.variables.outputs.BRANCH_NAME }}
git push --set-upstream origin ${{ steps.variables.outputs.BRANCH_NAME }}
- name: "[Notification] Temp branch created"
run: |
message="Temp branch ${{ steps.variables.outputs.BRANCH_NAME }} created"
echo "::notice title="Temporary branch created": $title::$message"
cleanup_changelog:
name: "Clean Up Changelog"
needs: ["create_temp_branch"]
runs-on: ubuntu-latest
outputs:
next-version: ${{ steps.semver-current.outputs.next-minor-alpha-version }}
steps:
- name: "Checkout ${{ github.repository }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with:
ref: ${{ needs.create_temp_branch.outputs.temp_branch_name }}
token: ${{ secrets.FISHTOWN_BOT_PAT }}
- name: "Add Homebrew To PATH"
run: |
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
- name: "Install Homebrew Packages"
run: |
brew install pre-commit
brew tap miniscruff/changie https://github.com/miniscruff/changie
brew install changie
- name: "Check Current Version In Code"
id: determine_version
run: |
current_version=$(grep '^version = ' core/pyproject.toml | sed 's/version = "\(.*\)"/\1/')
echo "current_version=$current_version" >> $GITHUB_OUTPUT
- name: "[Notification] Check Current Version In Code"
run: |
message="The current version is ${{ steps.determine_version.outputs.current_version }}"
echo "::notice title="Version Bump Check": $title::$message"
- name: "Parse Current Version Into Parts for Changelog Directories"
id: semver-current
uses: dbt-labs/actions/parse-semver@main
with:
version: ${{ steps.determine_version.outputs.current_version }}
- name: "[Notification] Next Alpha Version"
run: |
message="The next alpha version is ${{ steps.semver-current.outputs.next-minor-alpha-version }}"
echo "::notice title="Version Bump Check": $title::$message"
- name: "Delete Unreleased Changelog YAMLs"
# removal fails if no files exist. OK to continue since we're just cleaning up the files.
continue-on-error: true
run: |
rm .changes/unreleased/*.yaml || true
- name: "Delete Pre Release Changelogs and YAMLs"
# removal fails if no files exist. OK to continue since we're just cleaning up the files.
continue-on-error: true
run: |
rm .changes/${{ steps.semver-current.outputs.base-version }}/*.yaml || true
rm .changes/${{ steps.semver-current.outputs.major }}.${{ steps.semver-current.outputs.minor }}.*.md || true
- name: "Cleanup CHANGELOG.md"
run: |
changie merge
- name: "Commit Changelog Cleanup to Branch"
run: |
user="Github Build Bot"
email="buildbot@fishtownanalytics.com"
git config user.name "$user"
git config user.email "$email"
git status
git add .
git commit -m "Clean up changelog on main"
git push
- name: "[Notification] Changelog cleaned up"
run: |
message="Changelog on ${{ needs.create_temp_branch.outputs.temp_branch_name }} cleaned up"
echo "::notice title="Changelog cleaned up": $title::$message"
bump_version:
name: "Bump to next minor version"
needs: ["cleanup_changelog", "create_temp_branch"]
runs-on: ubuntu-latest
steps:
- name: "Checkout ${{ github.repository }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with:
ref: ${{ needs.create_temp_branch.outputs.temp_branch_name }}
token: ${{ secrets.FISHTOWN_BOT_PAT }}
- name: "Set up Python - ${{ env.PYTHON_TARGET_VERSION }}"
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # actions/setup-python@v5
with:
python-version: "${{ env.PYTHON_TARGET_VERSION }}"
- name: "Install Spark Dependencies"
if: ${{ contains(github.repository, 'dbt-labs/dbt-spark') }}
run: |
sudo apt-get update
sudo apt-get install libsasl2-dev
- name: "Install Python Dependencies"
run: |
python -m pip install --upgrade pip
python -m pip install hatch
- name: "Bump Version To ${{ needs.cleanup_changelog.outputs.next-version }}"
run: |
cd core
hatch version ${{ needs.cleanup_changelog.outputs.next-version }}
hatch run dev-req
dbt --version
- name: "Commit Version Bump to Branch"
run: |
user="Github Build Bot"
email="buildbot@fishtownanalytics.com"
git config user.name "$user"
git config user.email "$email"
git status
git add .
git commit -m "Bumping version to ${{ needs.cleanup_changelog.outputs.next-version }}"
git push
- name: "[Notification] Version Bump completed"
run: |
message="Version on ${{ needs.create_temp_branch.outputs.temp_branch_name }} bumped to ${{ needs.cleanup_changelog.outputs.next-version }}"
echo "::notice title="Version Bump Completed": $title::$message"
cleanup:
name: "Cleanup Code Quality"
needs: ["create_temp_branch", "bump_version"]
runs-on: ubuntu-latest
steps:
- name: "Checkout ${{ github.repository }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with:
ref: ${{ needs.create_temp_branch.outputs.temp_branch_name }}
token: ${{ secrets.FISHTOWN_BOT_PAT }}
- name: "Add Homebrew To PATH"
run: |
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
- name: "brew install pre-commit"
run: |
brew install pre-commit
# this step will fail on whitespace errors but also correct them
- name: "Cleanup - Remove Trailing Whitespace Via Pre-commit"
continue-on-error: true
run: |
pre-commit run trailing-whitespace --files CHANGELOG.md .changes/* || true
# this step will fail on newline errors but also correct them
- name: "Cleanup - Remove Extra Newlines Via Pre-commit"
continue-on-error: true
run: |
pre-commit run end-of-file-fixer --files CHANGELOG.md .changes/* || true
- name: "Commit Version Bump to Branch"
run: |
user="Github Build Bot"
email="buildbot@fishtownanalytics.com"
git config user.name "$user"
git config user.email "$email"
git status
git add .
git commit -m "Code quality cleanup"
git push
open_pr:
name: "Open PR Against main"
needs: ["cleanup_changelog", "create_temp_branch", "cleanup"]
runs-on: ubuntu-latest
outputs:
pr_number: ${{ steps.create_pr.outputs.pull-request-number }}
steps:
- name: "Checkout ${{ github.repository }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with:
ref: ${{ needs.create_temp_branch.outputs.temp_branch_name }}
token: ${{ secrets.FISHTOWN_BOT_PAT }}
- name: "Determine PR Title"
id: pr_title
run: |
echo "pr_title=${{ env.PR_TITLE }}" >> $GITHUB_OUTPUT
if [${{ env.PR_TITLE }} == ""]; then
echo "pr_title='Clean up changelogs and bump to version ${{ needs.cleanup_changelog.outputs.next-version }}'" >> $GITHUB_OUTPUT
fi
- name: "Determine PR Body"
id: pr_body
run: |
echo "pr_body=${{ env.PR_BODY }}" >> $GITHUB_OUTPUT
if [${{ env.PR_BODY }} == ""]; then
echo "pr_body='Clean up changelogs and bump to version ${{ needs.cleanup_changelog.outputs.next-version }}'" >> $GITHUB_OUTPUT
fi
- name: "Add Branch Details"
id: pr_body_branch
run: |
branch_details="The workflow that generated this PR also created a new branch: ${{ inputs.new_branch_name }}"
full_body="${{ steps.pr_body.outputs.pr_body }} $branch_details"
echo "pr_full_body=$full_body" >> $GITHUB_OUTPUT
- name: "Open Pull Request"
id: create_pr
run: |
pr_url=$(gh pr create -B main -H ${{ needs.create_temp_branch.outputs.temp_branch_name }} -l "Skip Changelog" -t "${{ steps.pr_title.outputs.pr_title }}" -b "${{ steps.pr_body_branch.outputs.pr_full_body }}")
echo "pr_url=$pr_url" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.FISHTOWN_BOT_PAT }}
- name: "[Notification] Pull Request Opened"
run: |
message="PR opened at ${{ steps.create_pr.outputs.pr_url }}"
echo "::notice title="Pull Request Opened": $title::$message"
cut_new_branch:
# don't cut the new branch until we're done opening the PR against main
name: "Cut New Branch ${{ inputs.new_branch_name }}"
needs: [open_pr]
runs-on: ubuntu-latest
steps:
- name: "Checkout ${{ github.repository }}"
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with:
token: ${{ secrets.FISHTOWN_BOT_PAT }}
fetch-depth: 0
- name: "Ensure New Branch Does Not Exist"
id: check_new_branch
run: |
title="Check New Branch Existence"
if git show-ref --quiet ${{ inputs.new_branch_name }}; then
message="Branch ${{ inputs.new_branch_name }} already exists. Exiting."
echo "::error $title::$message"
exit 1
fi
- name: "Create New Release Branch"
run: |
git checkout -b ${{ inputs.new_branch_name }}
- name: "Push up New Branch"
run: |
#Data for commit
user="Github Build Bot"
email="buildbot@fishtownanalytics.com"
git config user.name "$user"
git config user.email "$email"
git push --set-upstream origin ${{ inputs.new_branch_name }}
- name: "[Notification] New branch created"
run: |
message="New branch ${{ inputs.new_branch_name }} created"
echo "::notice title="New branch created": $title::$message"
- name: "Bump dependencies via script"
# This bumps the dependency on dbt-core in the adapters
if: ${{ !contains(github.repository, 'dbt-core') }}
run: |
echo ${{ github.repository }}
echo "running update_dependencies script"
bash ${GITHUB_WORKSPACE}/.github/scripts/update_dependencies.sh ${{ inputs.new_branch_name }}
commit_message="bumping .latest branch variable in update_dependencies.sh to ${{ inputs.new_branch_name }}"
git status
git add .
git commit -m "$commit_message"
git push
- name: "Bump env variable via script"
# bumps the RELEASE_BRANCH variable in nightly-release.yml in adapters
if: ${{ !contains(github.repository, 'dbt-core') }}
run: |
file="./.github/scripts/update_release_branch.sh"
if test -f "$file"; then
echo ${{ github.repository }}
echo "running some script yet to be written now"
bash $file ${{ inputs.new_branch_name }}
commit_message="updating env variable to ${{ inputs.new_branch_name }} in nightly-release.yml"
git status
git add .
git commit -m "$commit_message"
git push
else
echo "no $file seen skipping step"
fi

View File

@@ -20,6 +20,8 @@ on:
- "*.latest" - "*.latest"
- "releases/*" - "releases/*"
pull_request: pull_request:
merge_group:
types: [checks_requested]
workflow_dispatch: workflow_dispatch:
permissions: read-all permissions: read-all
@@ -42,50 +44,53 @@ jobs:
code-quality: code-quality:
name: code-quality name: code-quality
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: '3.9' python-version: "3.10"
- name: Install python dependencies - name: Install python dependencies
run: | run: |
python -m pip install --user --upgrade pip python -m pip install --user --upgrade pip
python -m pip --version python -m pip --version
make dev python -m pip install hatch
make dev_req cd core
mypy --version hatch run setup
dbt --version
- name: Verify dbt installation
run: |
cd core
hatch run dbt --version
- name: Run pre-commit hooks - name: Run pre-commit hooks
run: pre-commit run --all-files --show-diff-on-failure run: |
cd core
hatch run code-quality
unit: unit:
name: unit test / python ${{ matrix.python-version }} name: "unit test / python ${{ matrix.python-version }}"
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] python-version: ["3.10", "3.11", "3.12", "3.13"]
env:
TOXENV: "unit"
steps: steps:
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@@ -93,15 +98,15 @@ jobs:
run: | run: |
python -m pip install --user --upgrade pip python -m pip install --user --upgrade pip
python -m pip --version python -m pip --version
python -m pip install tox python -m pip install hatch
tox --version hatch --version
- name: Run unit tests - name: Run unit tests
uses: nick-fields/retry@v3 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # nick-fields/retry@v3
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3
command: tox -e unit command: cd core && hatch run ci:unit-tests
- name: Get current date - name: Get current date
if: always() if: always()
@@ -112,14 +117,15 @@ jobs:
- name: Upload Unit Test Coverage to Codecov - name: Upload Unit Test Coverage to Codecov
if: ${{ matrix.python-version == '3.11' }} if: ${{ matrix.python-version == '3.11' }}
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: unit flags: unit
fail_ci_if_error: false
integration-metadata: integration-metadata:
name: integration test metadata generation name: integration test metadata generation
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
outputs: outputs:
split-groups: ${{ steps.generate-split-groups.outputs.split-groups }} split-groups: ${{ steps.generate-split-groups.outputs.split-groups }}
include: ${{ steps.generate-include.outputs.include }} include: ${{ steps.generate-include.outputs.include }}
@@ -140,7 +146,7 @@ jobs:
- name: generate include - name: generate include
id: generate-include id: generate-include
run: | run: |
INCLUDE=('"python-version":"3.9","os":"${{ vars.WINDOWS_LATEST }}"' '"python-version":"3.9","os":"${{ vars.MACOS_LATEST }}"' ) INCLUDE=('"python-version":"3.10","os":"windows-latest"' '"python-version":"3.10","os":"macos-14"' )
INCLUDE_GROUPS="[" INCLUDE_GROUPS="["
for include in ${INCLUDE[@]}; do for include in ${INCLUDE[@]}; do
for group in $(seq 1 ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }}); do for group in $(seq 1 ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }}); do
@@ -153,7 +159,7 @@ jobs:
echo "include=${INCLUDE_GROUPS}" >> $GITHUB_OUTPUT echo "include=${INCLUDE_GROUPS}" >> $GITHUB_OUTPUT
integration-postgres: integration-postgres:
name: (${{ matrix.split-group }}) integration test / python ${{ matrix.python-version }} / ${{ matrix.os }} name: "(${{ matrix.split-group }}) integration test / python ${{ matrix.python-version }} / ${{ matrix.os }}"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 30 timeout-minutes: 30
@@ -162,11 +168,10 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] python-version: ["3.10", "3.11", "3.12", "3.13"]
os: ["${{ vars.UBUNTU_LATEST }}"] os: ["ubuntu-latest"]
split-group: ${{ fromJson(needs.integration-metadata.outputs.split-groups) }} split-group: ${{ fromJson(needs.integration-metadata.outputs.split-groups) }}
env: env:
TOXENV: integration
DBT_INVOCATION_ENV: github-actions DBT_INVOCATION_ENV: github-actions
DBT_TEST_USER_1: dbt_test_user_1 DBT_TEST_USER_1: dbt_test_user_1
DBT_TEST_USER_2: dbt_test_user_2 DBT_TEST_USER_2: dbt_test_user_2
@@ -197,16 +202,16 @@ jobs:
steps: steps:
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Run postgres setup script - name: Run postgres setup script
run: | run: |
./test/setup_db.sh ./scripts/setup_db.sh
env: env:
PGHOST: localhost PGHOST: localhost
PGPORT: 5432 PGPORT: 5432
@@ -216,17 +221,16 @@ jobs:
run: | run: |
python -m pip install --user --upgrade pip python -m pip install --user --upgrade pip
python -m pip --version python -m pip --version
python -m pip install tox python -m pip install hatch
tox --version hatch --version
- name: Run integration tests - name: Run integration tests
uses: nick-fields/retry@v3 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # nick-fields/retry@v3
with: with:
timeout_minutes: 30 timeout_minutes: 30
max_attempts: 3 max_attempts: 3
command: tox -- --ddtrace shell: bash
env: command: cd core && hatch run ci:integration-tests -- --ddtrace --splits ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }} --group ${{ matrix.split-group }}
PYTEST_ADDOPTS: ${{ format('--splits {0} --group {1}', env.PYTHON_INTEGRATION_TEST_WORKERS, matrix.split-group) }}
- name: Get current date - name: Get current date
if: always() if: always()
@@ -235,7 +239,7 @@ jobs:
CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4
if: always() if: always()
with: with:
name: logs_${{ matrix.python-version }}_${{ matrix.os }}_${{ matrix.split-group }}_${{ steps.date.outputs.date }} name: logs_${{ matrix.python-version }}_${{ matrix.os }}_${{ matrix.split-group }}_${{ steps.date.outputs.date }}
@@ -243,10 +247,11 @@ jobs:
- name: Upload Integration Test Coverage to Codecov - name: Upload Integration Test Coverage to Codecov
if: ${{ matrix.python-version == '3.11' }} if: ${{ matrix.python-version == '3.11' }}
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: integration flags: integration
fail_ci_if_error: false
integration-mac-windows: integration-mac-windows:
name: (${{ matrix.split-group }}) integration test / python ${{ matrix.python-version }} / ${{ matrix.os }} name: (${{ matrix.split-group }}) integration test / python ${{ matrix.python-version }} / ${{ matrix.os }}
@@ -258,11 +263,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
split-group: ${{ fromJson(needs.integration-metadata.outputs.split-groups) }} # already includes split group and runs mac + windows
# this include is where we add the mac and windows os
include: ${{ fromJson(needs.integration-metadata.outputs.include) }} include: ${{ fromJson(needs.integration-metadata.outputs.include) }}
env: env:
TOXENV: integration
DBT_INVOCATION_ENV: github-actions DBT_INVOCATION_ENV: github-actions
DBT_TEST_USER_1: dbt_test_user_1 DBT_TEST_USER_1: dbt_test_user_1
DBT_TEST_USER_2: dbt_test_user_2 DBT_TEST_USER_2: dbt_test_user_2
@@ -275,21 +278,21 @@ jobs:
steps: steps:
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Set up postgres (macos) - name: Set up postgres (macos)
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: nick-fields/retry@v3 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # nick-fields/retry@v3
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3
command: ./test/setup_db.sh command: ./scripts/setup_db.sh
- name: Set up postgres (windows) - name: Set up postgres (windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -299,17 +302,16 @@ jobs:
run: | run: |
python -m pip install --user --upgrade pip python -m pip install --user --upgrade pip
python -m pip --version python -m pip --version
python -m pip install tox python -m pip install hatch
tox --version hatch --version
- name: Run integration tests - name: Run integration tests
uses: nick-fields/retry@v3 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # nick-fields/retry@v3
with: with:
timeout_minutes: 30 timeout_minutes: 30
max_attempts: 3 max_attempts: 3
command: tox -- --ddtrace shell: bash
env: command: cd core && hatch run ci:integration-tests -- --ddtrace --splits ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }} --group ${{ matrix.split-group }}
PYTEST_ADDOPTS: ${{ format('--splits {0} --group {1}', env.PYTHON_INTEGRATION_TEST_WORKERS, matrix.split-group) }}
- name: Get current date - name: Get current date
if: always() if: always()
@@ -318,7 +320,7 @@ jobs:
CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4
if: always() if: always()
with: with:
name: logs_${{ matrix.python-version }}_${{ matrix.os }}_${{ matrix.split-group }}_${{ steps.date.outputs.date }} name: logs_${{ matrix.python-version }}_${{ matrix.os }}_${{ matrix.split-group }}_${{ steps.date.outputs.date }}
@@ -326,15 +328,16 @@ jobs:
- name: Upload Integration Test Coverage to Codecov - name: Upload Integration Test Coverage to Codecov
if: ${{ matrix.python-version == '3.11' }} if: ${{ matrix.python-version == '3.11' }}
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: integration flags: integration
fail_ci_if_error: false
integration-report: integration-report:
if: ${{ always() }} if: ${{ always() }}
name: Integration Test Suite name: Integration Test Suite
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
needs: [integration-mac-windows, integration-postgres] needs: [integration-mac-windows, integration-postgres]
steps: steps:
- name: "Integration Tests Failed" - name: "Integration Tests Failed"
@@ -351,21 +354,21 @@ jobs:
build: build:
name: build packages name: build packages
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
steps: steps:
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: '3.9' python-version: "3.10"
- name: Install python dependencies - name: Install python dependencies
run: | run: |
python -m pip install --user --upgrade pip python -m pip install --user --upgrade pip
python -m pip install --upgrade setuptools wheel twine check-wheel-contents python -m pip install --upgrade hatch twine check-wheel-contents
python -m pip --version python -m pip --version
- name: Build distributions - name: Build distributions
@@ -374,27 +377,7 @@ jobs:
- name: Show distributions - name: Show distributions
run: ls -lh dist/ run: ls -lh dist/
- name: Check distribution descriptions - name: Check and verify distributions
run: | run: |
twine check dist/* cd core
hatch run build:check-all
- name: Check wheel contents
run: |
check-wheel-contents dist/*.whl --ignore W007,W008
- name: Install wheel distributions
run: |
find ./dist/*.whl -maxdepth 1 -type f | xargs python -m pip install --force-reinstall --find-links=dist/
- name: Check wheel distributions
run: |
dbt --version
- name: Install source distributions
# ignore dbt-1.0.0, which intentionally raises an error when installed from source
run: |
find ./dist/*.gz -maxdepth 1 -type f | xargs python -m pip install --force-reinstall --find-links=dist/
- name: Check source distributions
run: |
dbt --version

View File

@@ -1,265 +0,0 @@
# **what?**
# This workflow models the performance characteristics of a point in time in dbt.
# It runs specific dbt commands on committed projects multiple times to create and
# commit information about the distribution to the current branch. For more information
# see the readme in the performance module at /performance/README.md.
#
# **why?**
# When developing new features, we can take quick performance samples and compare
# them against the commited baseline measurements produced by this workflow to detect
# some performance regressions at development time before they reach users.
#
# **when?**
# This is only run once directly after each release (for non-prereleases). If for some
# reason the results of a run are not satisfactory, it can also be triggered manually.
name: Model Performance Characteristics
on:
# runs after non-prereleases are published.
release:
types: [released]
# run manually from the actions tab
workflow_dispatch:
inputs:
release_id:
description: 'dbt version to model (must be non-prerelease in Pypi)'
type: string
required: true
env:
RUNNER_CACHE_PATH: performance/runner/target/release/runner
# both jobs need to write
permissions:
contents: write
pull-requests: write
jobs:
set-variables:
name: Setting Variables
runs-on: ${{ vars.UBUNTU_LATEST }}
outputs:
cache_key: ${{ steps.variables.outputs.cache_key }}
release_id: ${{ steps.semver.outputs.base-version }}
release_branch: ${{ steps.variables.outputs.release_branch }}
steps:
# explicitly checkout the performance runner from main regardless of which
# version we are modeling.
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Parse version into parts
id: semver
uses: dbt-labs/actions/parse-semver@v1
with:
version: ${{ github.event.inputs.release_id || github.event.release.tag_name }}
# collect all the variables that need to be used in subsequent jobs
- name: Set variables
id: variables
run: |
# create a cache key that will be used in the next job. without this the
# next job would have to checkout from main and hash the files itself.
echo "cache_key=${{ runner.os }}-${{ hashFiles('performance/runner/Cargo.toml')}}-${{ hashFiles('performance/runner/src/*') }}" >> $GITHUB_OUTPUT
branch_name="${{steps.semver.outputs.major}}.${{steps.semver.outputs.minor}}.latest"
echo "release_branch=$branch_name" >> $GITHUB_OUTPUT
echo "release branch is inferred to be ${branch_name}"
latest-runner:
name: Build or Fetch Runner
runs-on: ${{ vars.UBUNTU_LATEST }}
needs: [set-variables]
env:
RUSTFLAGS: "-D warnings"
steps:
- name: '[DEBUG] print variables'
run: |
echo "all variables defined in set-variables"
echo "cache_key: ${{ needs.set-variables.outputs.cache_key }}"
echo "release_id: ${{ needs.set-variables.outputs.release_id }}"
echo "release_branch: ${{ needs.set-variables.outputs.release_branch }}"
# explicitly checkout the performance runner from main regardless of which
# version we are modeling.
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
# attempts to access a previously cached runner
- uses: actions/cache@v4
id: cache
with:
path: ${{ env.RUNNER_CACHE_PATH }}
key: ${{ needs.set-variables.outputs.cache_key }}
- name: Fetch Rust Toolchain
if: steps.cache.outputs.cache-hit != 'true'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Add fmt
if: steps.cache.outputs.cache-hit != 'true'
run: rustup component add rustfmt
- name: Cargo fmt
if: steps.cache.outputs.cache-hit != 'true'
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path performance/runner/Cargo.toml --all -- --check
- name: Test
if: steps.cache.outputs.cache-hit != 'true'
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path performance/runner/Cargo.toml
- name: Build (optimized)
if: steps.cache.outputs.cache-hit != 'true'
uses: actions-rs/cargo@v1
with:
command: build
args: --release --manifest-path performance/runner/Cargo.toml
# the cache action automatically caches this binary at the end of the job
model:
# depends on `latest-runner` as a separate job so that failures in this job do not prevent
# a successfully tested and built binary from being cached.
needs: [set-variables, latest-runner]
name: Model a release
runs-on: ${{ vars.UBUNTU_LATEST }}
steps:
- name: '[DEBUG] print variables'
run: |
echo "all variables defined in set-variables"
echo "cache_key: ${{ needs.set-variables.outputs.cache_key }}"
echo "release_id: ${{ needs.set-variables.outputs.release_id }}"
echo "release_branch: ${{ needs.set-variables.outputs.release_branch }}"
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install dbt
run: pip install dbt-postgres==${{ needs.set-variables.outputs.release_id }}
- name: Install Hyperfine
run: wget https://github.com/sharkdp/hyperfine/releases/download/v1.11.0/hyperfine_1.11.0_amd64.deb && sudo dpkg -i hyperfine_1.11.0_amd64.deb
# explicitly checkout main to get the latest project definitions
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
# this was built in the previous job so it will be there.
- name: Fetch Runner
uses: actions/cache@v4
id: cache
with:
path: ${{ env.RUNNER_CACHE_PATH }}
key: ${{ needs.set-variables.outputs.cache_key }}
- name: Move Runner
run: mv performance/runner/target/release/runner performance/app
- name: Change Runner Permissions
run: chmod +x ./performance/app
- name: '[DEBUG] ls baseline directory before run'
run: ls -R performance/baselines/
# `${{ github.workspace }}` is used to pass the absolute path
- name: Create directories
run: |
mkdir ${{ github.workspace }}/performance/tmp/
mkdir -p performance/baselines/${{ needs.set-variables.outputs.release_id }}/
# Run modeling with taking 20 samples
- name: Run Measurement
run: |
performance/app model -v ${{ needs.set-variables.outputs.release_id }} -b ${{ github.workspace }}/performance/baselines/ -p ${{ github.workspace }}/performance/projects/ -t ${{ github.workspace }}/performance/tmp/ -n 20
- name: '[DEBUG] ls baseline directory after run'
run: ls -R performance/baselines/
- uses: actions/upload-artifact@v4
with:
name: baseline
path: performance/baselines/${{ needs.set-variables.outputs.release_id }}/
create-pr:
name: Open PR for ${{ matrix.base-branch }}
# depends on `model` as a separate job so that the baseline can be committed to more than one branch
# i.e. release branch and main
needs: [set-variables, latest-runner, model]
runs-on: ${{ vars.UBUNTU_LATEST }}
strategy:
matrix:
include:
- base-branch: refs/heads/main
target-branch: performance-bot/main_${{ needs.set-variables.outputs.release_id }}_${{GITHUB.RUN_ID}}
- base-branch: refs/heads/${{ needs.set-variables.outputs.release_branch }}
target-branch: performance-bot/release_${{ needs.set-variables.outputs.release_id }}_${{GITHUB.RUN_ID}}
steps:
- name: '[DEBUG] print variables'
run: |
echo "all variables defined in set-variables"
echo "cache_key: ${{ needs.set-variables.outputs.cache_key }}"
echo "release_id: ${{ needs.set-variables.outputs.release_id }}"
echo "release_branch: ${{ needs.set-variables.outputs.release_branch }}"
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ matrix.base-branch }}
- name: Create PR branch
run: |
git checkout -b ${{ matrix.target-branch }}
git push origin ${{ matrix.target-branch }}
git branch --set-upstream-to=origin/${{ matrix.target-branch }} ${{ matrix.target-branch }}
- uses: actions/download-artifact@v4
with:
name: baseline
path: performance/baselines/${{ needs.set-variables.outputs.release_id }}
- name: '[DEBUG] ls baselines after artifact download'
run: ls -R performance/baselines/
- name: Commit baseline
uses: EndBug/add-and-commit@v9
with:
add: 'performance/baselines/*'
author_name: 'Github Build Bot'
author_email: 'buildbot@fishtownanalytics.com'
message: 'adding performance baseline for ${{ needs.set-variables.outputs.release_id }}'
push: 'origin origin/${{ matrix.target-branch }}'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
author: 'Github Build Bot <buildbot@fishtownanalytics.com>'
base: ${{ matrix.base-branch }}
branch: '${{ matrix.target-branch }}'
title: 'Adding performance modeling for ${{needs.set-variables.outputs.release_id}} to ${{ matrix.base-branch }}'
body: 'Committing perf results for tracking for the ${{needs.set-variables.outputs.release_id}}'
labels: |
Skip Changelog
Performance

View File

@@ -39,14 +39,14 @@ jobs:
steps: steps:
- name: "Checkout ${{ github.repository }} Branch ${{ env.RELEASE_BRANCH }}" - name: "Checkout ${{ github.repository }} Branch ${{ env.RELEASE_BRANCH }}"
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
ref: ${{ env.RELEASE_BRANCH }} ref: ${{ env.RELEASE_BRANCH }}
- name: "Get Current Version Number" - name: "Get Current Version Number"
id: version-number-sources id: version-number-sources
run: | run: |
current_version=`awk -F"current_version = " '{print $2}' .bumpversion.cfg | tr '\n' ' '` current_version=$(grep '^version = ' core/dbt/__version__.py | sed 's/version = "\(.*\)"/\1/')
echo "current_version=$current_version" >> $GITHUB_OUTPUT echo "current_version=$current_version" >> $GITHUB_OUTPUT
- name: "Audit Version And Parse Into Parts" - name: "Audit Version And Parse Into Parts"

View File

@@ -72,12 +72,15 @@ defaults:
run: run:
shell: bash shell: bash
env:
MIN_HATCH_VERSION: "1.11.0"
jobs: jobs:
job-setup: job-setup:
name: Log Inputs name: Log Inputs
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ${{ vars.UBUNTU_LATEST }}
outputs: outputs:
starting_sha: ${{ steps.set_sha.outputs.starting_sha }} use_hatch: ${{ steps.use_hatch.outputs.use_hatch }}
steps: steps:
- name: "[DEBUG] Print Variables" - name: "[DEBUG] Print Variables"
run: | run: |
@@ -88,19 +91,29 @@ jobs:
echo Nightly release: ${{ inputs.nightly_release }} echo Nightly release: ${{ inputs.nightly_release }}
echo Only Docker: ${{ inputs.only_docker }} echo Only Docker: ${{ inputs.only_docker }}
- name: "Checkout target branch" # In version env.HATCH_VERSION we started to use hatch for build tooling. Before that we used setuptools.
uses: actions/checkout@v4 # This needs to check if we're using hatch or setuptools based on the version being released. We should
with: # check if the version is greater than or equal to env.HATCH_VERSION. If it is, we use hatch, otherwise we use setuptools.
ref: ${{ inputs.target_branch }} - name: "Check if using hatch"
id: use_hatch
# release-prep.yml really shouldn't take in the sha but since core + all adapters
# depend on it now this workaround lets us not input it manually with risk of error.
# The changes always get merged into the head so we can't use a specific commit for
# releases anyways.
- name: "Capture sha"
id: set_sha
run: | run: |
echo "starting_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT # Extract major.minor from versions like 1.11.0a1 -> 1.11
INPUT_MAJ_MIN=$(echo "${{ inputs.version_number }}" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
HATCH_MAJ_MIN=$(echo "${{ env.MIN_HATCH_VERSION }}" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
if [ $(echo "$INPUT_MAJ_MIN >= $HATCH_MAJ_MIN" | bc) -eq 1 ]; then
echo "use_hatch=true" >> $GITHUB_OUTPUT
else
echo "use_hatch=false" >> $GITHUB_OUTPUT
fi
- name: "Notify if using hatch"
run: |
if [ ${{ steps.use_hatch.outputs.use_hatch }} = "true" ]; then
echo "::notice title="Using Hatch": $title::Using Hatch for release"
else
echo "::notice title="Using Setuptools": $title::Using Setuptools for release"
fi
bump-version-generate-changelog: bump-version-generate-changelog:
name: Bump package version, Generate changelog name: Bump package version, Generate changelog
@@ -110,12 +123,13 @@ jobs:
uses: dbt-labs/dbt-release/.github/workflows/release-prep.yml@main uses: dbt-labs/dbt-release/.github/workflows/release-prep.yml@main
with: with:
sha: ${{ needs.job-setup.outputs.starting_sha }}
version_number: ${{ inputs.version_number }} version_number: ${{ inputs.version_number }}
hatch_directory: "core"
target_branch: ${{ inputs.target_branch }} target_branch: ${{ inputs.target_branch }}
env_setup_script_path: "scripts/env-setup.sh" env_setup_script_path: "scripts/env-setup.sh"
test_run: ${{ inputs.test_run }} test_run: ${{ inputs.test_run }}
nightly_release: ${{ inputs.nightly_release }} nightly_release: ${{ inputs.nightly_release }}
use_hatch: ${{ needs.job-setup.outputs.use_hatch == 'true' }} # workflow outputs are strings...
secrets: inherit secrets: inherit
@@ -143,16 +157,13 @@ jobs:
with: with:
sha: ${{ needs.bump-version-generate-changelog.outputs.final_sha }} sha: ${{ needs.bump-version-generate-changelog.outputs.final_sha }}
version_number: ${{ inputs.version_number }} version_number: ${{ inputs.version_number }}
hatch_directory: "core"
changelog_path: ${{ needs.bump-version-generate-changelog.outputs.changelog_path }} changelog_path: ${{ needs.bump-version-generate-changelog.outputs.changelog_path }}
build_script_path: "scripts/build-dist.sh" build_script_path: "scripts/build-dist.sh"
s3_bucket_name: "core-team-artifacts"
package_test_command: "dbt --version" package_test_command: "dbt --version"
test_run: ${{ inputs.test_run }} test_run: ${{ inputs.test_run }}
nightly_release: ${{ inputs.nightly_release }} nightly_release: ${{ inputs.nightly_release }}
use_hatch: ${{ needs.job-setup.outputs.use_hatch == 'true' }} # workflow outputs are strings...
secrets:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
github-release: github-release:
name: GitHub Release name: GitHub Release

View File

@@ -22,7 +22,7 @@ on:
target_branch: target_branch:
description: "The branch to check against" description: "The branch to check against"
type: string type: string
default: 'main' default: "main"
required: true required: true
# no special access is needed # no special access is needed
@@ -41,19 +41,19 @@ jobs:
steps: steps:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: 3.9 python-version: "3.10"
- name: Checkout dbt repo - name: Checkout dbt repo
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
path: ${{ env.DBT_REPO_DIRECTORY }} path: ${{ env.DBT_REPO_DIRECTORY }}
ref: ${{ inputs.target_branch }} ref: ${{ inputs.target_branch }}
- name: Check for changes in core/dbt/artifacts - name: Check for changes in core/dbt/artifacts
# https://github.com/marketplace/actions/paths-changes-filter # https://github.com/marketplace/actions/paths-changes-filter
uses: dorny/paths-filter@v3 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # dorny/paths-filter@v3
id: check_artifact_changes id: check_artifact_changes
with: with:
filters: | filters: |
@@ -69,21 +69,19 @@ jobs:
- name: Checkout schemas.getdbt.com repo - name: Checkout schemas.getdbt.com repo
if: steps.check_artifact_changes.outputs.artifacts_changed == 'true' if: steps.check_artifact_changes.outputs.artifacts_changed == 'true'
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
repository: dbt-labs/schemas.getdbt.com repository: dbt-labs/schemas.getdbt.com
ref: 'main' ref: "main"
path: ${{ env.SCHEMA_REPO_DIRECTORY }} path: ${{ env.SCHEMA_REPO_DIRECTORY }}
- name: Generate current schema - name: Generate current schema
if: steps.check_artifact_changes.outputs.artifacts_changed == 'true' if: steps.check_artifact_changes.outputs.artifacts_changed == 'true'
run: | run: |
cd ${{ env.DBT_REPO_DIRECTORY }} cd ${{ env.DBT_REPO_DIRECTORY }}/core
python3 -m venv env pip install --upgrade pip hatch
source env/bin/activate hatch run setup
pip install --upgrade pip hatch run json-schema -- --path ${{ env.LATEST_SCHEMA_PATH }}
pip install -r dev-requirements.txt -r editable-requirements.txt
python scripts/collect-artifact-schema.py --path ${{ env.LATEST_SCHEMA_PATH }}
# Copy generated schema files into the schemas.getdbt.com repo # Copy generated schema files into the schemas.getdbt.com repo
# Do a git diff to find any changes # Do a git diff to find any changes
@@ -96,8 +94,8 @@ jobs:
git diff -I='*[0-9]{4}-[0-9]{2}-[0-9]{2}' -I='*[0-9]+\.[0-9]+\.[0-9]+' --exit-code > ${{ env.SCHEMA_DIFF_ARTIFACT }} git diff -I='*[0-9]{4}-[0-9]{2}-[0-9]{2}' -I='*[0-9]+\.[0-9]+\.[0-9]+' --exit-code > ${{ env.SCHEMA_DIFF_ARTIFACT }}
- name: Upload schema diff - name: Upload schema diff
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4
if: ${{ failure() && steps.check_artifact_changes.outputs.artifacts_changed == 'true' }} if: ${{ failure() && steps.check_artifact_changes.outputs.artifacts_changed == 'true' }}
with: with:
name: 'schema_changes.txt' name: "schema_changes.txt"
path: '${{ env.SCHEMA_DIFF_ARTIFACT }}' path: "${{ env.SCHEMA_DIFF_ARTIFACT }}"

View File

@@ -14,6 +14,8 @@ on:
- "*.latest" - "*.latest"
- "releases/*" - "releases/*"
pull_request: pull_request:
merge_group:
types: [checks_requested]
workflow_dispatch: workflow_dispatch:
permissions: read-all permissions: read-all
@@ -26,7 +28,7 @@ env:
jobs: jobs:
integration-metadata: integration-metadata:
name: integration test metadata generation name: integration test metadata generation
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
outputs: outputs:
split-groups: ${{ steps.generate-split-groups.outputs.split-groups }} split-groups: ${{ steps.generate-split-groups.outputs.split-groups }}
@@ -45,7 +47,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: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
needs: needs:
- integration-metadata - integration-metadata
@@ -87,25 +89,25 @@ jobs:
steps: steps:
- name: checkout dev - name: checkout dev
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: "3.9" python-version: "3.10"
- name: Install python dependencies - name: Install python dependencies
run: | run: |
pip install --user --upgrade pip pip install --user --upgrade pip
pip --version pip --version
pip install tox pip install hatch
tox --version hatch --version
- name: Run postgres setup script - name: Run postgres setup script
run: | run: |
./test/setup_db.sh ./scripts/setup_db.sh
env: env:
PGHOST: localhost PGHOST: localhost
PGPORT: 5432 PGPORT: 5432
@@ -117,17 +119,17 @@ jobs:
# integration tests generate a ton of logs in different files. the next step will find them all. # integration tests generate a ton of logs in different files. the next step will find them all.
# we actually care if these pass, because the normal test run doesn't usually include many json log outputs # we actually care if these pass, because the normal test run doesn't usually include many json log outputs
- name: Run integration tests - name: Run integration tests
uses: nick-fields/retry@v3 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # nick-fields/retry@v3
with: with:
timeout_minutes: 30 timeout_minutes: 30
max_attempts: 3 max_attempts: 3
command: tox -e integration -- -nauto command: cd core && hatch run ci:integration-tests -- -nauto
env: env:
PYTEST_ADDOPTS: ${{ format('--splits {0} --group {1}', env.PYTHON_INTEGRATION_TEST_WORKERS, matrix.split-group) }} PYTEST_ADDOPTS: ${{ format('--splits {0} --group {1}', env.PYTHON_INTEGRATION_TEST_WORKERS, matrix.split-group) }}
test-schema-report: test-schema-report:
name: Log Schema Test Suite name: Log Schema Test Suite
runs-on: ${{ vars.UBUNTU_LATEST }} runs-on: ubuntu-latest
needs: test-schema needs: test-schema
steps: steps:
- name: "[Notification] Log test suite passes" - name: "[Notification] Log test suite passes"

View File

@@ -14,34 +14,33 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
branch: branch:
description: 'Branch to check out' description: "Branch to check out"
type: string type: string
required: true required: true
default: 'main' default: "main"
test_path: test_path:
description: 'Path to single test to run (ex: tests/functional/retry/test_retry.py::TestRetry::test_fail_fast)' description: "Path to single test to run (ex: tests/functional/retry/test_retry.py::TestRetry::test_fail_fast)"
type: string type: string
required: true required: true
default: 'tests/functional/...' default: "tests/functional/..."
python_version: python_version:
description: 'Version of Python to Test Against' description: "Version of Python to Test Against"
type: choice type: choice
options: options:
- '3.9' - "3.10"
- '3.10' - "3.11"
- '3.11'
os: os:
description: 'OS to run test in' description: "OS to run test in"
type: choice type: choice
options: options:
- 'ubuntu-latest' - "ubuntu-latest"
- 'macos-14' - "macos-14"
- 'windows-latest' - "windows-latest"
num_runs_per_batch: num_runs_per_batch:
description: 'Max number of times to run the test per batch. We always run 10 batches.' description: "Max number of times to run the test per batch. We always run 10 batches."
type: number type: number
required: true required: true
default: '50' default: "50"
permissions: read-all permissions: read-all
@@ -82,30 +81,37 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # actions/checkout@v4
with: with:
ref: ${{ inputs.branch }} ref: ${{ inputs.branch }}
- name: "Setup Python" - name: "Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # actions/setup-python@v6
with: with:
python-version: "${{ inputs.python_version }}" python-version: "${{ inputs.python_version }}"
- name: "Install hatch"
run: python -m pip install --user --upgrade pip hatch
- name: "Setup Dev Environment" - name: "Setup Dev Environment"
run: make dev run: |
cd core
hatch run setup
- name: "Set up postgres (linux)" - name: "Set up postgres (linux)"
if: inputs.os == '${{ vars.UBUNTU_LATEST }}' if: inputs.os == '${{ vars.UBUNTU_LATEST }}'
run: make setup-db run: |
cd core
hatch run setup-db
# mac and windows don't use make due to limitations with docker with those runners in GitHub # mac and windows don't use make due to limitations with docker with those runners in GitHub
- name: Set up postgres (macos) - name: Set up postgres (macos)
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: nick-fields/retry@v3 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # nick-fields/retry@v3
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3
command: ./test/setup_db.sh command: ./scripts/setup_db.sh
- name: "Set up postgres (windows)" - name: "Set up postgres (windows)"
if: inputs.os == 'windows-latest' if: inputs.os == 'windows-latest'

2
.gitignore vendored
View File

@@ -15,6 +15,7 @@ build/
!core/dbt/docs/build !core/dbt/docs/build
develop-eggs/ develop-eggs/
dist/ dist/
dist-*/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
@@ -95,6 +96,7 @@ target/
# pycharm # pycharm
.idea/ .idea/
venv/ venv/
.venv*/
# AWS credentials # AWS credentials
.aws/ .aws/

View File

@@ -3,7 +3,7 @@
exclude: ^(core/dbt/docs/build/|core/dbt/common/events/types_pb2.py|core/dbt/adapters/events/adapter_types_pb2.py) exclude: ^(core/dbt/docs/build/|core/dbt/common/events/types_pb2.py|core/dbt/adapters/events/adapter_types_pb2.py)
# Force all unspecified python hooks to run python 3.9 # Force all unspecified python hooks to run python 3.10
default_language_version: default_language_version:
python: python3 python: python3
@@ -20,52 +20,72 @@ repos:
exclude_types: exclude_types:
- "markdown" - "markdown"
- id: check-case-conflict - id: check-case-conflict
- repo: https://github.com/pycqa/isort # local hooks are used to run the hooks in the local environment instead of a pre-commit isolated one.
# rev must match what's in dev-requirements.txt # This ensures that the hooks are run with the same version of the dependencies as the local environment
rev: 5.13.2 # without having to manually keep them in sync.
- repo: local
hooks: hooks:
# Formatter/linter/type-checker pins live in the pyproject.dev optional dependency.
- id: isort - id: isort
- repo: https://github.com/psf/black name: isort
# rev must match what's in dev-requirements.txt entry: python -m isort
rev: 24.3.0 language: system
hooks: types: [python]
- id: black - id: black
- id: black name: black
alias: black-check entry: python -m black
stages: [manual] language: system
types: [python]
- id: black-check
name: black-check
entry: python -m black
args: args:
- "--check" - "--check"
- "--diff" - "--diff"
- repo: https://github.com/pycqa/flake8 language: system
# rev must match what's in dev-requirements.txt
rev: 4.0.1
hooks:
- id: flake8
- id: flake8
alias: flake8-check
stages: [manual] stages: [manual]
- repo: https://github.com/pre-commit/mirrors-mypy types: [python]
# rev must match what's in dev-requirements.txt - id: flake8
rev: v1.4.1 name: flake8
hooks: entry: python -m flake8
- id: mypy language: system
types: [python]
- id: flake8-check
name: flake8-check
entry: python -m flake8
language: system
stages: [manual]
types: [python]
# N.B.: Mypy is... a bit fragile. # N.B.: Mypy is... a bit fragile.
# #
# By using `language: system` we run this hook in the local # By using `language: system` we run this hook in the local
# environment instead of a pre-commit isolated one. This is needed # environment instead of a pre-commit isolated one. This is needed
# to ensure mypy correctly parses the project. # to ensure mypy correctly parses the project.
#
# It may cause trouble # It may cause trouble
# in that it adds environmental variables out of our control to the # in that it adds environmental variables out of our control to the
# mix. Unfortunately, there's nothing we can do about per pre-commit's # mix. Unfortunately, there's nothing we can do about per pre-commit's
# author. # author.
# See https://github.com/pre-commit/pre-commit/issues/730 for details. # See https://github.com/pre-commit/pre-commit/issues/730 for details.
- id: mypy
name: mypy
entry: python -m mypy
args: [--show-error-codes] args: [--show-error-codes]
files: ^core/dbt/ files: ^core/dbt/
language: system language: system
- id: mypy types: [python]
alias: mypy-check - id: mypy-check
stages: [manual] name: mypy-check
entry: python -m mypy
args: [--show-error-codes, --pretty] args: [--show-error-codes, --pretty]
files: ^core/dbt/ files: ^core/dbt/
language: system language: system
stages: [manual]
types: [python]
- id: no_versioned_artifact_resource_imports
name: no_versioned_artifact_resource_imports
entry: python scripts/pre-commit-hooks/no_versioned_artifact_resource_imports.py
language: system
files: ^core/dbt/
types: [python]
pass_filenames: true

View File

@@ -6,7 +6,6 @@ Most of the python code in the repository is within the `core/dbt` directory.
- [`single python files`](core/dbt/README.md): A number of individual files, such as 'compilation.py' and 'exceptions.py' - [`single python files`](core/dbt/README.md): A number of individual files, such as 'compilation.py' and 'exceptions.py'
The main subdirectories of core/dbt: The main subdirectories of core/dbt:
- [`adapters`](core/dbt/adapters/README.md): Define base classes for behavior that is likely to differ across databases
- [`clients`](core/dbt/clients/README.md): Interface with dependencies (agate, jinja) or across operating systems - [`clients`](core/dbt/clients/README.md): Interface with dependencies (agate, jinja) or across operating systems
- [`config`](core/dbt/config/README.md): Reconcile user-supplied configuration from connection profiles, project files, and Jinja macros - [`config`](core/dbt/config/README.md): Reconcile user-supplied configuration from connection profiles, project files, and Jinja macros
- [`context`](core/dbt/context/README.md): Build and expose dbt-specific Jinja functionality - [`context`](core/dbt/context/README.md): Build and expose dbt-specific Jinja functionality
@@ -18,10 +17,6 @@ The main subdirectories of core/dbt:
- [`parser`](core/dbt/parser/README.md): Read project files, validate, construct python objects - [`parser`](core/dbt/parser/README.md): Read project files, validate, construct python objects
- [`task`](core/dbt/task/README.md): Set forth the actions that dbt can perform when invoked - [`task`](core/dbt/task/README.md): Set forth the actions that dbt can perform when invoked
Legacy tests are found in the 'test' directory:
- [`unit tests`](core/dbt/test/unit/README.md): Unit tests
- [`integration tests`](core/dbt/test/integration/README.md): Integration tests
### Invoking dbt ### Invoking dbt
The "tasks" map to top-level dbt commands. So `dbt run` => task.run.RunTask, etc. Some are more like abstract base classes (GraphRunnableTask, for example) but all the concrete types outside of task should map to tasks. Currently one executes at a time. The tasks kick off their “Runners” and those do execute in parallel. The parallelism is managed via a thread pool, in GraphRunnableTask. The "tasks" map to top-level dbt commands. So `dbt run` => task.run.RunTask, etc. Some are more like abstract base classes (GraphRunnableTask, for example) but all the concrete types outside of task should map to tasks. Currently one executes at a time. The tasks kick off their “Runners” and those do execute in parallel. The parallelism is managed via a thread pool, in GraphRunnableTask.
@@ -40,16 +35,15 @@ Each adapter plugin is a standalone python package that includes:
- `dbt/include/[name]`: A "sub-global" dbt project, of YAML and SQL files, that reimplements Jinja macros to use the adapter's supported SQL syntax - `dbt/include/[name]`: A "sub-global" dbt project, of YAML and SQL files, that reimplements Jinja macros to use the adapter's supported SQL syntax
- `dbt/adapters/[name]`: Python modules that inherit, and optionally reimplement, the base adapter classes defined in dbt-core - `dbt/adapters/[name]`: Python modules that inherit, and optionally reimplement, the base adapter classes defined in dbt-core
- `setup.py` - `pyproject.toml`
The Postgres adapter code is the most central, and many of its implementations are used as the default defined in the dbt-core global project. The greater the distance of a data technology from Postgres, the more its adapter plugin may need to reimplement. The Postgres adapter code is the most central, and many of its implementations are used as the default defined in the dbt-core global project. The greater the distance of a data technology from Postgres, the more its adapter plugin may need to reimplement.
## Testing dbt ## Testing dbt
The [`test/`](test/) subdirectory includes unit and integration tests that run as continuous integration checks against open pull requests. Unit tests check mock inputs and outputs of specific python functions. Integration tests perform end-to-end dbt invocations against real adapters (Postgres, Redshift, Snowflake, BigQuery) and assert that the results match expectations. See [the contributing guide](CONTRIBUTING.md) for a step-by-step walkthrough of setting up a local development and testing environment. The [`tests/`](tests/) subdirectory includes unit and fuctional tests that run as continuous integration checks against open pull requests. Unit tests check mock inputs and outputs of specific python functions. Functional tests perform end-to-end dbt invocations against real adapters (Postgres) and assert that the results match expectations. See [the contributing guide](CONTRIBUTING.md) for a step-by-step walkthrough of setting up a local development and testing environment.
## Everything else ## Everything else
- [docker](docker/): All dbt versions are published as Docker images on DockerHub. This subfolder contains the `Dockerfile` (constant) and `requirements.txt` (one for each version). - [docker](docker/): All dbt versions are published as Docker images on DockerHub. This subfolder contains the `Dockerfile` (constant) and `requirements.txt` (one for each version).
- [etc](etc/): Images for README
- [scripts](scripts/): Helper scripts for testing, releasing, and producing JSON schemas. These are not included in distributions of dbt, nor are they rigorously tested—they're just handy tools for the dbt maintainers :) - [scripts](scripts/): Helper scripts for testing, releasing, and producing JSON schemas. These are not included in distributions of dbt, nor are they rigorously tested—they're just handy tools for the dbt maintainers :)

View File

@@ -9,7 +9,7 @@
For information on prior major and minor releases, see their changelogs: For information on prior major and minor releases, see their changelogs:
* [1.11](https://github.com/dbt-labs/dbt-core/blob/1.11.latest/CHANGELOG.md)
* [1.10](https://github.com/dbt-labs/dbt-core/blob/1.10.latest/CHANGELOG.md) * [1.10](https://github.com/dbt-labs/dbt-core/blob/1.10.latest/CHANGELOG.md)
* [1.9](https://github.com/dbt-labs/dbt-core/blob/1.9.latest/CHANGELOG.md) * [1.9](https://github.com/dbt-labs/dbt-core/blob/1.9.latest/CHANGELOG.md)
* [1.8](https://github.com/dbt-labs/dbt-core/blob/1.8.latest/CHANGELOG.md) * [1.8](https://github.com/dbt-labs/dbt-core/blob/1.8.latest/CHANGELOG.md)

View File

@@ -2,21 +2,39 @@
`dbt-core` is open source software. It is what it is today because community members have opened issues, provided feedback, and [contributed to the knowledge loop](https://www.getdbt.com/dbt-labs/values/). Whether you are a seasoned open source contributor or a first-time committer, we welcome and encourage you to contribute code, documentation, ideas, or problem statements to this project. `dbt-core` is open source software. It is what it is today because community members have opened issues, provided feedback, and [contributed to the knowledge loop](https://www.getdbt.com/dbt-labs/values/). Whether you are a seasoned open source contributor or a first-time committer, we welcome and encourage you to contribute code, documentation, ideas, or problem statements to this project.
1. [About this document](#about-this-document) - [Contributing to `dbt-core`](#contributing-to-dbt-core)
2. [Getting the code](#getting-the-code) - [About this document](#about-this-document)
3. [Setting up an environment](#setting-up-an-environment) - [Notes](#notes)
4. [Running dbt-core in development](#running-dbt-core-in-development) - [Getting the code](#getting-the-code)
5. [Testing dbt-core](#testing) - [Installing git](#installing-git)
6. [Debugging](#debugging) - [External contributors](#external-contributors)
7. [Adding or modifying a changelog entry](#adding-or-modifying-a-changelog-entry) - [dbt Labs contributors](#dbt-labs-contributors)
8. [Submitting a Pull Request](#submitting-a-pull-request) - [Setting up an environment](#setting-up-an-environment)
9. [Troubleshooting Tips](#troubleshooting-tips) - [Tools](#tools)
- [Virtual environments](#virtual-environments)
- [Docker and `docker-compose`](#docker-and-docker-compose)
- [Postgres (optional)](#postgres-optional)
- [Running `dbt-core` in development](#running-dbt-core-in-development)
- [Installation](#installation)
- [Running `dbt-core`](#running-dbt-core)
- [Testing](#testing)
- [Initial setup](#initial-setup)
- [Test commands](#test-commands)
- [Hatch scripts](#hatch-scripts)
- [`pre-commit`](#pre-commit)
- [`pytest`](#pytest)
- [Unit, Integration, Functional?](#unit-integration-functional)
- [Debugging](#debugging)
- [Assorted development tips](#assorted-development-tips)
- [Adding or modifying a CHANGELOG Entry](#adding-or-modifying-a-changelog-entry)
- [Submitting a Pull Request](#submitting-a-pull-request)
- [Troubleshooting Tips](#troubleshooting-tips)
## About this document ## About this document
There are many ways to contribute to the ongoing development of `dbt-core`, such as by participating in discussions and issues. We encourage you to first read our higher-level document: ["Expectations for Open Source Contributors"](https://docs.getdbt.com/docs/contributing/oss-expectations). There are many ways to contribute to the ongoing development of `dbt-core`, such as by participating in discussions and issues. We encourage you to first read our higher-level document: ["Expectations for Open Source Contributors"](https://docs.getdbt.com/docs/contributing/oss-expectations).
The rest of this document serves as a more granular guide for contributing code changes to `dbt-core` (this repository). It is not intended as a guide for using `dbt-core`, and some pieces assume a level of familiarity with Python development (virtualenvs, `pip`, etc). Specific code snippets in this guide assume you are using macOS or Linux and are comfortable with the command line. The rest of this document serves as a more granular guide for contributing code changes to `dbt-core` (this repository). It is not intended as a guide for using `dbt-core`, and some pieces assume a level of familiarity with Python development and package managers. Specific code snippets in this guide assume you are using macOS or Linux and are comfortable with the command line.
If you get stuck, we're happy to help! Drop us a line in the `#dbt-core-development` channel in the [dbt Community Slack](https://community.getdbt.com). If you get stuck, we're happy to help! Drop us a line in the `#dbt-core-development` channel in the [dbt Community Slack](https://community.getdbt.com).
@@ -55,28 +73,22 @@ 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.8, 3.9, 3.10 and 3.11 - [`hatch`](https://hatch.pypa.io/) for build backend, environment management, and running tests across Python versions (3.10, 3.11, 3.12, and 3.13)
- [`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
- [`mypy`](https://mypy.readthedocs.io/en/stable/) for static type checking - [`mypy`](https://mypy.readthedocs.io/en/stable/) for static type checking
- [`pre-commit`](https://pre-commit.com) to easily run those checks - [`pre-commit`](https://pre-commit.com) to easily run those checks
- [`changie`](https://changie.dev/) to create changelog entries, without merge conflicts - [`changie`](https://changie.dev/) to create changelog entries, without merge conflicts
- [`make`](https://users.cs.duke.edu/~ola/courses/programming/Makefiles/Makefiles.html) to run multiple setup or test steps in combination. Don't worry too much, nobody _really_ understands how `make` works, and our Makefile aims to be super simple.
- [GitHub Actions](https://github.com/features/actions) for automating tests and checks, once a PR is pushed to the `dbt-core` repository - [GitHub Actions](https://github.com/features/actions) for automating tests and checks, once a PR is pushed to the `dbt-core` repository
A deep understanding of these tools in not required to effectively contribute to `dbt-core`, but we recommend checking out the attached documentation if you're interested in learning more about each one. A deep understanding of these tools in not required to effectively contribute to `dbt-core`, but we recommend checking out the attached documentation if you're interested in learning more about each one.
#### Virtual environments #### Virtual environments
We strongly recommend using virtual environments when developing code in `dbt-core`. We recommend creating this virtualenv dbt-core uses [Hatch](https://hatch.pypa.io/) for dependency and environment management. Hatch automatically creates and manages isolated environments for development, testing, and building, so you don't need to manually create virtual environments.
in the root of the `dbt-core` repository. To create a new virtualenv, run:
```sh
python3 -m venv env
source env/bin/activate
```
This will create and activate a new Python virtual environment. For more information on how Hatch manages environments, see the [Hatch environment documentation](https://hatch.pypa.io/latest/environment/).
#### Docker and `docker-compose` #### Docker and `docker-compose`
@@ -95,22 +107,42 @@ 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): First make sure you have Python 3.10 or later installed. Ensure you have the latest version of pip installed with `pip install --upgrade pip`. Next, install `hatch`. Finally set up `dbt-core` for development:
```sh ```sh
make dev cd core
hatch run setup
``` ```
or, alternatively:
This will install all development dependencies and set up pre-commit hooks.
By default, hatch will use whatever Python version is active in your environment. To specify a particular Python version, set the `HATCH_PYTHON` environment variable:
```sh ```sh
pip install -r dev-requirements.txt -r editable-requirements.txt export HATCH_PYTHON=3.12
pre-commit install hatch env create
``` ```
Or add it to your shell profile (e.g., `~/.zshrc` or `~/.bashrc`) for persistence.
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.
#### Building dbt-core
dbt-core uses [Hatch](https://hatch.pypa.io/) (specifically `hatchling`) as its build backend. To build distribution packages:
```sh
cd core
hatch build
```
This will create both wheel (`.whl`) and source distribution (`.tar.gz`) files in the `dist/` directory.
The build configuration is defined in `core/pyproject.toml`. You can also use the standard `python -m build` command if you prefer.
### Running `dbt-core` ### Running `dbt-core`
With your virtualenv activated, the `dbt` script should point back to the source code you've cloned on your machine. You can verify this by running `which dbt`. This command should show you a path to an executable in your virtualenv. Once you've run `hatch run setup`, the `dbt` command will be available in your PATH. You can verify this by running `which dbt`.
Configure your [profile](https://docs.getdbt.com/docs/configure-your-profile) as necessary to connect to your target databases. It may be a good idea to add a new profile pointing to a local Postgres instance, or a specific test sandbox within your data warehouse if appropriate. Make sure to create a profile before running integration tests. Configure your [profile](https://docs.getdbt.com/docs/configure-your-profile) as necessary to connect to your target databases. It may be a good idea to add a new profile pointing to a local Postgres instance, or a specific test sandbox within your data warehouse if appropriate. Make sure to create a profile before running integration tests.
@@ -128,45 +160,78 @@ Although `dbt-core` works with a number of different databases, you won't need t
Postgres offers the easiest way to test most `dbt-core` functionality today. They are the fastest to run, and the easiest to set up. To run the Postgres integration tests, you'll have to do one extra step of setting up the test database: Postgres offers the easiest way to test most `dbt-core` functionality today. They are the fastest to run, and the easiest to set up. To run the Postgres integration tests, you'll have to do one extra step of setting up the test database:
```sh ```sh
make setup-db cd core
hatch run setup-db
``` ```
or, alternatively:
Alternatively, you can run the setup commands directly:
```sh ```sh
docker-compose up -d database docker-compose up -d database
PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres bash test/setup_db.sh PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres bash scripts/setup_db.sh
``` ```
### Test commands ### Test commands
There are a few methods for running tests locally. There are a few methods for running tests locally.
#### Makefile #### Hatch scripts
There are multiple targets in the Makefile to run common test suites and code The primary way to run tests and checks is using hatch scripts (defined in `core/hatch.toml`):
checks, most notably:
```sh ```sh
# Runs unit tests with py38 and code checks in parallel. cd core
make test
# Runs postgres integration tests with py38 in "fail fast" mode.
make integration
```
> These make targets assume you have a local installation of a recent version of [`tox`](https://tox.readthedocs.io/en/latest/) for unit/integration testing and pre-commit for code quality checks,
> unless you use choose a Docker container to run tests. Run `make help` for more info.
Check out the other targets in the Makefile to see other commonly used test # Run all unit tests
suites. hatch run unit-tests
# Run unit tests and all code quality checks
hatch run test
# Run integration tests
hatch run integration-tests
# Run integration tests in fail-fast mode
hatch run integration-tests-fail-fast
# Run linting checks only
hatch run lint
hatch run flake8
hatch run mypy
hatch run black
# Run all pre-commit hooks
hatch run code-quality
# Clean build artifacts
hatch run clean
```
Hatch manages isolated environments and dependencies automatically. The commands above use the `default` environment which is recommended for most local development.
**Using the `ci` environment (optional)**
If you need to replicate exactly what runs in GitHub Actions (e.g., with coverage reporting), use the `ci` environment:
```sh
cd core
# Run unit tests with coverage
hatch run ci:unit-tests
# Run unit tests with a specific Python version
hatch run +py=3.11 ci:unit-tests
```
> **Note:** Most developers should use the default environment (`hatch run unit-tests`). The `ci` environment is primarily for debugging CI failures or running tests with coverage.
#### `pre-commit` #### `pre-commit`
[`pre-commit`](https://pre-commit.com) takes care of running all code-checks for formatting and linting. Run `make dev` to install `pre-commit` in your local environment (we recommend running this command with a python virtual environment active). This command installs several pip executables including black, mypy, and flake8. Once this is done you can use any of the linter-based make targets as well as a git pre-commit hook that will ensure proper formatting and linting.
#### `tox` [`pre-commit`](https://pre-commit.com) takes care of running all code-checks for formatting and linting. Run `hatch run setup` to install `pre-commit` in your local environment (we recommend running this command with a python virtual environment active). This installs several pip executables including black, mypy, and flake8. Once installed, hooks will run automatically on `git commit`, or you can run them manually with `hatch run code-quality`.
[`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.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 py38`. The configuration for these tests in located in `tox.ini`.
#### `pytest` #### `pytest`
Finally, you can also run a specific test or group of tests using [`pytest`](https://docs.pytest.org/en/latest/) directly. With a virtualenv active and dev dependencies installed you can do things like: Finally, you can also run a specific test or group of tests using [`pytest`](https://docs.pytest.org/en/latest/) directly. After running `hatch run setup`, you can run pytest commands like:
```sh ```sh
# run all unit tests in a file # run all unit tests in a file
@@ -224,7 +289,9 @@ Code can be merged into the current development branch `main` by opening a pull
Automated tests run via GitHub Actions. If you're a first-time contributor, all tests (including code checks and unit tests) will require a maintainer to approve. Changes in the `dbt-core` repository trigger integration tests against Postgres. dbt Labs also provides CI environments in which to test changes to other adapters, triggered by PRs in those adapters' repositories, as well as periodic maintenance checks of each adapter in concert with the latest `dbt-core` code changes. Automated tests run via GitHub Actions. If you're a first-time contributor, all tests (including code checks and unit tests) will require a maintainer to approve. Changes in the `dbt-core` repository trigger integration tests against Postgres. dbt Labs also provides CI environments in which to test changes to other adapters, triggered by PRs in those adapters' repositories, as well as periodic maintenance checks of each adapter in concert with the latest `dbt-core` code changes.
Once all tests are passing and your PR has been approved, a `dbt-core` maintainer will merge your changes into the active development branch. And that's it! Happy developing :tada: We require signed git commits. See docs [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) for setting up code signing.
Once all tests are passing, all comments are resolved, and your PR has been approved, a `dbt-core` maintainer will merge your changes into the active development branch. And that's it! Happy developing :tada:
## Troubleshooting Tips ## Troubleshooting Tips

View File

@@ -33,9 +33,6 @@ RUN apt-get update \
python-is-python3 \ python-is-python3 \
python-dev-is-python3 \ python-dev-is-python3 \
python3-pip \ python3-pip \
python3.9 \
python3.9-dev \
python3.9-venv \
python3.10 \ python3.10 \
python3.10-dev \ python3.10-dev \
python3.10-venv \ python3.10-venv \
@@ -50,7 +47,7 @@ RUN curl -LO https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_V
&& tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
RUN pip3 install -U tox wheel six setuptools pre-commit RUN pip3 install -U hatch wheel pre-commit
# These args are passed in via docker-compose, which reads then from the .env file. # These args are passed in via docker-compose, which reads then from the .env file.
# On Linux, run `make .env` to create the .env file for the current user. # On Linux, run `make .env` to create the .env file for the current user.
@@ -65,7 +62,6 @@ RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
useradd -mU -l dbt_test_user; \ useradd -mU -l dbt_test_user; \
fi fi
RUN mkdir /usr/app && chown dbt_test_user /usr/app RUN mkdir /usr/app && chown dbt_test_user /usr/app
RUN mkdir /home/tox && chown dbt_test_user /home/tox
WORKDIR /usr/app WORKDIR /usr/app
VOLUME /usr/app VOLUME /usr/app

1
LICENSE Symbolic link
View File

@@ -0,0 +1 @@
core/LICENSE

161
Makefile
View File

@@ -1,146 +1,95 @@
# ============================================================================
# DEPRECATED: This Makefile is maintained for backwards compatibility only.
#
# dbt-core now uses Hatch for task management and development workflows.
# Please migrate to using hatch commands directly:
#
# make dev → cd core && hatch run setup
# make unit → cd core && hatch run unit-tests
# make test → cd core && hatch run test
# make integration → cd core && hatch run integration-tests
# make lint → cd core && hatch run lint
# make code_quality → cd core && hatch run code-quality
# make setup-db → cd core && hatch run setup-db
# make clean → cd core && hatch run clean
#
# See core/pyproject.toml [tool.hatch.envs.default.scripts] for all available
# commands and CONTRIBUTING.md for detailed usage instructions.
#
# This Makefile will be removed in a future version of dbt-core.
# ============================================================================
.DEFAULT_GOAL:=help .DEFAULT_GOAL:=help
# Optional flag to run target in a docker container.
# (example `make test USE_DOCKER=true`)
ifeq ($(USE_DOCKER),true)
DOCKER_CMD := docker-compose run --rm test
endif
#
# To override CI_flags, create a file at this repo's root dir named `makefile.test.env`. Fill it
# with any ENV_VAR overrides required by your test environment, e.g.
# DBT_TEST_USER_1=user
# LOG_DIR="dir with a space in it"
#
# Warn: Restrict each line to one variable only.
#
ifeq (./makefile.test.env,$(wildcard ./makefile.test.env))
include ./makefile.test.env
endif
CI_FLAGS =\
DBT_TEST_USER_1=$(if $(DBT_TEST_USER_1),$(DBT_TEST_USER_1),dbt_test_user_1)\
DBT_TEST_USER_2=$(if $(DBT_TEST_USER_2),$(DBT_TEST_USER_2),dbt_test_user_2)\
DBT_TEST_USER_3=$(if $(DBT_TEST_USER_3),$(DBT_TEST_USER_3),dbt_test_user_3)\
RUSTFLAGS=$(if $(RUSTFLAGS),$(RUSTFLAGS),"-D warnings")\
LOG_DIR=$(if $(LOG_DIR),$(LOG_DIR),./logs)\
DBT_LOG_FORMAT=$(if $(DBT_LOG_FORMAT),$(DBT_LOG_FORMAT),json)
.PHONY: dev_req .PHONY: dev_req
dev_req: ## Installs dbt-* packages in develop mode along with only development dependencies. dev_req: ## Installs dbt-* packages in develop mode along with only development dependencies.
@\ @cd core && hatch run dev-req
pip install -r dev-requirements.txt -r editable-requirements.txt
.PHONY: dev .PHONY: dev
dev: dev_req ## Installs dbt-* packages in develop mode along with development dependencies and pre-commit. dev: ## Installs dbt-* packages in develop mode along with development dependencies and pre-commit.
@\ @cd core && hatch run setup
$(DOCKER_CMD) pre-commit install
.PHONY: dev-uninstall .PHONY: dev-uninstall
dev-uninstall: ## Uninstall all packages in venv except for build tools dev-uninstall: ## Uninstall all packages in venv except for build tools
@\ @pip freeze | grep -v "^-e" | cut -d "@" -f1 | xargs pip uninstall -y; \
pip freeze | grep -v "^-e" | cut -d "@" -f1 | xargs pip uninstall -y; \
pip uninstall -y dbt-core pip uninstall -y dbt-core
.PHONY: mypy .PHONY: mypy
mypy: .env ## Runs mypy against staged changes for static type checking. mypy: ## Runs mypy against staged changes for static type checking.
@\ @cd core && hatch run mypy
$(DOCKER_CMD) pre-commit run --hook-stage manual mypy-check | grep -v "INFO"
.PHONY: flake8 .PHONY: flake8
flake8: .env ## Runs flake8 against staged changes to enforce style guide. flake8: ## Runs flake8 against staged changes to enforce style guide.
@\ @cd core && hatch run flake8
$(DOCKER_CMD) pre-commit run --hook-stage manual flake8-check | grep -v "INFO"
.PHONY: black .PHONY: black
black: .env ## Runs black against staged changes to enforce style guide. black: ## Runs black against staged changes to enforce style guide.
@\ @cd core && hatch run black
$(DOCKER_CMD) pre-commit run --hook-stage manual black-check -v | grep -v "INFO"
.PHONY: lint .PHONY: lint
lint: .env ## Runs flake8 and mypy code checks against staged changes. lint: ## Runs flake8 and mypy code checks against staged changes.
@\ @cd core && hatch run lint
$(DOCKER_CMD) pre-commit run flake8-check --hook-stage manual | grep -v "INFO"; \
$(DOCKER_CMD) pre-commit run mypy-check --hook-stage manual | grep -v "INFO" .PHONY: code_quality
code_quality: ## Runs all pre-commit hooks against all files.
@cd core && hatch run code-quality
.PHONY: unit .PHONY: unit
unit: .env ## Runs unit tests with py unit: ## Runs unit tests with py
@\ @cd core && hatch run unit-tests
$(DOCKER_CMD) tox -e py
.PHONY: test .PHONY: test
test: .env ## Runs unit tests with py and code checks against staged changes. test: ## Runs unit tests with py and code checks against staged changes.
@\ @cd core && hatch run test
$(DOCKER_CMD) tox -e py; \
$(DOCKER_CMD) pre-commit run black-check --hook-stage manual | grep -v "INFO"; \
$(DOCKER_CMD) pre-commit run flake8-check --hook-stage manual | grep -v "INFO"; \
$(DOCKER_CMD) pre-commit run mypy-check --hook-stage manual | grep -v "INFO"
.PHONY: integration .PHONY: integration
integration: .env ## Runs core integration tests using postgres with py-integration integration: ## Runs core integration tests using postgres with py-integration
@\ @cd core && hatch run integration-tests
$(CI_FLAGS) $(DOCKER_CMD) tox -e py-integration -- -nauto
.PHONY: integration-fail-fast .PHONY: integration-fail-fast
integration-fail-fast: .env ## Runs core integration tests using postgres with py-integration in "fail fast" mode. integration-fail-fast: ## Runs core integration tests using postgres with py-integration in "fail fast" mode.
@\ @cd core && hatch run integration-tests-fail-fast
$(DOCKER_CMD) tox -e py-integration -- -x -nauto
.PHONY: interop
interop: clean
@\
mkdir $(LOG_DIR) && \
$(CI_FLAGS) $(DOCKER_CMD) tox -e py-integration -- -nauto && \
LOG_DIR=$(LOG_DIR) cargo run --manifest-path test/interop/log_parsing/Cargo.toml
.PHONY: setup-db .PHONY: setup-db
setup-db: ## Setup Postgres database with docker-compose for system testing. setup-db: ## Setup Postgres database with docker-compose for system testing.
@\ @cd core && hatch run setup-db
docker-compose up -d database && \
PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres bash test/setup_db.sh
# This rule creates a file named .env that is used by docker-compose for passing
# the USER_ID and GROUP_ID arguments to the Docker image.
.env: ## Setup step for using using docker-compose with make target.
@touch .env
ifneq ($(OS),Windows_NT)
ifneq ($(shell uname -s), Darwin)
@echo USER_ID=$(shell id -u) > .env
@echo GROUP_ID=$(shell id -g) >> .env
endif
endif
.PHONY: clean .PHONY: clean
clean: ## Resets development environment. clean: ## Resets development environment.
@echo 'cleaning repo...' @cd core && hatch run clean
@rm -f .coverage
@rm -f .coverage.*
@rm -rf .eggs/
@rm -f .env
@rm -rf .tox/
@rm -rf build/
@rm -rf dbt.egg-info/
@rm -f dbt_project.yml
@rm -rf dist/
@rm -f htmlcov/*.{css,html,js,json,png}
@rm -rf logs/
@rm -rf target/
@find . -type f -name '*.pyc' -delete
@find . -type d -name '__pycache__' -depth -delete
@echo 'done.'
.PHONY: json_schema
json_schema: ## Update generated JSON schema using code changes.
@cd core && hatch run json-schema
.PHONY: help .PHONY: help
help: ## Show this help message. help: ## Show this help message.
@echo 'usage: make [target] [USE_DOCKER=true]' @echo 'usage: make [target]'
@echo
@echo 'DEPRECATED: This Makefile is a compatibility shim.'
@echo 'Please use "cd core && hatch run <command>" directly.'
@echo @echo
@echo 'targets:' @echo 'targets:'
@grep -E '^[8+a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @grep -E '^[8+a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@echo @echo
@echo 'options:' @echo 'For more information, see CONTRIBUTING.md'
@echo 'use USE_DOCKER=true to run target in a docker container'
.PHONY: json_schema
json_schema: ## Update generated JSON schema using code changes.
scripts/collect-artifact-schema.py --path schemas

View File

@@ -5,6 +5,7 @@
<a href="https://github.com/dbt-labs/dbt-core/actions/workflows/main.yml"> <a href="https://github.com/dbt-labs/dbt-core/actions/workflows/main.yml">
<img src="https://github.com/dbt-labs/dbt-core/actions/workflows/main.yml/badge.svg?event=push" alt="CI Badge"/> <img src="https://github.com/dbt-labs/dbt-core/actions/workflows/main.yml/badge.svg?event=push" alt="CI Badge"/>
</a> </a>
<a href="https://www.bestpractices.dev/projects/11095"><img src="https://www.bestpractices.dev/projects/11095/badge"></a>
</p> </p>
**[dbt](https://www.getdbt.com/)** enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications. **[dbt](https://www.getdbt.com/)** enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.

View File

@@ -2,39 +2,22 @@ ignore:
- ".github" - ".github"
- ".changes" - ".changes"
# Disable all status checks to prevent red X's in CI
# Coverage data is still uploaded and PR comments are still posted
coverage: coverage:
status: status:
project: project: off
default: patch: off
target: auto
threshold: 0.1% # Reduce noise by ignoring rounding errors in coverage drops
informational: true
patch:
default:
target: auto
threshold: 80%
informational: true
comment: comment:
layout: "header, diff, flags, components" # show component info in the PR comment layout: "header, diff, flags, components" # show component info in the PR comment
component_management: component_management:
default_rules: # default rules that will be inherited by all components
statuses:
- type: project # in this case every component that doens't have a status defined will have a project type one
target: auto
threshold: 0.1%
- type: patch
target: 80%
individual_components: individual_components:
- component_id: unittests - component_id: unittests
name: "Unit Tests" name: "Unit Tests"
flag_regexes: flag_regexes:
- "unit" - "unit"
statuses:
- type: patch
target: 80%
threshold: 5%
- component_id: integrationtests - component_id: integrationtests
name: "Integration Tests" name: "Integration Tests"
flag_regexes: flag_regexes:

View File

@@ -1,3 +0,0 @@
recursive-include dbt/include *.py *.sql *.yml *.html *.md .gitkeep .gitignore *.json
include dbt/py.typed
recursive-include dbt/task/docs *.html

View File

@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="https://raw.githubusercontent.com/dbt-labs/dbt-core/fa1ea14ddfb1d5ae319d5141844910dd53ab2834/etc/dbt-core.svg" alt="dbt logo" width="750"/> <img src="https://raw.githubusercontent.com/dbt-labs/dbt-core/fa1ea14ddfb1d5ae319d5141844910dd53ab2834/docs/images/dbt-core.svg" alt="dbt logo" width="750"/>
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/dbt-labs/dbt-core/actions/workflows/main.yml"> <a href="https://github.com/dbt-labs/dbt-core/actions/workflows/main.yml">
@@ -9,7 +9,7 @@
**[dbt](https://www.getdbt.com/)** enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications. **[dbt](https://www.getdbt.com/)** enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.
![architecture](https://raw.githubusercontent.com/dbt-labs/dbt-core/6c6649f9129d5d108aa3b0526f634cd8f3a9d1ed/etc/dbt-arch.png) ![architecture](https://raw.githubusercontent.com/dbt-labs/dbt-core/6c6649f9129d5d108aa3b0526f634cd8f3a9d1ed/docs/images/dbt-arch.png)
## Understanding dbt ## Understanding dbt
@@ -17,7 +17,7 @@ Analysts using dbt can transform their data by simply writing select statements,
These select statements, or "models", form a dbt project. Models frequently build on top of one another dbt makes it easy to [manage relationships](https://docs.getdbt.com/docs/ref) between models, and [visualize these relationships](https://docs.getdbt.com/docs/documentation), as well as assure the quality of your transformations through [testing](https://docs.getdbt.com/docs/testing). These select statements, or "models", form a dbt project. Models frequently build on top of one another dbt makes it easy to [manage relationships](https://docs.getdbt.com/docs/ref) between models, and [visualize these relationships](https://docs.getdbt.com/docs/documentation), as well as assure the quality of your transformations through [testing](https://docs.getdbt.com/docs/testing).
![dbt dag](https://raw.githubusercontent.com/dbt-labs/dbt-core/6c6649f9129d5d108aa3b0526f634cd8f3a9d1ed/etc/dbt-dag.png) ![dbt dag](https://raw.githubusercontent.com/dbt-labs/dbt-core/6c6649f9129d5d108aa3b0526f634cd8f3a9d1ed/docs/images/dbt-dag.png)
## Getting started ## Getting started

1
core/dbt/__version__.py Normal file
View File

@@ -0,0 +1 @@
version = "1.12.0a1"

View File

@@ -0,0 +1,26 @@
# type: ignore
"""Shim to allow support for both Pydantic 1 and Pydantic 2.
dbt-core must support both major versions of Pydantic because dbt-core users might be using an environment with
either version, and we can't restrict them to one or the other. Here, we essentially import all Pydantic objects
from version 1 that we use. Throughout the repo, we import these objects from this file instead of from Pydantic
directly, meaning that we essentially only use Pydantic 1 in dbt-core currently, but without forcing that restriction
on dbt users. The development environment for this repo should be pinned to Pydantic 1 to ensure devs get appropriate
type hints.
"""
from importlib.metadata import version
pydantic_version = version("pydantic")
# Pydantic uses semantic versioning, i.e. <major>.<minor>.<patch>, and we need to know the major
pydantic_major = pydantic_version.split(".")[0]
if pydantic_major == "1":
from pydantic import BaseSettings # noqa: F401
elif pydantic_major == "2":
from pydantic.v1 import BaseSettings # noqa: F401
else:
raise RuntimeError(
f"Currently only pydantic 1 and 2 are supported, found pydantic {pydantic_version}"
)

View File

@@ -25,6 +25,8 @@ from dbt.artifacts.resources.v1.config import (
NodeAndTestConfig, NodeAndTestConfig,
NodeConfig, NodeConfig,
TestConfig, TestConfig,
list_str,
metas,
) )
from dbt.artifacts.resources.v1.documentation import Documentation from dbt.artifacts.resources.v1.documentation import Documentation
from dbt.artifacts.resources.v1.exposure import ( from dbt.artifacts.resources.v1.exposure import (
@@ -33,6 +35,13 @@ from dbt.artifacts.resources.v1.exposure import (
ExposureType, ExposureType,
MaturityType, MaturityType,
) )
from dbt.artifacts.resources.v1.function import (
Function,
FunctionArgument,
FunctionConfig,
FunctionMandatory,
FunctionReturns,
)
from dbt.artifacts.resources.v1.generic_test import GenericTest, TestMetadata from dbt.artifacts.resources.v1.generic_test import GenericTest, TestMetadata
from dbt.artifacts.resources.v1.group import Group, GroupConfig from dbt.artifacts.resources.v1.group import Group, GroupConfig
from dbt.artifacts.resources.v1.hook import HookNode from dbt.artifacts.resources.v1.hook import HookNode
@@ -42,6 +51,7 @@ from dbt.artifacts.resources.v1.metric import (
ConversionTypeParams, ConversionTypeParams,
CumulativeTypeParams, CumulativeTypeParams,
Metric, Metric,
MetricAggregationParams,
MetricConfig, MetricConfig,
MetricInput, MetricInput,
MetricInputMeasure, MetricInputMeasure,
@@ -49,6 +59,7 @@ from dbt.artifacts.resources.v1.metric import (
MetricTypeParams, MetricTypeParams,
) )
from dbt.artifacts.resources.v1.model import ( from dbt.artifacts.resources.v1.model import (
CustomGranularity,
Model, Model,
ModelConfig, ModelConfig,
ModelFreshness, ModelFreshness,
@@ -66,6 +77,8 @@ from dbt.artifacts.resources.v1.saved_query import (
from dbt.artifacts.resources.v1.seed import Seed, SeedConfig from dbt.artifacts.resources.v1.seed import Seed, SeedConfig
from dbt.artifacts.resources.v1.semantic_layer_components import ( from dbt.artifacts.resources.v1.semantic_layer_components import (
FileSlice, FileSlice,
MeasureAggregationParameters,
NonAdditiveDimension,
SourceFileMetadata, SourceFileMetadata,
WhereFilter, WhereFilter,
WhereFilterIntersection, WhereFilterIntersection,
@@ -77,9 +90,8 @@ from dbt.artifacts.resources.v1.semantic_model import (
DimensionValidityParams, DimensionValidityParams,
Entity, Entity,
Measure, Measure,
MeasureAggregationParameters,
NodeRelation, NodeRelation,
NonAdditiveDimension, SemanticLayerElementConfig,
SemanticModel, SemanticModel,
SemanticModelConfig, SemanticModelConfig,
) )

View File

@@ -35,6 +35,7 @@ class NodeType(StrEnum):
SemanticModel = "semantic_model" SemanticModel = "semantic_model"
Unit = "unit_test" Unit = "unit_test"
Fixture = "fixture" Fixture = "fixture"
Function = "function"
def pluralize(self) -> str: def pluralize(self) -> str:
if self is self.Analysis: if self is self.Analysis:
@@ -78,3 +79,15 @@ class BatchSize(StrEnum):
def plural(self) -> str: def plural(self) -> str:
return str(self) + "s" return str(self) + "s"
class FunctionType(StrEnum):
Scalar = "scalar"
Aggregate = "aggregate"
Table = "table"
class FunctionVolatility(StrEnum):
Deterministic = "deterministic"
Stable = "stable"
NonDeterministic = "non-deterministic"

View File

@@ -249,6 +249,7 @@ class CompiledResource(ParsedResource):
refs: List[RefArgs] = field(default_factory=list) refs: List[RefArgs] = field(default_factory=list)
sources: List[List[str]] = field(default_factory=list) sources: List[List[str]] = field(default_factory=list)
metrics: List[List[str]] = field(default_factory=list) metrics: List[List[str]] = field(default_factory=list)
functions: List[List[str]] = field(default_factory=list)
depends_on: DependsOn = field(default_factory=DependsOn) depends_on: DependsOn = field(default_factory=DependsOn)
compiled_path: Optional[str] = None compiled_path: Optional[str] = None
compiled: bool = False compiled: bool = False

View File

@@ -181,7 +181,7 @@ class TestConfig(NodeAndTestConfig):
warn_if: str = "!= 0" warn_if: str = "!= 0"
error_if: str = "!= 0" error_if: str = "!= 0"
def __post_init__(self): def finalize_and_validate(self):
""" """
The presence of a setting for `store_failures_as` overrides any existing setting for `store_failures`, The presence of a setting for `store_failures_as` overrides any existing setting for `store_failures`,
regardless of level of granularity. If `store_failures_as` is not set, then `store_failures` takes effect. regardless of level of granularity. If `store_failures_as` is not set, then `store_failures` takes effect.
@@ -207,6 +207,7 @@ class TestConfig(NodeAndTestConfig):
but still allow for backwards compatibility for `store_failures`. but still allow for backwards compatibility for `store_failures`.
See https://github.com/dbt-labs/dbt-core/issues/6914 for more information. See https://github.com/dbt-labs/dbt-core/issues/6914 for more information.
""" """
super().finalize_and_validate()
# if `store_failures_as` is not set, it gets set by `store_failures` # if `store_failures_as` is not set, it gets set by `store_failures`
# the settings below mimic existing behavior prior to `store_failures_as` # the settings below mimic existing behavior prior to `store_failures_as`
@@ -229,6 +230,8 @@ class TestConfig(NodeAndTestConfig):
else: else:
self.store_failures = get_store_failures_map.get(self.store_failures_as, True) self.store_failures = get_store_failures_map.get(self.store_failures_as, True)
return self
@classmethod @classmethod
def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> bool: def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> bool:
"""This is like __eq__, except it explicitly checks certain fields.""" """This is like __eq__, except it explicitly checks certain fields."""

View File

@@ -0,0 +1,53 @@
from dataclasses import dataclass, field
from typing import Any, List, Literal, Optional
from dbt.artifacts.resources.types import FunctionType, FunctionVolatility, NodeType
from dbt.artifacts.resources.v1.components import CompiledResource
from dbt.artifacts.resources.v1.config import NodeConfig
from dbt_common.dataclass_schema import dbtClassMixin
# =============
# Function config, and supporting classes
# =============
@dataclass
class FunctionConfig(NodeConfig):
# The fact that this is a property, that can be changed, seems wrong.
# A function's materialization should never be changed, so why allow for it?
materialized: str = "function"
type: FunctionType = FunctionType.Scalar
volatility: Optional[FunctionVolatility] = None
runtime_version: Optional[str] = None
entry_point: Optional[str] = None
# =============
# Function resource, and supporting classes
# =============
@dataclass
class FunctionArgument(dbtClassMixin):
name: str
data_type: str
description: Optional[str] = None
default_value: Optional[Any] = None
@dataclass
class FunctionReturns(dbtClassMixin):
data_type: str
description: Optional[str] = None
@dataclass
class FunctionMandatory(dbtClassMixin):
returns: FunctionReturns
@dataclass
class Function(CompiledResource, FunctionMandatory):
resource_type: Literal[NodeType.Function]
config: FunctionConfig
arguments: List[FunctionArgument] = field(default_factory=list)

View File

@@ -6,6 +6,8 @@ from dbt.artifacts.resources.base import GraphResource
from dbt.artifacts.resources.types import NodeType from dbt.artifacts.resources.types import NodeType
from dbt.artifacts.resources.v1.components import DependsOn, RefArgs from dbt.artifacts.resources.v1.components import DependsOn, RefArgs
from dbt.artifacts.resources.v1.semantic_layer_components import ( from dbt.artifacts.resources.v1.semantic_layer_components import (
MeasureAggregationParameters,
NonAdditiveDimension,
SourceFileMetadata, SourceFileMetadata,
WhereFilterIntersection, WhereFilterIntersection,
) )
@@ -13,6 +15,7 @@ from dbt_common.contracts.config.base import BaseConfig, CompareBehavior, MergeB
from dbt_common.dataclass_schema import dbtClassMixin from dbt_common.dataclass_schema import dbtClassMixin
from dbt_semantic_interfaces.references import MeasureReference, MetricReference from dbt_semantic_interfaces.references import MeasureReference, MetricReference
from dbt_semantic_interfaces.type_enums import ( from dbt_semantic_interfaces.type_enums import (
AggregationType,
ConversionCalculationType, ConversionCalculationType,
MetricType, MetricType,
PeriodAggregation, PeriodAggregation,
@@ -93,6 +96,17 @@ class CumulativeTypeParams(dbtClassMixin):
window: Optional[MetricTimeWindow] = None window: Optional[MetricTimeWindow] = None
grain_to_date: Optional[str] = None grain_to_date: Optional[str] = None
period_agg: PeriodAggregation = PeriodAggregation.FIRST period_agg: PeriodAggregation = PeriodAggregation.FIRST
metric: Optional[MetricInput] = None
@dataclass
class MetricAggregationParams(dbtClassMixin):
semantic_model: str
agg: AggregationType
agg_params: Optional[MeasureAggregationParameters] = None
agg_time_dimension: Optional[str] = None
non_additive_dimension: Optional[NonAdditiveDimension] = None
expr: Optional[str] = None
@dataclass @dataclass
@@ -109,6 +123,7 @@ class MetricTypeParams(dbtClassMixin):
metrics: Optional[List[MetricInput]] = None metrics: Optional[List[MetricInput]] = None
conversion_type_params: Optional[ConversionTypeParams] = None conversion_type_params: Optional[ConversionTypeParams] = None
cumulative_type_params: Optional[CumulativeTypeParams] = None cumulative_type_params: Optional[CumulativeTypeParams] = None
metric_aggregation_params: Optional[MetricAggregationParams] = None
@dataclass @dataclass

View File

@@ -13,7 +13,11 @@ from dbt.artifacts.resources.v1.config import NodeConfig
from dbt_common.contracts.config.base import MergeBehavior from dbt_common.contracts.config.base import MergeBehavior
from dbt_common.contracts.constraints import ModelLevelConstraint from dbt_common.contracts.constraints import ModelLevelConstraint
from dbt_common.contracts.util import Mergeable from dbt_common.contracts.util import Mergeable
from dbt_common.dataclass_schema import ExtensibleDbtClassMixin, dbtClassMixin from dbt_common.dataclass_schema import (
ExtensibleDbtClassMixin,
ValidationError,
dbtClassMixin,
)
class ModelFreshnessUpdatesOnOptions(enum.Enum): class ModelFreshnessUpdatesOnOptions(enum.Enum):
@@ -23,8 +27,8 @@ class ModelFreshnessUpdatesOnOptions(enum.Enum):
@dataclass @dataclass
class ModelBuildAfter(ExtensibleDbtClassMixin): class ModelBuildAfter(ExtensibleDbtClassMixin):
count: int count: Optional[int] = None
period: TimePeriod period: Optional[TimePeriod] = None
updates_on: ModelFreshnessUpdatesOnOptions = ModelFreshnessUpdatesOnOptions.any updates_on: ModelFreshnessUpdatesOnOptions = ModelFreshnessUpdatesOnOptions.any
@@ -75,6 +79,39 @@ class ModelConfig(NodeConfig):
) )
freshness: Optional[ModelFreshness] = None freshness: Optional[ModelFreshness] = None
def __post_init__(self):
super().__post_init__()
if (
self.freshness
and self.freshness.build_after.period
and self.freshness.build_after.count is None
):
raise ValidationError(
"`freshness.build_after` must have a value for `count` if a `period` is provided"
)
elif (
self.freshness
and self.freshness.build_after.count is not None
and not self.freshness.build_after.period
):
raise ValidationError(
"`freshness.build_after` must have a value for `period` if a `count` is provided"
)
@classmethod
def __pre_deserialize__(cls, data):
data = super().__pre_deserialize__(data)
# scrub out model configs where "build_after" is not defined
if (
"freshness" in data
and isinstance(data["freshness"], dict)
and "build_after" in data["freshness"]
):
data["freshness"] = ModelFreshness.from_dict(data["freshness"]).to_dict()
else:
data.pop("freshness", None)
return data
@dataclass @dataclass
class CustomGranularity(dbtClassMixin): class CustomGranularity(dbtClassMixin):
@@ -100,7 +137,6 @@ class Model(CompiledResource):
defer_relation: Optional[DeferRelation] = None defer_relation: Optional[DeferRelation] = None
primary_key: List[str] = field(default_factory=list) primary_key: List[str] = field(default_factory=list)
time_spine: Optional[TimeSpine] = None time_spine: Optional[TimeSpine] = None
freshness: Optional[ModelFreshness] = None
def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None): def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None):
dct = super().__post_serialize__(dct, context) dct = super().__post_serialize__(dct, context)

View File

@@ -1,11 +1,13 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import List, Sequence, Tuple from typing import List, Optional, Sequence, Tuple
from dbt_common.dataclass_schema import dbtClassMixin from dbt_common.dataclass_schema import dbtClassMixin
from dbt_semantic_interfaces.call_parameter_sets import FilterCallParameterSets from dbt_semantic_interfaces.call_parameter_sets import JinjaCallParameterSets
from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import ( from dbt_semantic_interfaces.parsing.where_filter.jinja_object_parser import (
WhereFilterParser, JinjaObjectParser,
QueryItemLocation,
) )
from dbt_semantic_interfaces.type_enums import AggregationType
@dataclass @dataclass
@@ -14,9 +16,11 @@ class WhereFilter(dbtClassMixin):
def call_parameter_sets( def call_parameter_sets(
self, custom_granularity_names: Sequence[str] self, custom_granularity_names: Sequence[str]
) -> FilterCallParameterSets: ) -> JinjaCallParameterSets:
return WhereFilterParser.parse_call_parameter_sets( return JinjaObjectParser.parse_call_parameter_sets(
self.where_sql_template, custom_granularity_names=custom_granularity_names self.where_sql_template,
custom_granularity_names=custom_granularity_names,
query_item_location=QueryItemLocation.NON_ORDER_BY,
) )
@@ -26,7 +30,7 @@ class WhereFilterIntersection(dbtClassMixin):
def filter_expression_parameter_sets( def filter_expression_parameter_sets(
self, custom_granularity_names: Sequence[str] self, custom_granularity_names: Sequence[str]
) -> Sequence[Tuple[str, FilterCallParameterSets]]: ) -> Sequence[Tuple[str, JinjaCallParameterSets]]:
raise NotImplementedError raise NotImplementedError
@@ -52,3 +56,17 @@ class SourceFileMetadata(dbtClassMixin):
repo_file_path: str repo_file_path: str
file_slice: FileSlice file_slice: FileSlice
@dataclass
class MeasureAggregationParameters(dbtClassMixin):
percentile: Optional[float] = None
use_discrete_percentile: bool = False
use_approximate_percentile: bool = False
@dataclass
class NonAdditiveDimension(dbtClassMixin):
name: str
window_choice: AggregationType
window_groupings: List[str]

View File

@@ -5,6 +5,11 @@ from typing import Any, Dict, List, Optional, Sequence
from dbt.artifacts.resources import SourceFileMetadata from dbt.artifacts.resources import SourceFileMetadata
from dbt.artifacts.resources.base import GraphResource from dbt.artifacts.resources.base import GraphResource
from dbt.artifacts.resources.v1.components import DependsOn, RefArgs from dbt.artifacts.resources.v1.components import DependsOn, RefArgs
from dbt.artifacts.resources.v1.metric import Metric
from dbt.artifacts.resources.v1.semantic_layer_components import (
MeasureAggregationParameters,
NonAdditiveDimension,
)
from dbt_common.contracts.config.base import BaseConfig, CompareBehavior, MergeBehavior from dbt_common.contracts.config.base import BaseConfig, CompareBehavior, MergeBehavior
from dbt_common.dataclass_schema import dbtClassMixin from dbt_common.dataclass_schema import dbtClassMixin
from dbt_semantic_interfaces.references import ( from dbt_semantic_interfaces.references import (
@@ -19,6 +24,7 @@ from dbt_semantic_interfaces.type_enums import (
AggregationType, AggregationType,
DimensionType, DimensionType,
EntityType, EntityType,
MetricType,
TimeGranularity, TimeGranularity,
) )
@@ -127,25 +133,11 @@ class Entity(dbtClassMixin):
# ==================================== # ====================================
# Measure objects # Measure object
# Measure protocols: https://github.com/dbt-labs/dbt-semantic-interfaces/blob/main/dbt_semantic_interfaces/protocols/measure.py # Measure protocols: https://github.com/dbt-labs/dbt-semantic-interfaces/blob/main/dbt_semantic_interfaces/protocols/measure.py
# ==================================== # ====================================
@dataclass
class MeasureAggregationParameters(dbtClassMixin):
percentile: Optional[float] = None
use_discrete_percentile: bool = False
use_approximate_percentile: bool = False
@dataclass
class NonAdditiveDimension(dbtClassMixin):
name: str
window_choice: AggregationType
window_groupings: List[str]
@dataclass @dataclass
class Measure(dbtClassMixin): class Measure(dbtClassMixin):
name: str name: str
@@ -274,6 +266,45 @@ class SemanticModel(GraphResource):
) )
return TimeDimensionReference(element_name=agg_time_dimension_name) return TimeDimensionReference(element_name=agg_time_dimension_name)
def checked_agg_time_dimension_for_simple_metric(
self, metric: Metric
) -> TimeDimensionReference:
assert (
metric.type == MetricType.SIMPLE
), "Only simple metrics can have an agg time dimension."
metric_agg_params = metric.type_params.metric_aggregation_params
# There are validations elsewhere to check this for metrics and provide messaging for it.
assert metric_agg_params, "Simple metrics must have metric_aggregation_params."
# This indicates a validation bug / dev error, not a user error that should appear
# in a user's YAML.
assert (
metric_agg_params.semantic_model == self.name
), "Cannot retrieve the agg time dimension for a metric from a different model "
f"than the one that the metric belongs to. Metric `{metric.name}` belongs to model "
f"`{metric_agg_params.semantic_model}`, but we requested the agg time dimension from model `{self.name}`."
metric_time_dimension_name = None
if (
metric.type_params
and metric.type_params.metric_aggregation_params
and metric.type_params.metric_aggregation_params.agg_time_dimension
):
metric_time_dimension_name = (
metric.type_params.metric_aggregation_params.agg_time_dimension
)
default_agg_time_dimension = (
self.defaults.agg_time_dimension if self.defaults is not None else None
)
agg_time_dimension_name = metric_time_dimension_name or default_agg_time_dimension
assert agg_time_dimension_name is not None, (
f"Aggregation time dimension for metric {metric.name} is not set! This should either be set directly on "
f"the metric specification in the model, or else defaulted to the time dimension in the data "
f"source containing the metric."
)
return TimeDimensionReference(element_name=agg_time_dimension_name)
@property @property
def primary_entity_reference(self) -> Optional[EntityReference]: def primary_entity_reference(self) -> Optional[EntityReference]:
return ( return (

View File

@@ -10,7 +10,7 @@ from dbt.artifacts.resources.v1.components import (
HasRelationMetadata, HasRelationMetadata,
Quoting, Quoting,
) )
from dbt.artifacts.resources.v1.config import BaseConfig from dbt.artifacts.resources.v1.config import BaseConfig, MergeBehavior
from dbt_common.contracts.config.properties import AdditionalPropertiesAllowed from dbt_common.contracts.config.properties import AdditionalPropertiesAllowed
from dbt_common.contracts.util import Mergeable from dbt_common.contracts.util import Mergeable
from dbt_common.exceptions import CompilationError from dbt_common.exceptions import CompilationError
@@ -20,7 +20,11 @@ from dbt_common.exceptions import CompilationError
class SourceConfig(BaseConfig): class SourceConfig(BaseConfig):
enabled: bool = True enabled: bool = True
event_time: Any = None event_time: Any = None
freshness: Optional[FreshnessThreshold] = None freshness: Optional[FreshnessThreshold] = field(default_factory=FreshnessThreshold)
loaded_at_field: Optional[str] = None
loaded_at_query: Optional[str] = None
meta: Dict[str, Any] = field(default_factory=dict, metadata=MergeBehavior.Update.meta())
tags: List[str] = field(default_factory=list)
@dataclass @dataclass

View File

@@ -181,3 +181,11 @@ def get_artifact_schema_version(dct: dict) -> int:
# 4. Convert to int # 4. Convert to int
# TODO: If this gets more complicated, turn into a regex # TODO: If this gets more complicated, turn into a regex
return int(schema_version.split("/")[-1].split(".")[0][1:]) return int(schema_version.split("/")[-1].split(".")[0][1:])
def get_artifact_dbt_version(dct: dict) -> Optional[str]:
dbt_version = dct.get("metadata", {}).get("dbt_version", None)
if dbt_version is None:
return None
return str(dbt_version)

View File

@@ -1,11 +1,14 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Union from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Union
from uuid import UUID from uuid import UUID
from dbt import tracking
from dbt.artifacts.resources import ( from dbt.artifacts.resources import (
Analysis, Analysis,
Documentation, Documentation,
Exposure, Exposure,
Function,
GenericTest, GenericTest,
Group, Group,
HookNode, HookNode,
@@ -25,23 +28,21 @@ from dbt.artifacts.resources.v1.components import Quoting
from dbt.artifacts.schemas.base import ( from dbt.artifacts.schemas.base import (
ArtifactMixin, ArtifactMixin,
BaseArtifactMetadata, BaseArtifactMetadata,
get_artifact_dbt_version,
get_artifact_schema_version, get_artifact_schema_version,
schema_version, schema_version,
) )
from dbt.artifacts.schemas.upgrades import upgrade_manifest_json from dbt.artifacts.schemas.upgrades import (
upgrade_manifest_json,
upgrade_manifest_json_dbt_version,
)
from dbt.version import __version__
from dbt_common.exceptions import DbtInternalError from dbt_common.exceptions import DbtInternalError
NodeEdgeMap = Dict[str, List[str]] NodeEdgeMap = Dict[str, List[str]]
UniqueID = str UniqueID = str
ManifestResource = Union[ ManifestResource = Union[
Seed, Seed, Analysis, SingularTest, HookNode, Model, SqlOperation, GenericTest, Snapshot, Function
Analysis,
SingularTest,
HookNode,
Model,
SqlOperation,
GenericTest,
Snapshot,
] ]
DisabledManifestResource = Union[ DisabledManifestResource = Union[
ManifestResource, ManifestResource,
@@ -93,6 +94,10 @@ class ManifestMetadata(BaseArtifactMetadata):
default_factory=Quoting, default_factory=Quoting,
metadata=dict(description="The quoting configuration for the project"), metadata=dict(description="The quoting configuration for the project"),
) )
run_started_at: Optional[datetime] = field(
default=tracking.active_user.run_started_at if tracking.active_user is not None else None,
metadata=dict(description="The timestamp when the run started"),
)
@classmethod @classmethod
def default(cls): def default(cls):
@@ -164,6 +169,10 @@ class WritableManifest(ArtifactMixin):
description="The unit tests defined in the project", description="The unit tests defined in the project",
) )
) )
functions: Mapping[UniqueID, Function] = field(
default_factory=dict,
metadata=dict(description=("The functions defined in the dbt project")),
)
@classmethod @classmethod
def compatible_previous_versions(cls) -> Iterable[Tuple[str, int]]: def compatible_previous_versions(cls) -> Iterable[Tuple[str, int]]:
@@ -185,6 +194,10 @@ class WritableManifest(ArtifactMixin):
manifest_schema_version = get_artifact_schema_version(data) manifest_schema_version = get_artifact_schema_version(data)
if manifest_schema_version < cls.dbt_schema_version.version: if manifest_schema_version < cls.dbt_schema_version.version:
data = upgrade_manifest_json(data, manifest_schema_version) data = upgrade_manifest_json(data, manifest_schema_version)
manifest_dbt_version = get_artifact_dbt_version(data)
if manifest_dbt_version and manifest_dbt_version != __version__:
data = upgrade_manifest_json_dbt_version(data)
return cls.from_dict(data) return cls.from_dict(data)
@classmethod @classmethod

View File

@@ -1 +1,4 @@
from dbt.artifacts.schemas.upgrades.upgrade_manifest import upgrade_manifest_json from dbt.artifacts.schemas.upgrades.upgrade_manifest import upgrade_manifest_json
from dbt.artifacts.schemas.upgrades.upgrade_manifest_dbt_version import (
upgrade_manifest_json_dbt_version,
)

View File

@@ -0,0 +1,2 @@
def upgrade_manifest_json_dbt_version(manifest: dict) -> dict:
return manifest

View File

@@ -17,7 +17,7 @@ from dbt.cli.types import Command as CliCommand
from dbt.config.project import read_project_flags from dbt.config.project import read_project_flags
from dbt.config.utils import normalize_warn_error_options from dbt.config.utils import normalize_warn_error_options
from dbt.contracts.project import ProjectFlags from dbt.contracts.project import ProjectFlags
from dbt.deprecations import fire_buffered_deprecations, renamed_env_var from dbt.deprecations import fire_buffered_deprecations, renamed_env_var, warn
from dbt.events import ALL_EVENT_NAMES from dbt.events import ALL_EVENT_NAMES
from dbt_common import ui from dbt_common import ui
from dbt_common.clients import jinja from dbt_common.clients import jinja
@@ -50,6 +50,8 @@ DEPRECATED_PARAMS = {
} }
DEPRECATED_FLAGS_TO_WARNINGS = {("--models", "--model", "-m"): "model-param-usage-deprecation"}
WHICH_KEY = "which" WHICH_KEY = "which"
@@ -385,7 +387,7 @@ class Flags:
"Value for `--event-time-start` must be less than `--event-time-end`" "Value for `--event-time-start` must be less than `--event-time-end`"
) )
def fire_deprecations(self): def fire_deprecations(self, ctx: Optional[Context] = None):
"""Fires events for deprecated env_var usage.""" """Fires events for deprecated env_var usage."""
[dep_fn() for dep_fn in self.deprecated_env_var_warnings] [dep_fn() for dep_fn in self.deprecated_env_var_warnings]
# It is necessary to remove this attr from the class so it does # It is necessary to remove this attr from the class so it does
@@ -394,12 +396,25 @@ class Flags:
fire_buffered_deprecations() fire_buffered_deprecations()
# Handle firing deprecations of CLI aliases separately using argv or dbtRunner args
# because click doesn't make it possible to disambiguite which literal CLI option was used
# and only preserves the 'canonical' representation.
original_command_args = (
ctx.obj["dbt_runner_command_args"]
if (ctx and ctx.obj and "dbt_runner_command_args" in ctx.obj)
else sys.argv
)
for deprecated_flags, warning in DEPRECATED_FLAGS_TO_WARNINGS.items():
for deprecated_flag in deprecated_flags:
if deprecated_flag in original_command_args:
warn(warning)
@classmethod @classmethod
def from_dict(cls, command: CliCommand, args_dict: Dict[str, Any]) -> "Flags": def from_dict(cls, command: CliCommand, args_dict: Dict[str, Any]) -> "Flags":
command_arg_list = command_params(command, args_dict) command_arg_list = command_params(command, args_dict)
ctx = args_to_context(command_arg_list) ctx = args_to_context(command_arg_list)
flags = cls(ctx=ctx) flags = cls(ctx=ctx)
flags.fire_deprecations() flags.fire_deprecations(ctx=ctx)
return flags return flags
def set_common_global_flags(self): def set_common_global_flags(self):

View File

@@ -56,6 +56,7 @@ class dbtRunner:
dbt_ctx.obj = { dbt_ctx.obj = {
"manifest": self.manifest, "manifest": self.manifest,
"callbacks": self.callbacks, "callbacks": self.callbacks,
"dbt_runner_command_args": args,
} }
for key, value in kwargs.items(): for key, value in kwargs.items():
@@ -540,6 +541,7 @@ cli.add_command(ls, "ls")
@requires.preflight @requires.preflight
@requires.profile @requires.profile
@requires.project @requires.project
@requires.catalogs
@requires.runtime_config @requires.runtime_config
@requires.manifest(write_perf_info=True) @requires.manifest(write_perf_info=True)
def parse(ctx, **kwargs): def parse(ctx, **kwargs):
@@ -703,6 +705,7 @@ def run_operation(ctx, **kwargs):
@requires.preflight @requires.preflight
@requires.profile @requires.profile
@requires.project @requires.project
@requires.catalogs
@requires.runtime_config @requires.runtime_config
@requires.manifest @requires.manifest
def seed(ctx, **kwargs): def seed(ctx, **kwargs):
@@ -736,6 +739,7 @@ def seed(ctx, **kwargs):
@requires.preflight @requires.preflight
@requires.profile @requires.profile
@requires.project @requires.project
@requires.catalogs
@requires.runtime_config @requires.runtime_config
@requires.manifest @requires.manifest
def snapshot(ctx, **kwargs): def snapshot(ctx, **kwargs):

View File

@@ -2,11 +2,13 @@ import inspect
import typing as t import typing as t
import click import click
from click import Context
from click.parser import OptionParser, ParsingState
from dbt.cli.option_types import ChoiceTuple from dbt.cli.option_types import ChoiceTuple
if t.TYPE_CHECKING:
from click import Context
from click.parser import _OptionParser, _ParsingState
# Implementation from: https://stackoverflow.com/a/48394004 # Implementation from: https://stackoverflow.com/a/48394004
# Note MultiOption options must be specified with type=tuple or type=ChoiceTuple (https://github.com/pallets/click/issues/2012) # Note MultiOption options must be specified with type=tuple or type=ChoiceTuple (https://github.com/pallets/click/issues/2012)
@@ -33,8 +35,8 @@ class MultiOption(click.Option):
else: else:
assert isinstance(option_type, ChoiceTuple), msg assert isinstance(option_type, ChoiceTuple), msg
def add_to_parser(self, parser: OptionParser, ctx: Context): def add_to_parser(self, parser: "_OptionParser", ctx: "Context"):
def parser_process(value: str, state: ParsingState): def parser_process(value: str, state: "_ParsingState"):
# method to hook to the parser.process # method to hook to the parser.process
done = False done = False
value_list = str.split(value, " ") value_list = str.split(value, " ")
@@ -65,7 +67,7 @@ class MultiOption(click.Option):
break break
return retval return retval
def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: def type_cast_value(self, ctx: "Context", value: t.Any) -> t.Any:
def flatten(data): def flatten(data):
if isinstance(data, tuple): if isinstance(data, tuple):
for x in data: for x in data:

View File

@@ -1,4 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, Callable, List
import click import click
@@ -24,48 +25,65 @@ select_attrs = {
"type": tuple, "type": tuple,
} }
# Record of env vars associated with options
KNOWN_ENV_VARS: List[str] = []
def _create_option_and_track_env_var(
*args: Any, **kwargs: Any
) -> Callable[[click.decorators.FC], click.decorators.FC]:
global KNOWN_ENV_VARS
envvar = kwargs.get("envvar", None)
if isinstance(envvar, str):
KNOWN_ENV_VARS.append(envvar)
return click.option(*args, **kwargs)
# --- The actual option definitions --- # # --- The actual option definitions --- #
add_package = click.option( add_package = _create_option_and_track_env_var(
"--add-package", "--add-package",
help="Add a package to current package spec, specify it as package-name@version. Change the source with --source flag.", help="Add a package to current package spec, specify it as package-name@version. Change the source with --source flag.",
envvar=None, envvar=None,
type=Package(), type=Package(),
) )
args = click.option(
args = _create_option_and_track_env_var(
"--args", "--args",
envvar=None, envvar=None,
help="Supply arguments to the macro. This dictionary will be mapped to the keyword arguments defined in the selected macro. This argument should be a YAML string, eg. '{my_variable: my_value}'", help="Supply arguments to the macro. This dictionary will be mapped to the keyword arguments defined in the selected macro. This argument should be a YAML string, eg. '{my_variable: my_value}'",
type=YAML(), type=YAML(),
) )
browser = click.option( browser = _create_option_and_track_env_var(
"--browser/--no-browser", "--browser/--no-browser",
envvar=None, envvar=None,
help="Wether or not to open a local web browser after starting the server", help="Whether or not to open a local web browser after starting the server",
default=True, default=True,
) )
cache_selected_only = click.option( cache_selected_only = _create_option_and_track_env_var(
"--cache-selected-only/--no-cache-selected-only", "--cache-selected-only/--no-cache-selected-only",
envvar="DBT_CACHE_SELECTED_ONLY", envvar="DBT_CACHE_SELECTED_ONLY",
help="At start of run, populate relational cache only for schemas containing selected nodes, or for all schemas of interest.", help="At start of run, populate relational cache only for schemas containing selected nodes, or for all schemas of interest.",
) )
clean_project_files_only = click.option( clean_project_files_only = _create_option_and_track_env_var(
"--clean-project-files-only / --no-clean-project-files-only", "--clean-project-files-only / --no-clean-project-files-only",
envvar="DBT_CLEAN_PROJECT_FILES_ONLY", envvar="DBT_CLEAN_PROJECT_FILES_ONLY",
help="If disabled, dbt clean will delete all paths specified in clean-paths, even if they're outside the dbt project.", help="If disabled, dbt clean will delete all paths specified in clean-paths, even if they're outside the dbt project.",
default=True, default=True,
) )
compile_docs = click.option( compile_docs = _create_option_and_track_env_var(
"--compile/--no-compile", "--compile/--no-compile",
envvar=None, envvar=None,
help="Whether or not to run 'dbt compile' as part of docs generation", help="Whether or not to run 'dbt compile' as part of docs generation",
default=True, default=True,
) )
compile_inject_ephemeral_ctes = click.option( compile_inject_ephemeral_ctes = _create_option_and_track_env_var(
"--inject-ephemeral-ctes/--no-inject-ephemeral-ctes", "--inject-ephemeral-ctes/--no-inject-ephemeral-ctes",
envvar=None, envvar=None,
help="Internal flag controlling injection of referenced ephemeral models' CTEs during `compile`.", help="Internal flag controlling injection of referenced ephemeral models' CTEs during `compile`.",
@@ -73,21 +91,21 @@ compile_inject_ephemeral_ctes = click.option(
default=True, default=True,
) )
config_dir = click.option( config_dir = _create_option_and_track_env_var(
"--config-dir", "--config-dir",
envvar=None, envvar=None,
help="Print a system-specific command to access the directory that the current dbt project is searching for a profiles.yml. Then, exit. This flag renders other debug step flags no-ops.", help="Print a system-specific command to access the directory that the current dbt project is searching for a profiles.yml. Then, exit. This flag renders other debug step flags no-ops.",
is_flag=True, is_flag=True,
) )
debug = click.option( debug = _create_option_and_track_env_var(
"--debug/--no-debug", "--debug/--no-debug",
"-d/ ", "-d/ ",
envvar="DBT_DEBUG", envvar="DBT_DEBUG",
help="Display debug logging during dbt execution. Useful for debugging and making bug reports.", help="Display debug logging during dbt execution. Useful for debugging and making bug reports.",
) )
debug_connection = click.option( debug_connection = _create_option_and_track_env_var(
"--connection", "--connection",
envvar=None, envvar=None,
help="Test the connection to the target database independent of dependency checks.", help="Test the connection to the target database independent of dependency checks.",
@@ -95,13 +113,13 @@ debug_connection = click.option(
) )
# flag was previously named DEFER_MODE # flag was previously named DEFER_MODE
defer = click.option( defer = _create_option_and_track_env_var(
"--defer/--no-defer", "--defer/--no-defer",
envvar="DBT_DEFER", envvar="DBT_DEFER",
help="If set, resolve unselected nodes by deferring to the manifest within the --state directory.", help="If set, resolve unselected nodes by deferring to the manifest within the --state directory.",
) )
defer_state = click.option( defer_state = _create_option_and_track_env_var(
"--defer-state", "--defer-state",
envvar="DBT_DEFER_STATE", envvar="DBT_DEFER_STATE",
help="Override the state directory for deferral only.", help="Override the state directory for deferral only.",
@@ -114,7 +132,7 @@ defer_state = click.option(
), ),
) )
deprecated_defer = click.option( deprecated_defer = _create_option_and_track_env_var(
"--deprecated-defer", "--deprecated-defer",
envvar="DBT_DEFER_TO_STATE", envvar="DBT_DEFER_TO_STATE",
help="Internal flag for deprecating old env var.", help="Internal flag for deprecating old env var.",
@@ -122,14 +140,14 @@ deprecated_defer = click.option(
hidden=True, hidden=True,
) )
deprecated_favor_state = click.option( deprecated_favor_state = _create_option_and_track_env_var(
"--deprecated-favor-state", "--deprecated-favor-state",
envvar="DBT_FAVOR_STATE_MODE", envvar="DBT_FAVOR_STATE_MODE",
help="Internal flag for deprecating old env var.", help="Internal flag for deprecating old env var.",
) )
# Renamed to --export-saved-queries # Renamed to --export-saved-queries
deprecated_include_saved_query = click.option( deprecated_include_saved_query = _create_option_and_track_env_var(
"--include-saved-query/--no-include-saved-query", "--include-saved-query/--no-include-saved-query",
envvar="DBT_INCLUDE_SAVED_QUERY", envvar="DBT_INCLUDE_SAVED_QUERY",
help="Include saved queries in the list of resources to be selected for build command", help="Include saved queries in the list of resources to be selected for build command",
@@ -137,7 +155,7 @@ deprecated_include_saved_query = click.option(
hidden=True, hidden=True,
) )
deprecated_print = click.option( deprecated_print = _create_option_and_track_env_var(
"--deprecated-print/--deprecated-no-print", "--deprecated-print/--deprecated-no-print",
envvar="DBT_NO_PRINT", envvar="DBT_NO_PRINT",
help="Internal flag for deprecating old env var.", help="Internal flag for deprecating old env var.",
@@ -146,7 +164,7 @@ deprecated_print = click.option(
callback=lambda ctx, param, value: not value, callback=lambda ctx, param, value: not value,
) )
deprecated_state = click.option( deprecated_state = _create_option_and_track_env_var(
"--deprecated-state", "--deprecated-state",
envvar="DBT_ARTIFACT_STATE_PATH", envvar="DBT_ARTIFACT_STATE_PATH",
help="Internal flag for deprecating old env var.", help="Internal flag for deprecating old env var.",
@@ -160,21 +178,21 @@ deprecated_state = click.option(
), ),
) )
empty = click.option( empty = _create_option_and_track_env_var(
"--empty/--no-empty", "--empty/--no-empty",
envvar="DBT_EMPTY", envvar="DBT_EMPTY",
help="If specified, limit input refs and sources to zero rows.", help="If specified, limit input refs and sources to zero rows.",
is_flag=True, is_flag=True,
) )
empty_catalog = click.option( empty_catalog = _create_option_and_track_env_var(
"--empty-catalog", "--empty-catalog",
help="If specified, generate empty catalog.json file during the `dbt docs generate` command.", help="If specified, generate empty catalog.json file during the `dbt docs generate` command.",
default=False, default=False,
is_flag=True, is_flag=True,
) )
event_time_end = click.option( event_time_end = _create_option_and_track_env_var(
"--event-time-end", "--event-time-end",
envvar="DBT_EVENT_TIME_END", envvar="DBT_EVENT_TIME_END",
help="If specified, the end datetime dbt uses to filter microbatch model inputs (exclusive).", help="If specified, the end datetime dbt uses to filter microbatch model inputs (exclusive).",
@@ -182,7 +200,7 @@ event_time_end = click.option(
default=None, default=None,
) )
event_time_start = click.option( event_time_start = _create_option_and_track_env_var(
"--event-time-start", "--event-time-start",
envvar="DBT_EVENT_TIME_START", envvar="DBT_EVENT_TIME_START",
help="If specified, the start datetime dbt uses to filter microbatch model inputs (inclusive).", help="If specified, the start datetime dbt uses to filter microbatch model inputs (inclusive).",
@@ -190,7 +208,7 @@ event_time_start = click.option(
default=None, default=None,
) )
exclude = click.option( exclude = _create_option_and_track_env_var(
"--exclude", "--exclude",
envvar=None, envvar=None,
type=tuple, type=tuple,
@@ -199,7 +217,7 @@ exclude = click.option(
help="Specify the nodes to exclude.", help="Specify the nodes to exclude.",
) )
exclude_resource_type = click.option( exclude_resource_type = _create_option_and_track_env_var(
"--exclude-resource-types", "--exclude-resource-types",
"--exclude-resource-type", "--exclude-resource-type",
envvar="DBT_EXCLUDE_RESOURCE_TYPES", envvar="DBT_EXCLUDE_RESOURCE_TYPES",
@@ -217,6 +235,7 @@ exclude_resource_type = click.option(
"exposure", "exposure",
"snapshot", "snapshot",
"seed", "seed",
"function",
"default", "default",
], ],
case_sensitive=False, case_sensitive=False,
@@ -226,7 +245,7 @@ exclude_resource_type = click.option(
default=(), default=(),
) )
export_saved_queries = click.option( export_saved_queries = _create_option_and_track_env_var(
"--export-saved-queries/--no-export-saved-queries", "--export-saved-queries/--no-export-saved-queries",
envvar="DBT_EXPORT_SAVED_QUERIES", envvar="DBT_EXPORT_SAVED_QUERIES",
help="Export saved queries within the 'build' command, otherwise no-op", help="Export saved queries within the 'build' command, otherwise no-op",
@@ -234,20 +253,20 @@ export_saved_queries = click.option(
hidden=True, hidden=True,
) )
fail_fast = click.option( fail_fast = _create_option_and_track_env_var(
"--fail-fast/--no-fail-fast", "--fail-fast/--no-fail-fast",
"-x/ ", "-x/ ",
envvar="DBT_FAIL_FAST", envvar="DBT_FAIL_FAST",
help="Stop execution on first failure.", help="Stop execution on first failure.",
) )
favor_state = click.option( favor_state = _create_option_and_track_env_var(
"--favor-state/--no-favor-state", "--favor-state/--no-favor-state",
envvar="DBT_FAVOR_STATE", envvar="DBT_FAVOR_STATE",
help="If set, defer to the argument provided to the state flag for resolving unselected nodes, even if the node(s) exist as a database object in the current environment.", help="If set, defer to the argument provided to the state flag for resolving unselected nodes, even if the node(s) exist as a database object in the current environment.",
) )
full_refresh = click.option( full_refresh = _create_option_and_track_env_var(
"--full-refresh", "--full-refresh",
"-f", "-f",
envvar="DBT_FULL_REFRESH", envvar="DBT_FULL_REFRESH",
@@ -255,7 +274,7 @@ full_refresh = click.option(
is_flag=True, is_flag=True,
) )
host = click.option( host = _create_option_and_track_env_var(
"--host", "--host",
envvar="DBT_HOST", envvar="DBT_HOST",
help="host to serve dbt docs on", help="host to serve dbt docs on",
@@ -263,7 +282,7 @@ host = click.option(
default="127.0.0.1", default="127.0.0.1",
) )
indirect_selection = click.option( indirect_selection = _create_option_and_track_env_var(
"--indirect-selection", "--indirect-selection",
envvar="DBT_INDIRECT_SELECTION", envvar="DBT_INDIRECT_SELECTION",
help="Choose which tests to select that are adjacent to selected resources. Eager is most inclusive, cautious is most exclusive, and buildable is in between. Empty includes no tests at all.", help="Choose which tests to select that are adjacent to selected resources. Eager is most inclusive, cautious is most exclusive, and buildable is in between. Empty includes no tests at all.",
@@ -271,40 +290,40 @@ indirect_selection = click.option(
default="eager", default="eager",
) )
inline = click.option( inline = _create_option_and_track_env_var(
"--inline", "--inline",
envvar=None, envvar=None,
help="Pass SQL inline to dbt compile and show", help="Pass SQL inline to dbt compile and show",
) )
inline_direct = click.option( inline_direct = _create_option_and_track_env_var(
"--inline-direct", "--inline-direct",
envvar=None, envvar=None,
help="Internal flag to pass SQL inline to dbt show. Do not load the entire project or apply templating.", help="Internal flag to pass SQL inline to dbt show. Do not load the entire project or apply templating.",
hidden=True, hidden=True,
) )
introspect = click.option( introspect = _create_option_and_track_env_var(
"--introspect/--no-introspect", "--introspect/--no-introspect",
envvar="DBT_INTROSPECT", envvar="DBT_INTROSPECT",
help="Whether to scaffold introspective queries as part of compilation", help="Whether to scaffold introspective queries as part of compilation",
default=True, default=True,
) )
lock = click.option( lock = _create_option_and_track_env_var(
"--lock", "--lock",
envvar=None, envvar=None,
help="Generate the package-lock.yml file without install the packages.", help="Generate the package-lock.yml file without install the packages.",
is_flag=True, is_flag=True,
) )
log_cache_events = click.option( log_cache_events = _create_option_and_track_env_var(
"--log-cache-events/--no-log-cache-events", "--log-cache-events/--no-log-cache-events",
help="Enable verbose logging for relational cache events to help when debugging.", help="Enable verbose logging for relational cache events to help when debugging.",
envvar="DBT_LOG_CACHE_EVENTS", envvar="DBT_LOG_CACHE_EVENTS",
) )
log_format = click.option( log_format = _create_option_and_track_env_var(
"--log-format", "--log-format",
envvar="DBT_LOG_FORMAT", envvar="DBT_LOG_FORMAT",
help="Specify the format of logging to the console and the log file. Use --log-format-file to configure the format for the log file differently than the console.", help="Specify the format of logging to the console and the log file. Use --log-format-file to configure the format for the log file differently than the console.",
@@ -312,7 +331,7 @@ log_format = click.option(
default="default", default="default",
) )
log_format_file = click.option( log_format_file = _create_option_and_track_env_var(
"--log-format-file", "--log-format-file",
envvar="DBT_LOG_FORMAT_FILE", envvar="DBT_LOG_FORMAT_FILE",
help="Specify the format of logging to the log file by overriding the default value and the general --log-format setting.", help="Specify the format of logging to the log file by overriding the default value and the general --log-format setting.",
@@ -320,7 +339,7 @@ log_format_file = click.option(
default="debug", default="debug",
) )
log_level = click.option( log_level = _create_option_and_track_env_var(
"--log-level", "--log-level",
envvar="DBT_LOG_LEVEL", envvar="DBT_LOG_LEVEL",
help="Specify the minimum severity of events that are logged to the console and the log file. Use --log-level-file to configure the severity for the log file differently than the console.", help="Specify the minimum severity of events that are logged to the console and the log file. Use --log-level-file to configure the severity for the log file differently than the console.",
@@ -328,7 +347,7 @@ log_level = click.option(
default="info", default="info",
) )
log_level_file = click.option( log_level_file = _create_option_and_track_env_var(
"--log-level-file", "--log-level-file",
envvar="DBT_LOG_LEVEL_FILE", envvar="DBT_LOG_LEVEL_FILE",
help="Specify the minimum severity of events that are logged to the log file by overriding the default value and the general --log-level setting.", help="Specify the minimum severity of events that are logged to the log file by overriding the default value and the general --log-level setting.",
@@ -336,7 +355,7 @@ log_level_file = click.option(
default="debug", default="debug",
) )
log_file_max_bytes = click.option( log_file_max_bytes = _create_option_and_track_env_var(
"--log-file-max-bytes", "--log-file-max-bytes",
envvar="DBT_LOG_FILE_MAX_BYTES", envvar="DBT_LOG_FILE_MAX_BYTES",
help="Configure the max file size in bytes for a single dbt.log file, before rolling over. 0 means no limit.", help="Configure the max file size in bytes for a single dbt.log file, before rolling over. 0 means no limit.",
@@ -345,7 +364,7 @@ log_file_max_bytes = click.option(
hidden=True, hidden=True,
) )
log_path = click.option( log_path = _create_option_and_track_env_var(
"--log-path", "--log-path",
envvar="DBT_LOG_PATH", envvar="DBT_LOG_PATH",
help="Configure the 'log-path'. Only applies this setting for the current run. Overrides the 'DBT_LOG_PATH' if it is set.", help="Configure the 'log-path'. Only applies this setting for the current run. Overrides the 'DBT_LOG_PATH' if it is set.",
@@ -353,16 +372,16 @@ log_path = click.option(
type=click.Path(resolve_path=True, path_type=Path), type=click.Path(resolve_path=True, path_type=Path),
) )
macro_debugging = click.option( macro_debugging = _create_option_and_track_env_var(
"--macro-debugging/--no-macro-debugging", "--macro-debugging/--no-macro-debugging",
envvar="DBT_MACRO_DEBUGGING", envvar="DBT_MACRO_DEBUGGING",
hidden=True, hidden=True,
) )
models = click.option(*model_decls, **select_attrs) # type: ignore[arg-type] models = _create_option_and_track_env_var(*model_decls, **select_attrs) # type: ignore[arg-type]
# This less standard usage of --output where output_path below is more standard # This less standard usage of --output where output_path below is more standard
output = click.option( output = _create_option_and_track_env_var(
"--output", "--output",
envvar=None, envvar=None,
help="Specify the output format: either JSON or a newline-delimited list of selectors, paths, or names", help="Specify the output format: either JSON or a newline-delimited list of selectors, paths, or names",
@@ -370,12 +389,13 @@ output = click.option(
default="selector", default="selector",
) )
output_keys = click.option( output_keys = _create_option_and_track_env_var(
"--output-keys", "--output-keys",
envvar=None, envvar=None,
help=( help=(
"Space-delimited listing of node properties to include as custom keys for JSON output " "Space-delimited listing of node properties to include as custom keys for JSON output. "
"(e.g. `--output json --output-keys name resource_type description`)" "Supports nested keys using dot notation "
"(e.g. `--output json --output-keys name config.materialized resource_type`)"
), ),
type=tuple, type=tuple,
cls=MultiOption, cls=MultiOption,
@@ -383,7 +403,7 @@ output_keys = click.option(
default=[], default=[],
) )
output_path = click.option( output_path = _create_option_and_track_env_var(
"--output", "--output",
"-o", "-o",
envvar=None, envvar=None,
@@ -392,14 +412,14 @@ output_path = click.option(
default=None, default=None,
) )
partial_parse = click.option( partial_parse = _create_option_and_track_env_var(
"--partial-parse/--no-partial-parse", "--partial-parse/--no-partial-parse",
envvar="DBT_PARTIAL_PARSE", envvar="DBT_PARTIAL_PARSE",
help="Allow for partial parsing by looking for and writing to a pickle file in the target directory. This overrides the user configuration file.", help="Allow for partial parsing by looking for and writing to a pickle file in the target directory. This overrides the user configuration file.",
default=True, default=True,
) )
partial_parse_file_diff = click.option( partial_parse_file_diff = _create_option_and_track_env_var(
"--partial-parse-file-diff/--no-partial-parse-file-diff", "--partial-parse-file-diff/--no-partial-parse-file-diff",
envvar="DBT_PARTIAL_PARSE_FILE_DIFF", envvar="DBT_PARTIAL_PARSE_FILE_DIFF",
help="Internal flag for whether to compute a file diff during partial parsing.", help="Internal flag for whether to compute a file diff during partial parsing.",
@@ -407,7 +427,7 @@ partial_parse_file_diff = click.option(
default=True, default=True,
) )
partial_parse_file_path = click.option( partial_parse_file_path = _create_option_and_track_env_var(
"--partial-parse-file-path", "--partial-parse-file-path",
envvar="DBT_PARTIAL_PARSE_FILE_PATH", envvar="DBT_PARTIAL_PARSE_FILE_PATH",
help="Internal flag for path to partial_parse.manifest file.", help="Internal flag for path to partial_parse.manifest file.",
@@ -416,21 +436,21 @@ partial_parse_file_path = click.option(
type=click.Path(exists=True, dir_okay=False, resolve_path=True), type=click.Path(exists=True, dir_okay=False, resolve_path=True),
) )
print = click.option( print = _create_option_and_track_env_var(
"--print/--no-print", "--print/--no-print",
envvar="DBT_PRINT", envvar="DBT_PRINT",
help="Output all {{ print() }} macro calls.", help="Output all {{ print() }} macro calls.",
default=True, default=True,
) )
populate_cache = click.option( populate_cache = _create_option_and_track_env_var(
"--populate-cache/--no-populate-cache", "--populate-cache/--no-populate-cache",
envvar="DBT_POPULATE_CACHE", envvar="DBT_POPULATE_CACHE",
help="At start of run, use `show` or `information_schema` queries to populate a relational cache, which can speed up subsequent materializations.", help="At start of run, use `show` or `information_schema` queries to populate a relational cache, which can speed up subsequent materializations.",
default=True, default=True,
) )
port = click.option( port = _create_option_and_track_env_var(
"--port", "--port",
envvar=None, envvar=None,
help="Specify the port number for the docs server", help="Specify the port number for the docs server",
@@ -438,7 +458,7 @@ port = click.option(
type=click.INT, type=click.INT,
) )
printer_width = click.option( printer_width = _create_option_and_track_env_var(
"--printer-width", "--printer-width",
envvar="DBT_PRINTER_WIDTH", envvar="DBT_PRINTER_WIDTH",
help="Sets the width of terminal output", help="Sets the width of terminal output",
@@ -446,13 +466,13 @@ printer_width = click.option(
default=80, default=80,
) )
profile = click.option( profile = _create_option_and_track_env_var(
"--profile", "--profile",
envvar="DBT_PROFILE", envvar="DBT_PROFILE",
help="Which existing profile to load. Overrides setting in dbt_project.yml.", help="Which existing profile to load. Overrides setting in dbt_project.yml.",
) )
profiles_dir = click.option( profiles_dir = _create_option_and_track_env_var(
"--profiles-dir", "--profiles-dir",
envvar="DBT_PROFILES_DIR", envvar="DBT_PROFILES_DIR",
help="Which directory to look in for the profiles.yml file. If not set, dbt will look in the current working directory first, then HOME/.dbt/", help="Which directory to look in for the profiles.yml file. If not set, dbt will look in the current working directory first, then HOME/.dbt/",
@@ -463,7 +483,7 @@ profiles_dir = click.option(
# `dbt debug` uses this because it implements custom behaviour for non-existent profiles.yml directories # `dbt debug` uses this because it implements custom behaviour for non-existent profiles.yml directories
# `dbt deps` does not load a profile at all # `dbt deps` does not load a profile at all
# `dbt init` will write profiles.yml if it doesn't yet exist # `dbt init` will write profiles.yml if it doesn't yet exist
profiles_dir_exists_false = click.option( profiles_dir_exists_false = _create_option_and_track_env_var(
"--profiles-dir", "--profiles-dir",
envvar="DBT_PROFILES_DIR", envvar="DBT_PROFILES_DIR",
help="Which directory to look in for the profiles.yml file. If not set, dbt will look in the current working directory first, then HOME/.dbt/", help="Which directory to look in for the profiles.yml file. If not set, dbt will look in the current working directory first, then HOME/.dbt/",
@@ -471,7 +491,7 @@ profiles_dir_exists_false = click.option(
type=click.Path(exists=False), type=click.Path(exists=False),
) )
project_dir = click.option( project_dir = _create_option_and_track_env_var(
"--project-dir", "--project-dir",
envvar="DBT_PROJECT_DIR", envvar="DBT_PROJECT_DIR",
help="Which directory to look in for the dbt_project.yml file. Default is the current working directory and its parents.", help="Which directory to look in for the dbt_project.yml file. Default is the current working directory and its parents.",
@@ -479,16 +499,16 @@ project_dir = click.option(
type=click.Path(exists=True), type=click.Path(exists=True),
) )
quiet = click.option( quiet = _create_option_and_track_env_var(
"--quiet/--no-quiet", "--quiet/--no-quiet",
"-q", "-q",
envvar="DBT_QUIET", envvar="DBT_QUIET",
help="Suppress all non-error logging to stdout. Does not affect {{ print() }} macro calls.", help="Suppress all non-error logging to stdout. Does not affect {{ print() }} macro calls.",
) )
raw_select = click.option(*select_decls, **select_attrs) # type: ignore[arg-type] raw_select = _create_option_and_track_env_var(*select_decls, **select_attrs) # type: ignore[arg-type]
record_timing_info = click.option( record_timing_info = _create_option_and_track_env_var(
"--record-timing-info", "--record-timing-info",
"-r", "-r",
envvar=None, envvar=None,
@@ -496,7 +516,7 @@ record_timing_info = click.option(
type=click.Path(exists=False), type=click.Path(exists=False),
) )
resource_type = click.option( resource_type = _create_option_and_track_env_var(
"--resource-types", "--resource-types",
"--resource-type", "--resource-type",
envvar="DBT_RESOURCE_TYPES", envvar="DBT_RESOURCE_TYPES",
@@ -508,6 +528,7 @@ resource_type = click.option(
"saved_query", "saved_query",
"source", "source",
"analysis", "analysis",
"function",
"model", "model",
"test", "test",
"unit_test", "unit_test",
@@ -524,42 +545,41 @@ resource_type = click.option(
default=(), default=(),
) )
sample = click.option( sample = _create_option_and_track_env_var(
"--sample", "--sample",
envvar="DBT_SAMPLE", envvar="DBT_SAMPLE",
help="Run in sample mode with given SAMPLE_WINDOW spec, such that ref/source calls are sampled by the sample window.", help="Run in sample mode with given SAMPLE_WINDOW spec, such that ref/source calls are sampled by the sample window.",
default=None, default=None,
type=SampleType(), type=SampleType(),
hidden=True, # TODO: Unhide
) )
# `--select` and `--models` are analogous for most commands except `dbt list` for legacy reasons. # `--select` and `--models` are analogous for most commands except `dbt list` for legacy reasons.
# Most CLI arguments should use the combined `select` option that aliases `--models` to `--select`. # Most CLI arguments should use the combined `select` option that aliases `--models` to `--select`.
# However, if you need to split out these separators (like `dbt ls`), use the `models` and `raw_select` options instead. # However, if you need to split out these separators (like `dbt ls`), use the `models` and `raw_select` options instead.
# See https://github.com/dbt-labs/dbt-core/pull/6774#issuecomment-1408476095 for more info. # See https://github.com/dbt-labs/dbt-core/pull/6774#issuecomment-1408476095 for more info.
select = click.option(*select_decls, *model_decls, **select_attrs) # type: ignore[arg-type] select = _create_option_and_track_env_var(*select_decls, *model_decls, **select_attrs) # type: ignore[arg-type]
selector = click.option( selector = _create_option_and_track_env_var(
"--selector", "--selector",
envvar=None, envvar=None,
help="The selector name to use, as defined in selectors.yml", help="The selector name to use, as defined in selectors.yml",
) )
send_anonymous_usage_stats = click.option( send_anonymous_usage_stats = _create_option_and_track_env_var(
"--send-anonymous-usage-stats/--no-send-anonymous-usage-stats", "--send-anonymous-usage-stats/--no-send-anonymous-usage-stats",
envvar="DBT_SEND_ANONYMOUS_USAGE_STATS", envvar="DBT_SEND_ANONYMOUS_USAGE_STATS",
help="Send anonymous usage stats to dbt Labs.", help="Send anonymous usage stats to dbt Labs.",
default=True, default=True,
) )
show = click.option( show = _create_option_and_track_env_var(
"--show", "--show",
envvar=None, envvar=None,
help="Show a sample of the loaded data in the terminal", help="Show a sample of the loaded data in the terminal",
is_flag=True, is_flag=True,
) )
show_limit = click.option( show_limit = _create_option_and_track_env_var(
"--limit", "--limit",
envvar=None, envvar=None,
help="Limit the number of results returned by dbt show", help="Limit the number of results returned by dbt show",
@@ -567,7 +587,7 @@ show_limit = click.option(
default=5, default=5,
) )
show_output_format = click.option( show_output_format = _create_option_and_track_env_var(
"--output", "--output",
envvar=None, envvar=None,
help="Output format for dbt compile and dbt show", help="Output format for dbt compile and dbt show",
@@ -575,7 +595,7 @@ show_output_format = click.option(
default="text", default="text",
) )
show_resource_report = click.option( show_resource_report = _create_option_and_track_env_var(
"--show-resource-report/--no-show-resource-report", "--show-resource-report/--no-show-resource-report",
default=False, default=False,
envvar="DBT_SHOW_RESOURCE_REPORT", envvar="DBT_SHOW_RESOURCE_REPORT",
@@ -588,14 +608,14 @@ show_resource_report = click.option(
# This will need to be communicated as a change to the community! # This will need to be communicated as a change to the community!
# #
# N.B. This flag is only used for testing, hence it's hidden from help text. # N.B. This flag is only used for testing, hence it's hidden from help text.
single_threaded = click.option( single_threaded = _create_option_and_track_env_var(
"--single-threaded/--no-single-threaded", "--single-threaded/--no-single-threaded",
envvar="DBT_SINGLE_THREADED", envvar="DBT_SINGLE_THREADED",
default=False, default=False,
hidden=True, hidden=True,
) )
show_all_deprecations = click.option( show_all_deprecations = _create_option_and_track_env_var(
"--show-all-deprecations/--no-show-all-deprecations", "--show-all-deprecations/--no-show-all-deprecations",
envvar=None, envvar=None,
help="By default, each type of a deprecation warning is only shown once. Use this flag to show all deprecation warning instances.", help="By default, each type of a deprecation warning is only shown once. Use this flag to show all deprecation warning instances.",
@@ -603,7 +623,7 @@ show_all_deprecations = click.option(
default=False, default=False,
) )
skip_profile_setup = click.option( skip_profile_setup = _create_option_and_track_env_var(
"--skip-profile-setup", "--skip-profile-setup",
"-s", "-s",
envvar=None, envvar=None,
@@ -611,7 +631,7 @@ skip_profile_setup = click.option(
is_flag=True, is_flag=True,
) )
source = click.option( source = _create_option_and_track_env_var(
"--source", "--source",
envvar=None, envvar=None,
help="Source to download page from, must be one of hub, git, or local. Defaults to hub.", help="Source to download page from, must be one of hub, git, or local. Defaults to hub.",
@@ -619,7 +639,7 @@ source = click.option(
default="hub", default="hub",
) )
state = click.option( state = _create_option_and_track_env_var(
"--state", "--state",
envvar="DBT_STATE", envvar="DBT_STATE",
help="Unless overridden, use this state directory for both state comparison and deferral.", help="Unless overridden, use this state directory for both state comparison and deferral.",
@@ -632,42 +652,42 @@ state = click.option(
), ),
) )
static = click.option( static = _create_option_and_track_env_var(
"--static", "--static",
help="Generate an additional static_index.html with manifest and catalog built-in.", help="Generate an additional static_index.html with manifest and catalog built-in.",
default=False, default=False,
is_flag=True, is_flag=True,
) )
static_parser = click.option( static_parser = _create_option_and_track_env_var(
"--static-parser/--no-static-parser", "--static-parser/--no-static-parser",
envvar="DBT_STATIC_PARSER", envvar="DBT_STATIC_PARSER",
help="Use the static parser.", help="Use the static parser.",
default=True, default=True,
) )
store_failures = click.option( store_failures = _create_option_and_track_env_var(
"--store-failures", "--store-failures",
envvar="DBT_STORE_FAILURES", envvar="DBT_STORE_FAILURES",
help="Store test results (failing rows) in the database", help="Store test results (failing rows) in the database",
is_flag=True, is_flag=True,
) )
target = click.option( target = _create_option_and_track_env_var(
"--target", "--target",
"-t", "-t",
envvar="DBT_TARGET", envvar="DBT_TARGET",
help="Which target to load for the given profile", help="Which target to load for the given profile",
) )
target_path = click.option( target_path = _create_option_and_track_env_var(
"--target-path", "--target-path",
envvar="DBT_TARGET_PATH", envvar="DBT_TARGET_PATH",
help="Configure the 'target-path'. Only applies this setting for the current run. Overrides the 'DBT_TARGET_PATH' if it is set.", help="Configure the 'target-path'. Only applies this setting for the current run. Overrides the 'DBT_TARGET_PATH' if it is set.",
type=click.Path(), type=click.Path(),
) )
threads = click.option( threads = _create_option_and_track_env_var(
"--threads", "--threads",
envvar=None, envvar=None,
help="Specify number of threads to use while executing models. Overrides settings in profiles.yml.", help="Specify number of threads to use while executing models. Overrides settings in profiles.yml.",
@@ -675,41 +695,41 @@ threads = click.option(
type=click.INT, type=click.INT,
) )
upgrade = click.option( upgrade = _create_option_and_track_env_var(
"--upgrade", "--upgrade",
envvar=None, envvar=None,
help="Upgrade packages to the latest version.", help="Upgrade packages to the latest version.",
is_flag=True, is_flag=True,
) )
use_colors = click.option( use_colors = _create_option_and_track_env_var(
"--use-colors/--no-use-colors", "--use-colors/--no-use-colors",
envvar="DBT_USE_COLORS", envvar="DBT_USE_COLORS",
help="Specify whether log output is colorized in the console and the log file. Use --use-colors-file/--no-use-colors-file to colorize the log file differently than the console.", help="Specify whether log output is colorized in the console and the log file. Use --use-colors-file/--no-use-colors-file to colorize the log file differently than the console.",
default=True, default=True,
) )
use_colors_file = click.option( use_colors_file = _create_option_and_track_env_var(
"--use-colors-file/--no-use-colors-file", "--use-colors-file/--no-use-colors-file",
envvar="DBT_USE_COLORS_FILE", envvar="DBT_USE_COLORS_FILE",
help="Specify whether log file output is colorized by overriding the default value and the general --use-colors/--no-use-colors setting.", help="Specify whether log file output is colorized by overriding the default value and the general --use-colors/--no-use-colors setting.",
default=True, default=True,
) )
use_experimental_parser = click.option( use_experimental_parser = _create_option_and_track_env_var(
"--use-experimental-parser/--no-use-experimental-parser", "--use-experimental-parser/--no-use-experimental-parser",
envvar="DBT_USE_EXPERIMENTAL_PARSER", envvar="DBT_USE_EXPERIMENTAL_PARSER",
help="Enable experimental parsing features.", help="Enable experimental parsing features.",
) )
use_fast_test_edges = click.option( use_fast_test_edges = _create_option_and_track_env_var(
"--use-fast-test-edges/--no-use-fast-test-edges", "--use-fast-test-edges/--no-use-fast-test-edges",
envvar="DBT_USE_FAST_TEST_EDGES", envvar="DBT_USE_FAST_TEST_EDGES",
default=False, default=False,
hidden=True, hidden=True,
) )
vars = click.option( vars = _create_option_and_track_env_var(
"--vars", "--vars",
envvar=None, envvar=None,
help="Supply variables to the project. This argument overrides variables defined in your dbt_project.yml file. This argument should be a YAML string, eg. '{my_variable: my_value}'", help="Supply variables to the project. This argument overrides variables defined in your dbt_project.yml file. This argument should be a YAML string, eg. '{my_variable: my_value}'",
@@ -727,7 +747,7 @@ def _version_callback(ctx, _param, value):
ctx.exit() ctx.exit()
version = click.option( version = _create_option_and_track_env_var(
"--version", "--version",
"-V", "-V",
"-v", "-v",
@@ -739,14 +759,14 @@ version = click.option(
is_flag=True, is_flag=True,
) )
version_check = click.option( version_check = _create_option_and_track_env_var(
"--version-check/--no-version-check", "--version-check/--no-version-check",
envvar="DBT_VERSION_CHECK", envvar="DBT_VERSION_CHECK",
help="If set, ensure the installed dbt version matches the require-dbt-version specified in the dbt_project.yml file (if any). Otherwise, allow them to differ.", help="If set, ensure the installed dbt version matches the require-dbt-version specified in the dbt_project.yml file (if any). Otherwise, allow them to differ.",
default=True, default=True,
) )
warn_error = click.option( warn_error = _create_option_and_track_env_var(
"--warn-error", "--warn-error",
envvar="DBT_WARN_ERROR", envvar="DBT_WARN_ERROR",
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.", 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.",
@@ -754,7 +774,7 @@ warn_error = click.option(
is_flag=True, is_flag=True,
) )
warn_error_options = click.option( warn_error_options = _create_option_and_track_env_var(
"--warn-error-options", "--warn-error-options",
envvar="DBT_WARN_ERROR_OPTIONS", envvar="DBT_WARN_ERROR_OPTIONS",
default="{}", default="{}",
@@ -763,14 +783,14 @@ warn_error_options = click.option(
type=WarnErrorOptionsType(), type=WarnErrorOptionsType(),
) )
write_json = click.option( write_json = _create_option_and_track_env_var(
"--write-json/--no-write-json", "--write-json/--no-write-json",
envvar="DBT_WRITE_JSON", envvar="DBT_WRITE_JSON",
help="Whether or not to write the manifest.json and run_results.json files to the target directory", help="Whether or not to write the manifest.json and run_results.json files to the target directory",
default=True, default=True,
) )
upload_artifacts = click.option( upload_artifacts = _create_option_and_track_env_var(
"--upload-to-artifacts-ingest-api/--no-upload-to-artifacts-ingest-api", "--upload-to-artifacts-ingest-api/--no-upload-to-artifacts-ingest-api",
envvar="DBT_UPLOAD_TO_ARTIFACTS_INGEST_API", envvar="DBT_UPLOAD_TO_ARTIFACTS_INGEST_API",
help="Whether or not to upload the artifacts to the dbt Cloud API", help="Whether or not to upload the artifacts to the dbt Cloud API",

View File

@@ -3,7 +3,7 @@ import os
import time import time
import traceback import traceback
from functools import update_wrapper from functools import update_wrapper
from typing import Optional from typing import Dict, Optional
from click import Context from click import Context
@@ -17,6 +17,7 @@ from dbt.config.runtime import UnsetProfile, load_profile, load_project
from dbt.context.providers import generate_runtime_macro_context from dbt.context.providers import generate_runtime_macro_context
from dbt.context.query_header import generate_query_header_context from dbt.context.query_header import generate_query_header_context
from dbt.deprecations import show_deprecations_summary from dbt.deprecations import show_deprecations_summary
from dbt.env_vars import KNOWN_ENGINE_ENV_VARS, validate_engine_env_vars
from dbt.events.logging import setup_event_logger from dbt.events.logging import setup_event_logger
from dbt.events.types import ( from dbt.events.types import (
ArtifactUploadError, ArtifactUploadError,
@@ -56,6 +57,17 @@ from dbt_common.record import (
from dbt_common.utils import cast_dict_to_dict_of_strings from dbt_common.utils import cast_dict_to_dict_of_strings
def _cross_propagate_engine_env_vars(env_dict: Dict[str, str]) -> None:
for env_var in KNOWN_ENGINE_ENV_VARS:
if env_var.old_name is not None:
# If the old name is in the env dict, and not the new name, set the new name based on the old name
if env_var.old_name in env_dict and env_var.name not in env_dict:
env_dict[env_var.name] = env_dict[env_var.old_name]
# If the new name is in the env dict, override the old name with it
elif env_var.name in env_dict:
env_dict[env_var.old_name] = env_dict[env_var.name]
def preflight(func): def preflight(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
ctx = args[0] ctx = args[0]
@@ -69,7 +81,9 @@ def preflight(func):
# Must be set after record/replay is set up so that the env can be # Must be set after record/replay is set up so that the env can be
# recorded or replayed if needed. # recorded or replayed if needed.
get_invocation_context()._env = get_env() env_dict = get_env()
_cross_propagate_engine_env_vars(env_dict)
get_invocation_context()._env = env_dict
# Flags # Flags
flags = Flags(ctx) flags = Flags(ctx)
@@ -96,7 +110,7 @@ def preflight(func):
fire_event(MainReportArgs(args=flags_dict_str)) fire_event(MainReportArgs(args=flags_dict_str))
# Deprecation warnings # Deprecation warnings
flags.fire_deprecations() flags.fire_deprecations(ctx)
if active_user is not None: # mypy appeasement, always true if active_user is not None: # mypy appeasement, always true
fire_event(MainTrackingUserState(user_state=active_user.state())) fire_event(MainTrackingUserState(user_state=active_user.state()))
@@ -108,6 +122,9 @@ def preflight(func):
# Adapter management # Adapter management
ctx.with_resource(adapter_management()) ctx.with_resource(adapter_management())
# Validate engine env var restricted name space
validate_engine_env_vars()
return func(*args, **kwargs) return func(*args, **kwargs)
return update_wrapper(wrapper, func) return update_wrapper(wrapper, func)
@@ -119,12 +136,16 @@ def setup_record_replay():
recorder: Optional[Recorder] = None recorder: Optional[Recorder] = None
if rec_mode == RecorderMode.REPLAY: if rec_mode == RecorderMode.REPLAY:
previous_recording_path = os.environ.get("DBT_RECORDER_FILE_PATH") previous_recording_path = os.environ.get(
"DBT_ENGINE_RECORDER_FILE_PATH"
) or os.environ.get("DBT_RECORDER_FILE_PATH")
recorder = Recorder( recorder = Recorder(
RecorderMode.REPLAY, types=rec_types, previous_recording_path=previous_recording_path RecorderMode.REPLAY, types=rec_types, previous_recording_path=previous_recording_path
) )
elif rec_mode == RecorderMode.DIFF: elif rec_mode == RecorderMode.DIFF:
previous_recording_path = os.environ.get("DBT_RECORDER_FILE_PATH") previous_recording_path = os.environ.get(
"DBT_ENGINE_RECORDER_FILE_PATH"
) or os.environ.get("DBT_RECORDER_FILE_PATH")
# ensure types match the previous recording # ensure types match the previous recording
types = get_record_types_from_dict(previous_recording_path) types = get_record_types_from_dict(previous_recording_path)
recorder = Recorder( recorder = Recorder(
@@ -250,6 +271,7 @@ def profile(func):
threads = getattr(flags, "THREADS", None) threads = getattr(flags, "THREADS", None)
profile = load_profile(flags.PROJECT_DIR, flags.VARS, flags.PROFILE, flags.TARGET, threads) profile = load_profile(flags.PROJECT_DIR, flags.VARS, flags.PROFILE, flags.TARGET, threads)
ctx.obj["profile"] = profile ctx.obj["profile"] = profile
get_invocation_context().uses_adapter(profile.credentials.type)
return func(*args, **kwargs) return func(*args, **kwargs)
@@ -269,8 +291,22 @@ def project(func):
flags = ctx.obj["flags"] flags = ctx.obj["flags"]
# TODO deprecations warnings fired from loading the project will lack # TODO deprecations warnings fired from loading the project will lack
# the project_id in the snowplow event. # the project_id in the snowplow event.
# Determine if vars should be required during project loading.
# Commands that don't need vars evaluated (like 'deps', 'clean')
# should use lenient mode (require_vars=False) to allow missing vars.
# Commands that validate or execute (like 'run', 'compile', 'build', 'debug') should use
# strict mode (require_vars=True) to show helpful "Required var X not found" errors.
# If adding more commands to lenient mode, update this condition.
require_vars = flags.WHICH != "deps"
project = load_project( project = load_project(
flags.PROJECT_DIR, flags.VERSION_CHECK, ctx.obj["profile"], flags.VARS, validate=True flags.PROJECT_DIR,
flags.VERSION_CHECK,
ctx.obj["profile"],
flags.VARS,
validate=True,
require_vars=require_vars,
) )
ctx.obj["project"] = project ctx.obj["project"] = project
@@ -396,7 +432,11 @@ def setup_manifest(ctx: Context, write: bool = True, write_perf_info: bool = Fal
# if a manifest has already been set on the context, don't overwrite it # if a manifest has already been set on the context, don't overwrite it
if ctx.obj.get("manifest") is None: if ctx.obj.get("manifest") is None:
ctx.obj["manifest"] = parse_manifest( ctx.obj["manifest"] = parse_manifest(
runtime_config, write_perf_info, write, ctx.obj["flags"].write_json runtime_config,
write_perf_info,
write,
ctx.obj["flags"].write_json,
active_integrations,
) )
adapter = get_adapter(runtime_config) adapter = get_adapter(runtime_config)
else: else:
@@ -406,6 +446,5 @@ def setup_manifest(ctx: Context, write: bool = True, write_perf_info: bool = Fal
adapter.set_macro_resolver(ctx.obj["manifest"]) adapter.set_macro_resolver(ctx.obj["manifest"])
query_header_context = generate_query_header_context(adapter.config, ctx.obj["manifest"]) # type: ignore[attr-defined] query_header_context = generate_query_header_context(adapter.config, ctx.obj["manifest"]) # type: ignore[attr-defined]
adapter.connections.set_query_header(query_header_context) adapter.connections.set_query_header(query_header_context)
for integration in active_integrations: for integration in active_integrations:
adapter.add_catalog_integration(integration) adapter.add_catalog_integration(integration)

View File

@@ -26,6 +26,7 @@ from dbt.contracts.graph.nodes import (
SeedNode, SeedNode,
UnitTestDefinition, UnitTestDefinition,
UnitTestNode, UnitTestNode,
UnitTestSourceDefinition,
) )
from dbt.events.types import FoundStats, WritingInjectedSQLForNode from dbt.events.types import FoundStats, WritingInjectedSQLForNode
from dbt.exceptions import ( from dbt.exceptions import (
@@ -181,6 +182,8 @@ class Linker:
self.dependency(node.unique_id, (manifest.metrics[dependency].unique_id)) self.dependency(node.unique_id, (manifest.metrics[dependency].unique_id))
elif dependency in manifest.semantic_models: elif dependency in manifest.semantic_models:
self.dependency(node.unique_id, (manifest.semantic_models[dependency].unique_id)) self.dependency(node.unique_id, (manifest.semantic_models[dependency].unique_id))
elif dependency in manifest.functions:
self.dependency(node.unique_id, (manifest.functions[dependency].unique_id))
else: else:
raise GraphDependencyNotFoundError(node, dependency) raise GraphDependencyNotFoundError(node, dependency)
@@ -193,6 +196,8 @@ class Linker:
self.link_node(semantic_model, manifest) self.link_node(semantic_model, manifest)
for exposure in manifest.exposures.values(): for exposure in manifest.exposures.values():
self.link_node(exposure, manifest) self.link_node(exposure, manifest)
for function in manifest.functions.values():
self.link_node(function, manifest)
for metric in manifest.metrics.values(): for metric in manifest.metrics.values():
self.link_node(metric, manifest) self.link_node(metric, manifest)
for unit_test in manifest.unit_tests.values(): for unit_test in manifest.unit_tests.values():
@@ -562,7 +567,12 @@ class Compiler:
_extend_prepended_ctes(prepended_ctes, new_prepended_ctes) _extend_prepended_ctes(prepended_ctes, new_prepended_ctes)
new_cte_name = self.add_ephemeral_prefix(cte_model.identifier) cte_name = (
cte_model.cte_name
if isinstance(cte_model, UnitTestSourceDefinition)
else cte_model.identifier
)
new_cte_name = self.add_ephemeral_prefix(cte_name)
rendered_sql = cte_model._pre_injected_sql or cte_model.compiled_code rendered_sql = cte_model._pre_injected_sql or cte_model.compiled_code
sql = f" {new_cte_name} as (\n{rendered_sql}\n)" sql = f" {new_cte_name} as (\n{rendered_sql}\n)"
@@ -595,7 +605,7 @@ class Compiler:
if extra_context is None: if extra_context is None:
extra_context = {} extra_context = {}
if node.language == ModelLanguage.python: if node.language == ModelLanguage.python and node.resource_type == NodeType.Model:
context = self._create_node_context(node, manifest, extra_context) context = self._create_node_context(node, manifest, extra_context)
postfix = jinja.get_rendered( postfix = jinja.get_rendered(
@@ -650,8 +660,15 @@ class Compiler:
raise GraphDependencyNotFoundError(node, to_expression) raise GraphDependencyNotFoundError(node, to_expression)
adapter = get_adapter(self.config) adapter = get_adapter(self.config)
relation_name = str(adapter.Relation.create_from(self.config, foreign_key_node))
return relation_name if (
hasattr(foreign_key_node, "defer_relation")
and foreign_key_node.defer_relation
and self.config.args.defer
):
return str(adapter.Relation.create_from(self.config, foreign_key_node.defer_relation))
else:
return str(adapter.Relation.create_from(self.config, foreign_key_node))
# This method doesn't actually "compile" any of the nodes. That is done by the # This method doesn't actually "compile" any of the nodes. That is done by the
# "compile_node" method. This creates a Linker and builds the networkx graph, # "compile_node" method. This creates a Linker and builds the networkx graph,

View File

@@ -68,7 +68,9 @@ class Profile(HasCredentials):
threads: int, threads: int,
credentials: Credentials, credentials: Credentials,
) -> None: ) -> None:
"""Explicitly defining `__init__` to work around bug in Python 3.9.7 """
TODO: Is this no longer needed now that 3.9 is no longer supported?
Explicitly defining `__init__` to work around bug in Python 3.9.7
https://bugs.python.org/issue45081 https://bugs.python.org/issue45081
""" """
self.profile_name = profile_name self.profile_name = profile_name

View File

@@ -206,7 +206,7 @@ def load_raw_project(project_root: str, validate: bool = False) -> Dict[str, Any
project_dict = _load_yaml(project_yaml_filepath, validate=validate) project_dict = _load_yaml(project_yaml_filepath, validate=validate)
if validate: if validate:
from dbt.jsonschemas import jsonschema_validate, project_schema from dbt.jsonschemas.jsonschemas import jsonschema_validate, project_schema
jsonschema_validate( jsonschema_validate(
schema=project_schema(), json=project_dict, file_path=project_yaml_filepath schema=project_schema(), json=project_dict, file_path=project_yaml_filepath
@@ -420,9 +420,16 @@ class PartialProject(RenderComponents):
test_paths: List[str] = value_or(cfg.test_paths, ["tests"]) test_paths: List[str] = value_or(cfg.test_paths, ["tests"])
analysis_paths: List[str] = value_or(cfg.analysis_paths, ["analyses"]) analysis_paths: List[str] = value_or(cfg.analysis_paths, ["analyses"])
snapshot_paths: List[str] = value_or(cfg.snapshot_paths, ["snapshots"]) snapshot_paths: List[str] = value_or(cfg.snapshot_paths, ["snapshots"])
function_paths: List[str] = value_or(cfg.function_paths, ["functions"])
all_source_paths: List[str] = _all_source_paths( all_source_paths: List[str] = _all_source_paths(
model_paths, seed_paths, snapshot_paths, analysis_paths, macro_paths, test_paths model_paths,
seed_paths,
snapshot_paths,
analysis_paths,
macro_paths,
test_paths,
function_paths,
) )
docs_paths: List[str] = value_or(cfg.docs_paths, all_source_paths) docs_paths: List[str] = value_or(cfg.docs_paths, all_source_paths)
@@ -453,6 +460,7 @@ class PartialProject(RenderComponents):
semantic_models: Dict[str, Any] semantic_models: Dict[str, Any]
saved_queries: Dict[str, Any] saved_queries: Dict[str, Any]
exposures: Dict[str, Any] exposures: Dict[str, Any]
functions: Dict[str, Any]
vars_value: VarProvider vars_value: VarProvider
dbt_cloud: Dict[str, Any] dbt_cloud: Dict[str, Any]
@@ -469,6 +477,7 @@ class PartialProject(RenderComponents):
semantic_models = cfg.semantic_models semantic_models = cfg.semantic_models
saved_queries = cfg.saved_queries saved_queries = cfg.saved_queries
exposures = cfg.exposures exposures = cfg.exposures
functions = cfg.functions
if cfg.vars is None: if cfg.vars is None:
vars_dict: Dict[str, Any] = {} vars_dict: Dict[str, Any] = {}
else: else:
@@ -509,6 +518,7 @@ class PartialProject(RenderComponents):
asset_paths=asset_paths, asset_paths=asset_paths,
target_path=target_path, target_path=target_path,
snapshot_paths=snapshot_paths, snapshot_paths=snapshot_paths,
function_paths=function_paths,
clean_targets=clean_targets, clean_targets=clean_targets,
log_path=log_path, log_path=log_path,
packages_install_path=packages_install_path, packages_install_path=packages_install_path,
@@ -532,6 +542,7 @@ class PartialProject(RenderComponents):
semantic_models=semantic_models, semantic_models=semantic_models,
saved_queries=saved_queries, saved_queries=saved_queries,
exposures=exposures, exposures=exposures,
functions=functions,
vars=vars_value, vars=vars_value,
config_version=cfg.config_version, config_version=cfg.config_version,
unrendered=unrendered, unrendered=unrendered,
@@ -626,6 +637,7 @@ class Project:
asset_paths: List[str] asset_paths: List[str]
target_path: str target_path: str
snapshot_paths: List[str] snapshot_paths: List[str]
function_paths: List[str]
clean_targets: List[str] clean_targets: List[str]
log_path: str log_path: str
packages_install_path: str packages_install_path: str
@@ -644,6 +656,7 @@ class Project:
semantic_models: Dict[str, Any] semantic_models: Dict[str, Any]
saved_queries: Dict[str, Any] saved_queries: Dict[str, Any]
exposures: Dict[str, Any] exposures: Dict[str, Any]
functions: Dict[str, Any]
vars: VarProvider vars: VarProvider
dbt_version: List[VersionSpecifier] dbt_version: List[VersionSpecifier]
packages: PackageConfig packages: PackageConfig
@@ -666,6 +679,7 @@ class Project:
self.analysis_paths, self.analysis_paths,
self.macro_paths, self.macro_paths,
self.test_paths, self.test_paths,
self.function_paths,
) )
@property @property
@@ -732,6 +746,7 @@ class Project:
"semantic-models": self.semantic_models, "semantic-models": self.semantic_models,
"saved-queries": self.saved_queries, "saved-queries": self.saved_queries,
"exposures": self.exposures, "exposures": self.exposures,
"functions": self.functions,
"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],
"restrict-access": self.restrict_access, "restrict-access": self.restrict_access,

View File

@@ -101,7 +101,10 @@ class DbtProjectYamlRenderer(BaseRenderer):
_KEYPATH_HANDLERS = ProjectPostprocessor() _KEYPATH_HANDLERS = ProjectPostprocessor()
def __init__( def __init__(
self, profile: Optional[HasCredentials] = None, cli_vars: Optional[Dict[str, Any]] = None self,
profile: Optional[HasCredentials] = None,
cli_vars: Optional[Dict[str, Any]] = None,
require_vars: bool = True,
) -> None: ) -> None:
# Generate contexts here because we want to save the context # Generate contexts here because we want to save the context
# object in order to retrieve the env_vars. This is almost always # object in order to retrieve the env_vars. This is almost always
@@ -109,10 +112,19 @@ class DbtProjectYamlRenderer(BaseRenderer):
# even when we don't have a profile. # even when we don't have a profile.
if cli_vars is None: if cli_vars is None:
cli_vars = {} cli_vars = {}
# Store profile and cli_vars for creating strict context later
self.profile = profile
self.cli_vars = cli_vars
# By default, require vars (strict mode) for proper error messages.
# Commands that don't need vars (like 'deps') should explicitly pass
# require_vars=False for lenient loading.
if profile: if profile:
self.ctx_obj = TargetContext(profile.to_target_dict(), cli_vars) self.ctx_obj = TargetContext(
profile.to_target_dict(), cli_vars, require_vars=require_vars
)
else: else:
self.ctx_obj = BaseContext(cli_vars) # type:ignore self.ctx_obj = BaseContext(cli_vars, require_vars=require_vars) # type:ignore
context = self.ctx_obj.to_dict() context = self.ctx_obj.to_dict()
super().__init__(context) super().__init__(context)

View File

@@ -23,7 +23,7 @@ from dbt.adapters.contracts.connection import (
) )
from dbt.adapters.contracts.relation import ComponentName from dbt.adapters.contracts.relation import ComponentName
from dbt.adapters.factory import get_include_paths, get_relation_class_by_name from dbt.adapters.factory import get_include_paths, get_relation_class_by_name
from dbt.artifacts.resources.v1.components import Quoting from dbt.artifacts.resources import Quoting
from dbt.config.project import load_raw_project from dbt.config.project import load_raw_project
from dbt.contracts.graph.manifest import ManifestMetadata from dbt.contracts.graph.manifest import ManifestMetadata
from dbt.contracts.project import Configuration from dbt.contracts.project import Configuration
@@ -52,9 +52,10 @@ def load_project(
profile: HasCredentials, profile: HasCredentials,
cli_vars: Optional[Dict[str, Any]] = None, cli_vars: Optional[Dict[str, Any]] = None,
validate: bool = False, validate: bool = False,
require_vars: bool = True,
) -> Project: ) -> Project:
# get the project with all of the provided information # get the project with all of the provided information
project_renderer = DbtProjectYamlRenderer(profile, cli_vars) project_renderer = DbtProjectYamlRenderer(profile, cli_vars, require_vars=require_vars)
project = Project.from_project_root( project = Project.from_project_root(
project_root, project_renderer, verify_version=version_check, validate=validate project_root, project_renderer, verify_version=version_check, validate=validate
) )
@@ -155,6 +156,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
analysis_paths=project.analysis_paths, analysis_paths=project.analysis_paths,
docs_paths=project.docs_paths, docs_paths=project.docs_paths,
asset_paths=project.asset_paths, asset_paths=project.asset_paths,
function_paths=project.function_paths,
target_path=project.target_path, target_path=project.target_path,
snapshot_paths=project.snapshot_paths, snapshot_paths=project.snapshot_paths,
clean_targets=project.clean_targets, clean_targets=project.clean_targets,
@@ -180,6 +182,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
semantic_models=project.semantic_models, semantic_models=project.semantic_models,
saved_queries=project.saved_queries, saved_queries=project.saved_queries,
exposures=project.exposures, exposures=project.exposures,
functions=project.functions,
vars=project.vars, vars=project.vars,
config_version=project.config_version, config_version=project.config_version,
unrendered=project.unrendered, unrendered=project.unrendered,
@@ -265,7 +268,14 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
args, args,
) )
flags = get_flags() flags = get_flags()
project = load_project(project_root, bool(flags.VERSION_CHECK), profile, cli_vars) # For dbt deps, use lenient var validation to allow missing vars
# For all other commands, use strict validation for helpful error messages
# If command is not set (e.g., during test setup), default to strict mode
# unless the command is explicitly "deps"
require_vars = getattr(flags, "WHICH", None) != "deps"
project = load_project(
project_root, bool(flags.VERSION_CHECK), profile, cli_vars, require_vars=require_vars
)
return project, profile return project, profile
# Called in task/base.py, in BaseTask.from_args # Called in task/base.py, in BaseTask.from_args
@@ -303,6 +313,9 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
identifier=self.quoting.get("identifier", None), identifier=self.quoting.get("identifier", None),
column=self.quoting.get("column", None), column=self.quoting.get("column", None),
), ),
run_started_at=(
tracking.active_user.run_started_at if tracking.active_user is not None else None
),
) )
def _get_v2_config_paths( def _get_v2_config_paths(
@@ -350,6 +363,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
"semantic_models": self._get_config_paths(self.semantic_models), "semantic_models": self._get_config_paths(self.semantic_models),
"saved_queries": self._get_config_paths(self.saved_queries), "saved_queries": self._get_config_paths(self.saved_queries),
"exposures": self._get_config_paths(self.exposures), "exposures": self._get_config_paths(self.exposures),
"functions": self._get_config_paths(self.functions),
} }
def warn_for_unused_resource_config_paths( def warn_for_unused_resource_config_paths(

View File

@@ -12,6 +12,7 @@ from typing import Any, Callable, Dict, Iterable, List, Mapping, NoReturn, Optio
# approaches which will extend well to potentially many modules # approaches which will extend well to potentially many modules
import pytz import pytz
import dbt.deprecations as deprecations
import dbt.flags as flags_module import dbt.flags as flags_module
from dbt import tracking, utils from dbt import tracking, utils
from dbt.clients.jinja import get_rendered from dbt.clients.jinja import get_rendered
@@ -83,7 +84,14 @@ def get_itertools_module_context() -> Dict[str, Any]:
"combinations_with_replacement", "combinations_with_replacement",
] ]
return {name: getattr(itertools, name) for name in context_exports} def deprecation_wrapper(fn):
def deprecation_wrapper_inner(*args, **kwargs):
deprecations.warn("modules-itertools-usage-deprecation")
return fn(*args, **kwargs)
return deprecation_wrapper_inner
return {name: deprecation_wrapper(getattr(itertools, name)) for name in context_exports}
def get_context_modules() -> Dict[str, Dict[str, Any]]: def get_context_modules() -> Dict[str, Dict[str, Any]]:
@@ -144,10 +152,12 @@ class Var:
context: Mapping[str, Any], context: Mapping[str, Any],
cli_vars: Mapping[str, Any], cli_vars: Mapping[str, Any],
node: Optional[Resource] = None, node: Optional[Resource] = None,
require_vars: bool = True,
) -> 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[Resource] = node self._node: Optional[Resource] = node
self._require_vars: bool = require_vars
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]:
@@ -160,7 +170,9 @@ class Var:
else: else:
return "<Configuration>" return "<Configuration>"
def get_missing_var(self, var_name: str) -> NoReturn: def get_missing_var(self, var_name: str) -> None:
# Only raise an error if vars are _required_
if self._require_vars:
# TODO function name implies a non exception resolution # TODO function name implies a non exception resolution
raise RequiredVarNotFoundError(var_name, dict(self._merged), self._node) raise RequiredVarNotFoundError(var_name, dict(self._merged), self._node)
@@ -190,10 +202,11 @@ class BaseContext(metaclass=ContextMeta):
_context_attrs_: Dict[str, Any] _context_attrs_: Dict[str, Any]
# subclass is TargetContext # subclass is TargetContext
def __init__(self, cli_vars: Dict[str, Any]) -> None: def __init__(self, cli_vars: Dict[str, Any], require_vars: bool = True) -> None:
self._ctx: Dict[str, Any] = {} self._ctx: Dict[str, Any] = {}
self.cli_vars: Dict[str, Any] = cli_vars self.cli_vars: Dict[str, Any] = cli_vars
self.env_vars: Dict[str, Any] = {} self.env_vars: Dict[str, Any] = {}
self.require_vars: bool = require_vars
def generate_builtins(self) -> Dict[str, Any]: def generate_builtins(self) -> Dict[str, Any]:
builtins: Dict[str, Any] = {} builtins: Dict[str, Any] = {}
@@ -299,7 +312,7 @@ class BaseContext(metaclass=ContextMeta):
from events from events
where event_type = '{{ var("event_type", "activation") }}' where event_type = '{{ var("event_type", "activation") }}'
""" """
return Var(self._ctx, self.cli_vars) return Var(self._ctx, self.cli_vars, require_vars=self.require_vars)
@contextmember() @contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str: def env_var(self, var: str, default: Optional[str] = None) -> str:

View File

@@ -15,8 +15,8 @@ class ConfiguredContext(TargetContext):
# subclasses are SchemaYamlContext, MacroResolvingContext, ManifestContext # subclasses are SchemaYamlContext, MacroResolvingContext, ManifestContext
config: AdapterRequiredConfig config: AdapterRequiredConfig
def __init__(self, config: AdapterRequiredConfig) -> None: def __init__(self, config: AdapterRequiredConfig, require_vars: bool = True) -> None:
super().__init__(config.to_target_dict(), config.cli_vars) super().__init__(config.to_target_dict(), config.cli_vars, require_vars=require_vars)
self.config = config self.config = config
@contextproperty() @contextproperty()

View File

@@ -88,6 +88,8 @@ class RenderedConfig(ConfigSource):
model_configs = self.project.exposures model_configs = self.project.exposures
elif resource_type == NodeType.Unit: elif resource_type == NodeType.Unit:
model_configs = self.project.unit_tests model_configs = self.project.unit_tests
elif resource_type == NodeType.Function:
model_configs = self.project.functions
else: else:
model_configs = self.project.models model_configs = self.project.models
return model_configs return model_configs
@@ -141,17 +143,21 @@ class BaseContextConfigGenerator(Generic[T]):
def calculate_node_config( def calculate_node_config(
self, self,
# this is the config from the sql file
config_call_dict: Dict[str, Any], config_call_dict: Dict[str, Any],
fqn: List[str], fqn: List[str],
resource_type: NodeType, resource_type: NodeType,
project_name: str, project_name: str,
base: bool, base: bool,
# this is the config from the schema file
patch_config_dict: Optional[Dict[str, Any]] = None, patch_config_dict: Optional[Dict[str, Any]] = None,
) -> BaseConfig: ) -> BaseConfig:
own_config = self.get_node_project(project_name) own_config = self.get_node_project(project_name)
result = self.initial_result(resource_type=resource_type, base=base) result = self.initial_result(resource_type=resource_type, base=base)
# builds the config from what was specified in the runtime_config, which generally
# comes from the project's dbt_project.yml file.
project_configs = self._project_configs(own_config, fqn, resource_type) project_configs = self._project_configs(own_config, fqn, resource_type)
for fqn_config in project_configs: for fqn_config in project_configs:
result = self._update_from_config(result, fqn_config) result = self._update_from_config(result, fqn_config)

View File

@@ -20,7 +20,7 @@ from typing_extensions import Protocol
from dbt import selected_resources from dbt import selected_resources
from dbt.adapters.base.column import Column from dbt.adapters.base.column import Column
from dbt.adapters.base.relation import EventTimeFilter from dbt.adapters.base.relation import EventTimeFilter, RelationType
from dbt.adapters.contracts.connection import AdapterResponse from dbt.adapters.contracts.connection import AdapterResponse
from dbt.adapters.exceptions import MissingConfigError from dbt.adapters.exceptions import MissingConfigError
from dbt.adapters.factory import ( from dbt.adapters.factory import (
@@ -56,6 +56,7 @@ from dbt.contracts.graph.metrics import MetricReference, ResolvedMetricReference
from dbt.contracts.graph.nodes import ( from dbt.contracts.graph.nodes import (
AccessType, AccessType,
Exposure, Exposure,
FunctionNode,
Macro, Macro,
ManifestNode, ManifestNode,
ModelNode, ModelNode,
@@ -242,9 +243,59 @@ class BaseResolver(metaclass=abc.ABCMeta):
def resolve_limit(self) -> Optional[int]: def resolve_limit(self) -> Optional[int]:
return 0 if getattr(self.config.args, "EMPTY", False) else None return 0 if getattr(self.config.args, "EMPTY", False) else None
def _resolve_event_time_field_name(self, target: ManifestNode) -> str:
"""Get the event time field name with proper quoting based on configuration."""
# Default to False for quoting
should_quote = False
column_found = False
column = None
# Check if config has event_time attribute
if not hasattr(target.config, "event_time") or target.config.event_time is None:
return ""
# Check column-level quote configuration first (overrides source-level)
if hasattr(target, "columns") and target.columns and isinstance(target.columns, dict):
for _, column_info in target.columns.items():
if column_info.name == target.config.event_time:
column_found = True
# Create the column object
column = Column.create(
column_info.name, column_info.data_type if column_info.data_type else ""
)
# Column-level quote setting takes precedence
if hasattr(column_info, "quote") and column_info.quote is not None:
should_quote = column_info.quote
# Fallback to source-level quote setting
elif (
hasattr(target, "quoting")
and hasattr(target.quoting, "column")
and target.quoting.column is not None
):
should_quote = target.quoting.column
break
# If column not found, fall back to source-level quote setting
if not column_found:
if (
hasattr(target, "quoting")
and hasattr(target.quoting, "column")
and target.quoting.column is not None
):
should_quote = target.quoting.column
# Create column object for quoting
column = Column.create(target.config.event_time, "")
# Apply quoting logic
if should_quote and column is not None:
return column.quoted
else:
return target.config.event_time
def resolve_event_time_filter(self, target: ManifestNode) -> Optional[EventTimeFilter]: def resolve_event_time_filter(self, target: ManifestNode) -> Optional[EventTimeFilter]:
event_time_filter = None event_time_filter = None
sample_mode = getattr(self.config.args, "sample", None) is not None sample_mode = getattr(self.config.args, "sample", None) is not None
field_name = self._resolve_event_time_field_name(target)
# TODO The number of branches here is getting rough. We should consider ways to simplify # TODO The number of branches here is getting rough. We should consider ways to simplify
# what is going on to make it easier to maintain # what is going on to make it easier to maintain
@@ -277,7 +328,7 @@ class BaseResolver(metaclass=abc.ABCMeta):
else self.model.batch.event_time_end else self.model.batch.event_time_end
) )
event_time_filter = EventTimeFilter( event_time_filter = EventTimeFilter(
field_name=target.config.event_time, field_name=field_name,
start=start, start=start,
end=end, end=end,
) )
@@ -285,7 +336,7 @@ class BaseResolver(metaclass=abc.ABCMeta):
# Regular microbatch models # Regular microbatch models
else: else:
event_time_filter = EventTimeFilter( event_time_filter = EventTimeFilter(
field_name=target.config.event_time, field_name=field_name,
start=self.model.batch.event_time_start, start=self.model.batch.event_time_start,
end=self.model.batch.event_time_end, end=self.model.batch.event_time_end,
) )
@@ -293,7 +344,7 @@ class BaseResolver(metaclass=abc.ABCMeta):
# Sample mode _non_ microbatch models # Sample mode _non_ microbatch models
elif sample_mode: elif sample_mode:
event_time_filter = EventTimeFilter( event_time_filter = EventTimeFilter(
field_name=target.config.event_time, field_name=field_name,
start=self.config.args.sample.start, start=self.config.args.sample.start,
end=self.config.args.sample.end, end=self.config.args.sample.end,
) )
@@ -408,6 +459,41 @@ class BaseMetricResolver(BaseResolver):
return self.resolve(name, package) return self.resolve(name, package)
class BaseFunctionResolver(BaseResolver):
@abc.abstractmethod
def resolve(self, name: str, package: Optional[str] = None): ...
def _repack_args(self, name: str, package: Optional[str]) -> List[str]:
if package is None:
return [name]
else:
return [package, name]
def validate_args(self, name: str, package: Optional[str]):
if not isinstance(name, str):
raise CompilationError(
f"The name argument to function() must be a string, got {type(name)}"
)
if package is not None and not isinstance(package, str):
raise CompilationError(
f"The package argument to function() must be a string or None, got {type(package)}"
)
def __call__(self, *args: str):
name: str
package: Optional[str] = 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)
return self.resolve(name, package)
class Config(Protocol): class Config(Protocol):
def __init__(self, model, context_config: Optional[ContextConfig]): ... def __init__(self, model, context_config: Optional[ContextConfig]): ...
@@ -458,9 +544,15 @@ class ParseConfigObject(Config):
def require(self, name, validator=None): def require(self, name, validator=None):
return "" return ""
def meta_require(self, name, validator=None):
return ""
def get(self, name, default=None, validator=None): def get(self, name, default=None, validator=None):
return "" return ""
def meta_get(self, name, default=None, validator=None):
return ""
def persist_relation_docs(self) -> bool: def persist_relation_docs(self) -> bool:
return False return False
@@ -492,6 +584,16 @@ class RuntimeConfigObject(Config):
raise MissingConfigError(unique_id=self.model.unique_id, name=name) raise MissingConfigError(unique_id=self.model.unique_id, name=name)
return result return result
def _lookup_meta(self, name, default=_MISSING):
# if this is a macro, there might be no `model.config`.
if not hasattr(self.model, "config"):
result = default
else:
result = self.model.config.meta_get(name, default)
if result is _MISSING:
raise MissingConfigError(unique_id=self.model.unique_id, name=name)
return result
def require(self, name, validator=None): def require(self, name, validator=None):
to_return = self._lookup(name) to_return = self._lookup(name)
@@ -500,6 +602,12 @@ class RuntimeConfigObject(Config):
return to_return return to_return
def meta_require(self, name, validator=None):
to_return = self._lookup_meta(name)
if validator is not None:
self._validate(validator, to_return)
def get(self, name, default=None, validator=None): def get(self, name, default=None, validator=None):
to_return = self._lookup(name, default) to_return = self._lookup(name, default)
@@ -508,6 +616,14 @@ class RuntimeConfigObject(Config):
return to_return return to_return
def meta_get(self, name, default=None, validator=None):
to_return = self._lookup_meta(name, default)
if validator is not None and default is not None:
self._validate(validator, to_return)
return to_return
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):
@@ -768,7 +884,12 @@ class RuntimeUnitTestSourceResolver(BaseSourceResolver):
# we just need to set_cte, but skipping it confuses typing. We *do* need # we just need to set_cte, but skipping it confuses typing. We *do* need
# the relation in the "this" property. # the relation in the "this" property.
self.model.set_cte(target_source.unique_id, None) self.model.set_cte(target_source.unique_id, None)
return self.Relation.create_ephemeral_from(target_source)
identifier = self.Relation.add_ephemeral_prefix(target_source.cte_name)
return self.Relation.create(
type=self.Relation.CTE,
identifier=identifier,
).quote(identifier=False)
# metric` implementations # metric` implementations
@@ -862,6 +983,51 @@ class UnitTestVar(RuntimeVar):
super().__init__(context, config_copy or config, node=node) super().__init__(context, config_copy or config, node=node)
# `function` implementations.
class ParseFunctionResolver(BaseFunctionResolver):
def resolve(self, name: str, package: Optional[str] = None):
# When you call function(), this is what happens at parse time
self.model.functions.append(self._repack_args(name, package))
return self.Relation.create_from(self.config, self.model, type=RelationType.Function)
class RuntimeFunctionResolver(BaseFunctionResolver):
def resolve(self, name: str, package: Optional[str] = None):
target_function = self.manifest.resolve_function(
name,
package,
self.current_project,
self.model.package_name,
)
if target_function is None or isinstance(target_function, Disabled):
raise TargetNotFoundError(
node=self.model,
target_name=name,
target_kind="function",
disabled=(isinstance(target_function, Disabled)),
)
# Source quoting does _not_ respect global configs in dbt_project.yml, as documented here:
# https://docs.getdbt.com/reference/project-configs/quoting
# Use an object with an empty quoting field to bypass any settings in self.
class SourceQuotingBaseConfig:
quoting: Dict[str, Any] = {}
return self.Relation.create_from(
SourceQuotingBaseConfig(),
target_function,
limit=self.resolve_limit,
event_time_filter=self.resolve_event_time_filter(target_function),
type=RelationType.Function,
)
# TODO: Right now the RuntimeUnitTestProvider uses the RuntimeFunctionResolver for functions,
# but for CT-12025 we'll likely need to create a separate RuntimeUnitTestFunctionResolver to
# handle function overrides (mocking functions)
# Providers # Providers
class Provider(Protocol): class Provider(Protocol):
execute: bool execute: bool
@@ -871,6 +1037,7 @@ class Provider(Protocol):
ref: Type[BaseRefResolver] ref: Type[BaseRefResolver]
source: Type[BaseSourceResolver] source: Type[BaseSourceResolver]
metric: Type[BaseMetricResolver] metric: Type[BaseMetricResolver]
function: Type[BaseFunctionResolver]
class ParseProvider(Provider): class ParseProvider(Provider):
@@ -881,6 +1048,7 @@ class ParseProvider(Provider):
ref = ParseRefResolver ref = ParseRefResolver
source = ParseSourceResolver source = ParseSourceResolver
metric = ParseMetricResolver metric = ParseMetricResolver
function = ParseFunctionResolver
class GenerateNameProvider(Provider): class GenerateNameProvider(Provider):
@@ -891,6 +1059,7 @@ class GenerateNameProvider(Provider):
ref = ParseRefResolver ref = ParseRefResolver
source = ParseSourceResolver source = ParseSourceResolver
metric = ParseMetricResolver metric = ParseMetricResolver
function = ParseFunctionResolver
class RuntimeProvider(Provider): class RuntimeProvider(Provider):
@@ -901,6 +1070,7 @@ class RuntimeProvider(Provider):
ref = RuntimeRefResolver ref = RuntimeRefResolver
source = RuntimeSourceResolver source = RuntimeSourceResolver
metric = RuntimeMetricResolver metric = RuntimeMetricResolver
function = RuntimeFunctionResolver
class RuntimeUnitTestProvider(Provider): class RuntimeUnitTestProvider(Provider):
@@ -911,6 +1081,7 @@ class RuntimeUnitTestProvider(Provider):
ref = RuntimeUnitTestRefResolver ref = RuntimeUnitTestRefResolver
source = RuntimeUnitTestSourceResolver source = RuntimeUnitTestSourceResolver
metric = RuntimeMetricResolver metric = RuntimeMetricResolver
function = RuntimeFunctionResolver
class OperationProvider(RuntimeProvider): class OperationProvider(RuntimeProvider):
@@ -1153,6 +1324,10 @@ class ProviderContext(ManifestContext):
def metric(self) -> Callable: def metric(self) -> Callable:
return self.provider.metric(self.db_wrapper, self.model, self.config, self.manifest) return self.provider.metric(self.db_wrapper, self.model, self.config, self.manifest)
@contextproperty()
def function(self) -> Callable:
return self.provider.function(self.db_wrapper, self.model, self.config, self.manifest)
@contextproperty("config") @contextproperty("config")
def ctx_config(self) -> Config: def ctx_config(self) -> Config:
"""The `config` variable exists to handle end-user configuration for """The `config` variable exists to handle end-user configuration for
@@ -1741,6 +1916,14 @@ class UnitTestContext(ModelContext):
return None return None
class FunctionContext(ModelContext):
model: FunctionNode
@contextproperty()
def this(self) -> Optional[RelationProxy]:
return self.db_wrapper.Relation.create_from(self.config, self.model)
# This is called by '_context_for', used in 'render_with_context' # This is called by '_context_for', used in 'render_with_context'
def generate_parser_model_context( def generate_parser_model_context(
model: ManifestNode, model: ManifestNode,
@@ -1757,6 +1940,21 @@ def generate_parser_model_context(
return ctx.to_dict() return ctx.to_dict()
def generate_parser_unit_test_context(
unit_test: UnitTestNode, config: RuntimeConfig, manifest: Manifest
) -> Dict[str, Any]:
context_config = ContextConfig(
config,
unit_test.fqn,
NodeType.Unit,
config.project_name,
)
ctx = UnitTestContext(unit_test, config, manifest, ParseProvider(), context_config)
return ctx.to_dict()
def generate_generate_name_macro_context( def generate_generate_name_macro_context(
macro: Macro, macro: Macro,
config: RuntimeConfig, config: RuntimeConfig,
@@ -1838,6 +2036,15 @@ def generate_runtime_unit_test_context(
return ctx_dict return ctx_dict
def generate_runtime_function_context(
function: FunctionNode,
config: RuntimeConfig,
manifest: Manifest,
) -> Dict[str, Any]:
ctx = FunctionContext(function, config, manifest, OperationProvider(), None)
return ctx.to_dict()
class ExposureRefResolver(BaseResolver): class ExposureRefResolver(BaseResolver):
def __call__(self, *args, **kwargs) -> str: def __call__(self, *args, **kwargs) -> str:
package = None package = None

View File

@@ -5,8 +5,10 @@ from dbt.context.base import BaseContext, contextproperty
class TargetContext(BaseContext): class TargetContext(BaseContext):
# subclass is ConfiguredContext # subclass is ConfiguredContext
def __init__(self, target_dict: Dict[str, Any], cli_vars: Dict[str, Any]): def __init__(
super().__init__(cli_vars=cli_vars) self, target_dict: Dict[str, Any], cli_vars: Dict[str, Any], require_vars: bool = True
):
super().__init__(cli_vars=cli_vars, require_vars=require_vars)
self.target_dict = target_dict self.target_dict = target_dict
@contextproperty() @contextproperty()

View File

@@ -23,6 +23,7 @@ class ParseFileType(StrEnum):
Schema = "schema" Schema = "schema"
Hook = "hook" # not a real filetype, from dbt_project.yml Hook = "hook" # not a real filetype, from dbt_project.yml
Fixture = "fixture" Fixture = "fixture"
Function = "function"
parse_file_type_to_parser = { parse_file_type_to_parser = {
@@ -37,6 +38,7 @@ parse_file_type_to_parser = {
ParseFileType.Schema: "SchemaParser", ParseFileType.Schema: "SchemaParser",
ParseFileType.Hook: "HookParser", ParseFileType.Hook: "HookParser",
ParseFileType.Fixture: "FixtureParser", ParseFileType.Fixture: "FixtureParser",
ParseFileType.Function: "FunctionParser",
} }
@@ -159,6 +161,7 @@ class SourceFile(BaseSourceFile):
docs: List[str] = field(default_factory=list) docs: List[str] = field(default_factory=list)
macros: List[str] = field(default_factory=list) macros: List[str] = field(default_factory=list)
env_vars: List[str] = field(default_factory=list) env_vars: List[str] = field(default_factory=list)
functions: List[str] = field(default_factory=list)
@classmethod @classmethod
def big_seed(cls, path: FilePath) -> "SourceFile": def big_seed(cls, path: FilePath) -> "SourceFile":
@@ -191,6 +194,7 @@ class SchemaSourceFile(BaseSourceFile):
data_tests: Dict[str, Any] = field(default_factory=dict) data_tests: Dict[str, Any] = field(default_factory=dict)
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)
functions: List[str] = field(default_factory=list)
metrics: List[str] = field(default_factory=list) metrics: List[str] = field(default_factory=list)
snapshots: List[str] = field(default_factory=list) snapshots: List[str] = field(default_factory=list)
# The following field will no longer be used. Leaving # The following field will no longer be used. Leaving

View File

@@ -32,8 +32,13 @@ from dbt.adapters.exceptions import (
from dbt.adapters.factory import get_adapter_package_names from dbt.adapters.factory import get_adapter_package_names
# to preserve import paths # to preserve import paths
from dbt.artifacts.resources import BaseResource, DeferRelation, NodeVersion, RefArgs from dbt.artifacts.resources import (
from dbt.artifacts.resources.v1.config import NodeConfig BaseResource,
DeferRelation,
NodeConfig,
NodeVersion,
RefArgs,
)
from dbt.artifacts.schemas.manifest import ManifestMetadata, UniqueID, WritableManifest from dbt.artifacts.schemas.manifest import ManifestMetadata, UniqueID, WritableManifest
from dbt.clients.jinja_static import statically_parse_ref_or_source from dbt.clients.jinja_static import statically_parse_ref_or_source
from dbt.contracts.files import ( from dbt.contracts.files import (
@@ -48,6 +53,7 @@ from dbt.contracts.graph.nodes import (
BaseNode, BaseNode,
Documentation, Documentation,
Exposure, Exposure,
FunctionNode,
GenericTestNode, GenericTestNode,
GraphMemberNode, GraphMemberNode,
Group, Group,
@@ -172,8 +178,41 @@ class SourceLookup(dbtClassMixin):
return manifest.sources[unique_id] return manifest.sources[unique_id]
class FunctionLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest") -> None:
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_function(self, function: FunctionNode):
if function.search_name not in self.storage:
self.storage[function.search_name] = {}
self.storage[function.search_name][function.package_name] = function.unique_id
def populate(self, manifest):
for function in manifest.functions.values():
if hasattr(function, "name"):
self.add_function(function)
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> FunctionNode:
if unique_id not in manifest.functions:
raise dbt_common.exceptions.DbtInternalError(
f"Function {unique_id} found in cache but not found in manifest"
)
return manifest.functions[unique_id]
class RefableLookup(dbtClassMixin): class RefableLookup(dbtClassMixin):
# model, seed, snapshot # model, seed, snapshot, function
_lookup_types: ClassVar[set] = set(REFABLE_NODE_TYPES) _lookup_types: ClassVar[set] = set(REFABLE_NODE_TYPES)
_versioned_types: ClassVar[set] = set(VERSIONED_NODE_TYPES) _versioned_types: ClassVar[set] = set(VERSIONED_NODE_TYPES)
@@ -681,6 +720,9 @@ class Disabled(Generic[D]):
target: D target: D
MaybeFunctionNode = Optional[Union[FunctionNode, Disabled[FunctionNode]]]
MaybeMetricNode = Optional[Union[Metric, Disabled[Metric]]] MaybeMetricNode = Optional[Union[Metric, Disabled[Metric]]]
@@ -877,6 +919,7 @@ class Manifest(MacroMethods, dbtClassMixin):
macros: MutableMapping[str, Macro] = field(default_factory=dict) macros: MutableMapping[str, Macro] = field(default_factory=dict)
docs: MutableMapping[str, Documentation] = field(default_factory=dict) docs: MutableMapping[str, Documentation] = field(default_factory=dict)
exposures: MutableMapping[str, Exposure] = field(default_factory=dict) exposures: MutableMapping[str, Exposure] = field(default_factory=dict)
functions: MutableMapping[str, FunctionNode] = field(default_factory=dict)
metrics: MutableMapping[str, Metric] = field(default_factory=dict) metrics: MutableMapping[str, Metric] = field(default_factory=dict)
groups: MutableMapping[str, Group] = field(default_factory=dict) groups: MutableMapping[str, Group] = field(default_factory=dict)
selectors: MutableMapping[str, Any] = field(default_factory=dict) selectors: MutableMapping[str, Any] = field(default_factory=dict)
@@ -919,6 +962,9 @@ class Manifest(MacroMethods, dbtClassMixin):
_singular_test_lookup: Optional[SingularTestLookup] = field( _singular_test_lookup: Optional[SingularTestLookup] = field(
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None} default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
) )
_function_lookup: Optional[FunctionLookup] = field(
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
)
_parsing_info: ParsingInfo = field( _parsing_info: ParsingInfo = field(
default_factory=ParsingInfo, default_factory=ParsingInfo,
metadata={"serialize": lambda x: None, "deserialize": lambda x: None}, metadata={"serialize": lambda x: None, "deserialize": lambda x: None},
@@ -955,6 +1001,7 @@ class Manifest(MacroMethods, 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()},
"functions": {k: v.to_dict(omit_none=False) for k, v in self.functions.items()},
"groups": {k: v.to_dict(omit_none=False) for k, v in self.groups.items()}, "groups": {k: v.to_dict(omit_none=False) for k, v in self.groups.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()},
"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()},
@@ -1048,6 +1095,7 @@ class Manifest(MacroMethods, dbtClassMixin):
resource_fqns: Dict[str, Set[Tuple[str, ...]]] = {} resource_fqns: Dict[str, Set[Tuple[str, ...]]] = {}
all_resources = chain( all_resources = chain(
self.exposures.values(), self.exposures.values(),
self.functions.values(),
self.nodes.values(), self.nodes.values(),
self.sources.values(), self.sources.values(),
self.metrics.values(), self.metrics.values(),
@@ -1081,6 +1129,7 @@ class Manifest(MacroMethods, dbtClassMixin):
macros={k: _deepcopy(v) for k, v in self.macros.items()}, macros={k: _deepcopy(v) for k, v in self.macros.items()},
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()},
functions={k: _deepcopy(v) for k, v in self.functions.items()},
metrics={k: _deepcopy(v) for k, v in self.metrics.items()}, metrics={k: _deepcopy(v) for k, v in self.metrics.items()},
groups={k: _deepcopy(v) for k, v in self.groups.items()}, groups={k: _deepcopy(v) for k, v in self.groups.items()},
selectors={k: _deepcopy(v) for k, v in self.selectors.items()}, selectors={k: _deepcopy(v) for k, v in self.selectors.items()},
@@ -1101,6 +1150,7 @@ class Manifest(MacroMethods, dbtClassMixin):
self.nodes.values(), self.nodes.values(),
self.sources.values(), self.sources.values(),
self.exposures.values(), self.exposures.values(),
self.functions.values(),
self.metrics.values(), self.metrics.values(),
self.semantic_models.values(), self.semantic_models.values(),
self.saved_queries.values(), self.saved_queries.values(),
@@ -1157,6 +1207,7 @@ class Manifest(MacroMethods, dbtClassMixin):
macros=cls._map_resources_to_map_nodes(writable_manifest.macros), macros=cls._map_resources_to_map_nodes(writable_manifest.macros),
docs=cls._map_resources_to_map_nodes(writable_manifest.docs), docs=cls._map_resources_to_map_nodes(writable_manifest.docs),
exposures=cls._map_resources_to_map_nodes(writable_manifest.exposures), exposures=cls._map_resources_to_map_nodes(writable_manifest.exposures),
functions=cls._map_resources_to_map_nodes(writable_manifest.functions),
metrics=cls._map_resources_to_map_nodes(writable_manifest.metrics), metrics=cls._map_resources_to_map_nodes(writable_manifest.metrics),
groups=cls._map_resources_to_map_nodes(writable_manifest.groups), groups=cls._map_resources_to_map_nodes(writable_manifest.groups),
semantic_models=cls._map_resources_to_map_nodes(writable_manifest.semantic_models), semantic_models=cls._map_resources_to_map_nodes(writable_manifest.semantic_models),
@@ -1165,6 +1216,7 @@ class Manifest(MacroMethods, dbtClassMixin):
selector_id: selector selector_id: selector
for selector_id, selector in writable_manifest.selectors.items() for selector_id, selector in writable_manifest.selectors.items()
}, },
metadata=writable_manifest.metadata,
) )
return manifest return manifest
@@ -1213,6 +1265,7 @@ class Manifest(MacroMethods, dbtClassMixin):
macros=self._map_nodes_to_map_resources(self.macros), macros=self._map_nodes_to_map_resources(self.macros),
docs=self._map_nodes_to_map_resources(self.docs), docs=self._map_nodes_to_map_resources(self.docs),
exposures=self._map_nodes_to_map_resources(self.exposures), exposures=self._map_nodes_to_map_resources(self.exposures),
functions=self._map_nodes_to_map_resources(self.functions),
metrics=self._map_nodes_to_map_resources(self.metrics), metrics=self._map_nodes_to_map_resources(self.metrics),
groups=self._map_nodes_to_map_resources(self.groups), groups=self._map_nodes_to_map_resources(self.groups),
selectors=self.selectors, selectors=self.selectors,
@@ -1240,6 +1293,8 @@ class Manifest(MacroMethods, dbtClassMixin):
return self.sources[unique_id] return self.sources[unique_id]
elif unique_id in self.exposures: elif unique_id in self.exposures:
return self.exposures[unique_id] return self.exposures[unique_id]
elif unique_id in self.functions:
return self.functions[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.semantic_models: elif unique_id in self.semantic_models:
@@ -1322,6 +1377,12 @@ class Manifest(MacroMethods, dbtClassMixin):
self._singular_test_lookup = SingularTestLookup(self) self._singular_test_lookup = SingularTestLookup(self)
return self._singular_test_lookup return self._singular_test_lookup
@property
def function_lookup(self) -> FunctionLookup:
if self._function_lookup is None:
self._function_lookup = FunctionLookup(self)
return self._function_lookup
@property @property
def external_node_unique_ids(self): def external_node_unique_ids(self):
return [node.unique_id for node in self.nodes.values() if node.is_external_node] return [node.unique_id for node in self.nodes.values() if node.is_external_node]
@@ -1391,6 +1452,29 @@ class Manifest(MacroMethods, dbtClassMixin):
return Disabled(disabled[0]) return Disabled(disabled[0])
return None return None
def resolve_function(
self,
target_function_name: str,
target_function_package: Optional[str],
current_project: str,
node_package: str,
) -> MaybeFunctionNode:
package_candidates = _packages_to_search(
current_project, node_package, target_function_package
)
disabled: Optional[List[FunctionNode]] = None
for package in package_candidates:
function = self.function_lookup.find(target_function_name, package, self)
if function is not None and function.config.enabled:
return function
# it's possible that the function is disabled
if disabled is None:
disabled = self.disabled_lookup.find(target_function_name, package)
if disabled:
return Disabled(disabled[0])
return None
def resolve_metric( def resolve_metric(
self, self,
target_metric_name: str, target_metric_name: str,
@@ -1631,6 +1715,11 @@ class Manifest(MacroMethods, dbtClassMixin):
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_function(self, source_file: SourceFile, function: FunctionNode):
_check_duplicates(function, self.functions)
self.functions[function.unique_id] = function
source_file.functions.append(function.unique_id)
def add_metric( def add_metric(
self, source_file: SchemaSourceFile, metric: Metric, generated_from: Optional[str] = None self, source_file: SchemaSourceFile, metric: Metric, generated_from: Optional[str] = None
): ):
@@ -1667,6 +1756,8 @@ class Manifest(MacroMethods, dbtClassMixin):
source_file.semantic_models.append(node.unique_id) source_file.semantic_models.append(node.unique_id)
if isinstance(node, Exposure): if isinstance(node, Exposure):
source_file.exposures.append(node.unique_id) source_file.exposures.append(node.unique_id)
if isinstance(node, FunctionNode):
source_file.functions.append(node.unique_id)
if isinstance(node, UnitTestDefinition): if isinstance(node, UnitTestDefinition):
source_file.unit_tests.append(node.unique_id) source_file.unit_tests.append(node.unique_id)
elif isinstance(source_file, FixtureSourceFile): elif isinstance(source_file, FixtureSourceFile):
@@ -1733,6 +1824,7 @@ class Manifest(MacroMethods, dbtClassMixin):
self.macros, self.macros,
self.docs, self.docs,
self.exposures, self.exposures,
self.functions,
self.metrics, self.metrics,
self.groups, self.groups,
self.selectors, self.selectors,

View File

@@ -3,6 +3,7 @@ from typing import Any, Dict, List, Optional, Type
from dbt.artifacts.resources import ( from dbt.artifacts.resources import (
ExposureConfig, ExposureConfig,
FunctionConfig,
GroupConfig, GroupConfig,
MetricConfig, MetricConfig,
ModelConfig, ModelConfig,
@@ -52,6 +53,7 @@ RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
NodeType.Snapshot: SnapshotConfig, NodeType.Snapshot: SnapshotConfig,
NodeType.Unit: UnitTestConfig, NodeType.Unit: UnitTestConfig,
NodeType.Group: GroupConfig, NodeType.Group: GroupConfig,
NodeType.Function: FunctionConfig,
} }

Some files were not shown because too many files have changed in this diff Show More