Compare commits

...

255 Commits

Author SHA1 Message Date
Github Build Bot
c723cb1f7a Bumping version to 1.7.8 and generate changelog 2024-02-14 20:24:42 +00:00
Gerda Shank
6517c75e7e [Backport 1.7.latest] Store node_info in GenericExceptionOnRun logging event (#9568)
* Add node_info to GenericExceptionOnRun, InternalErrorOnRun &
SQLRunnerException

* Changie

* Formatting
2024-02-14 11:06:36 -05:00
Gerda Shank
f95cf55d33 [Backport 1.7.latest] Set query headers when manifest is passed in to dbtRunner (#9569) 2024-02-14 09:51:56 -05:00
github-actions[bot]
ad37fc9534 Preserve model constraints when versioning models (#9539) (#9541)
(cherry picked from commit 2b6e2e18df)

Co-authored-by: Gerda Shank <gerda@dbtlabs.com>
2024-02-12 10:17:18 -05:00
FishtownBuildBot
169972f480 [Automated] Merged prep-release/1.7.7_7745564565 into target 1.7.latest during release process 2024-02-01 12:38:17 -06:00
Github Build Bot
7b96509409 Bumping version to 1.7.7 and generate changelog 2024-02-01 18:00:42 +00:00
Michelle Ark
cfbba81938 TestGenerateCatalogWithExternalNodes (#9456) (#9483)
* TestGenerateCatalogWithExternalNodes (#9456)

(cherry picked from commit 06e55bb93e)

* Flip logic in `packages_for_node` to remove error case

By flipping the logic from `not in` to `in` we can drop the exception
and instead default to the model runtime config when the package isn't
found. We're still trying to grok if there will be any fallout from this.
The tests all pass, but that doesn't guarantee nothing bad will happen.

---------

Co-authored-by: Quigley Malcolm <quigley.malcolm@dbtlabs.com>
2024-01-31 13:32:50 -05:00
Michelle Ark
1391363d53 Fix source seed selection docs generate (#9454) (#9493)
(cherry picked from commit 719a50cc91)
2024-01-30 18:23:10 -05:00
github-actions[bot]
0d8e4af2f0 [Backport 1.7.latest] Pin pytest in dev-requirements.txt (#9475)
Co-authored-by: Kshitij Aranke <kshitij.aranke@dbtlabs.com>
2024-01-30 16:33:26 +00:00
FishtownBuildBot
e9ee761194 [Automated] Merged prep-release/1.7.6_7658416467 into target 1.7.latest during release process 2024-01-25 12:26:54 -06:00
Github Build Bot
5ec34cd4cc Bumping version to 1.7.6 and generate changelog 2024-01-25 17:48:30 +00:00
github-actions[bot]
bc494bbe80 [Backport 1.7.latest] fix retry as CLI (#9448)
(cherry picked from commit 50b85a0b01)

Co-authored-by: Chenyu Li <chenyu.li@dbtlabs.com>
2024-01-24 16:29:01 -08:00
github-actions[bot]
3f3dda48f7 Handle unknown type_code for model contracts (#8887) (#9409) 2024-01-19 15:08:10 -06:00
FishtownBuildBot
1f98991376 [Automated] Merged prep-release/1.7.5_7572664948 into target 1.7.latest during release process 2024-01-18 17:42:32 +01:00
Github Build Bot
5f5ddd27ac Bumping version to 1.7.5 and generate changelog 2024-01-18 16:04:29 +00:00
Chenyu Li
6e331833b0 Backport 9328 to 1.7.latest (#9391)
* Fix full-refresh and vars for retry (#9328)

Co-authored-by: Peter Allen Webb <peter.webb@dbtlabs.com>
(cherry picked from commit 1e4286a62d)

* pr feedback

* Update requires.py
2024-01-17 13:27:46 -08:00
FishtownBuildBot
d338b3e065 [Automated] Merged prep-release/1.7.4_7213813158 into target 1.7.latest during release process 2023-12-14 15:28:18 -05:00
Github Build Bot
9f0bcf5666 Bumping version to 1.7.4 and generate changelog 2023-12-14 19:50:11 +00:00
Quigley Malcolm
d65179b24e [Backport 1.7.latest] Update parser to support conversion metrics #9173 (#9255)
* Move minimum DSI version to 0.4.2

We're backporting a feature "conversion metrics" to 1.7. Conversion
metrics don't exist in DSI < 0.4.2 which is problematic if we allow
for those versions. This ensures that those who are on a version of
1.7 that supports conversion metrics will also have the requisit version
of DSI.

* added ConversionTypeParams classes

* updated parser for ConversionTypeParams

* added step to populate input_measure for conversion metrics

* added tests

* added changelog

* Regenerate v11 manifest jsonschema to include conversion metrics definition

* Regenerate v11 manifest test artifact for testing version compatability

---------

Co-authored-by: Will Deng <william.deng14@gmail.com>
2023-12-11 10:37:06 -08:00
Quigley Malcolm
0ef8638ab4 [Backport 1.7.latest] Fix ensuring we produce valid jsonschema artifacts for manifest, catalog, sources, and run-results (#9229)
* Drop `all_refs=True` from jsonschema-ization build process

Passing `all_refs=True` makes it so that Everything is a ref, even
the top level schema. In jsonschema land, this essentially makes the
produced artifact not a full schema, but a fractal object to be included
in a schema. Thus when `$id` is passed in, jsonschema tools blow up
because `$id` is for identifying a schema, which we explicitly weren't
creating. The alternative was to drop the inclusion of `$id`. Howver, we're
intending to create a schema, and having an `$id` is recommended best
practice. Additionally since we were intending to create a schema,
not a fractal, it seemed best to create to full schema.

* Explicity produce jsonschemas using DRAFT_2020_12 dialect

Previously were were implicitly using the `DRAFT_2020_12` dialect through
mashumaro. It felt wise to begin explicitly specifying this. First, it
is closest in available mashumaro provided dialects to what we produced
pre 1.7. Secondly, if mashumaro changes its default for whatever reason
(say a new dialect is added, and mashumaro moves to that), we don't want
to automatically inherit that.

* Begin including schema dialect specification in produced jsonschema

In jsonschema's documentation they state
> It's not always easy to tell which draft a JSON Schema is using.
> You can use the $schema keyword to declare which version of the JSON Schema specification the schema is written to.
> It's generally good practice to include it, though it is not required.

and

> For brevity, the $schema keyword isn't included in most of the examples in this book, but it should always be used in the real world.

Basically, to know how to parse a schema, it's important to include what
schema dialect is being used for the schema specification. The change in
this commit ensures we include that information.

* Add change documentation for jsonschema schema production fix

* Regenerate dbt jsonschemas with fixed mashumaro jsonschema production process

Specifically we regenerated
* catalog v1
* manifest v11
* run-results v5
* sources v3
using the command `scripts/collect-artifact-schema.py --path schemas`
2023-12-07 12:28:34 -08:00
FishtownBuildBot
fc2d16fd34 [Automated] Merged prep-release/1.7.3_7034454600 into target 1.7.latest during release process 2023-11-29 10:42:33 -05:00
Github Build Bot
de35686532 Bumping version to 1.7.3 and generate changelog 2023-11-29 15:04:49 +00:00
github-actions[bot]
cf3714d2c1 [Backport 1.7.latest] Fix #9119: Get sources working again in dbt docs generate (#9163)
Co-authored-by: Kshitij Aranke <kshitij.aranke@dbtlabs.com>
2023-11-29 14:45:40 +00:00
Jeremy Cohen
09f5bb3dcf Backport #9147 to 1.7.latest (#9156)
* Fixups for deps lock file (#9147)

* Update git revision with commit SHA

* Use PackageRenderer for lock file

* add unit tests for git and tarball packages

* deepcopy unrendered_packages_data before iteration, fix remaining tests

* Add functional tests

* Add changelog entries

* Assert one more

---------

Co-authored-by: Michelle Ark <michelle.ark@dbtlabs.com>

* Restore warning on unpinned git packages

---------

Co-authored-by: Michelle Ark <michelle.ark@dbtlabs.com>
2023-11-29 08:40:54 +01:00
FishtownBuildBot
f94bf2bba4 [Automated] Merged prep-release/1.7.2_6894976289 into target 1.7.latest during release process 2023-11-16 14:11:56 -05:00
Github Build Bot
ae75cc3a62 Bumping version to 1.7.2 and generate changelog 2023-11-16 18:34:17 +00:00
Emily Rockman
1e45a751a5 roll back agate pin (#9106) 2023-11-16 12:29:54 -06:00
github-actions[bot]
b5885da54e [Backport 1.7.latest] [IDE] also treat SystemExit exception as an interrupt. (#9042)
* During node execution, also treat SystemExit as an interrupt. (#8994)

IDE worker process raises SystemExit in multiple scenarios, including user abort of a command.

(cherry picked from commit 931b2dbe40)

* Add test asserting GraphRunnableTasks attempt to cancel connections on SystemExit (#9101)

* Add test asserting GraphRunnableTasks attempt to cancel connections on SystemExit

* Add test asserting GraphRunnableTasks attempt to cancel connections on KeyboardInterrupt

* Add test asserting GraphRunnableNode doesn't try to cancel connections on generic Exception

---------

Co-authored-by: Ben Mosher <ben.mosher@dbtlabs.com>
Co-authored-by: Quigley Malcolm <QMalcolm@users.noreply.github.com>
2023-11-16 08:02:08 -08:00
github-actions[bot]
73ebe53273 Support hierarchical config setting for SavedQueryExport configs (#9065) (#9074)
* Add test asserting `SavedQuery` configs can be set from `dbt_project.yml`

* Allow extraneous properties in Export configs

This brings the Export config object more in line with how other config
objects are specified in the unparsed definition. It allows for specifying
of extra configs, although they won't get propagate to the final config.

* Add `ExportConfig` options to `SavedQueryConfig` options

This allows for specifying `ExportConfig` options at the `SavedQueryConfig` level.
This also therefore allows these options to be specified in the dbt_project.yml
config. The plan in the follow up commit is to merge the `SavedQueryConfig` options
into all configs of `Exports` belonging to the saved query.

There are a couple caveots to call out:
1. We've used `schema` instead of `schema_name` on the `SavedQueryConfig` despite
it being called `schema_name` on the `ExportConfig`. This is because need `schema_name`
to be the name of the property on the `ExportConfig`, but `schema` is the user facing
specification.
2. We didn't add the `ExportConfig` `alias` property to the `SavedQueryConfig` This
is because `alias` will always be specific to a single export, and thus it doesn't
make sense to allow defining it on the `SavedQueryConfig` to then apply to all
`Exports` belonging to the `SavedQuery`

* Begin inheriting configs from saved query config, and transitively from project config

Export configs will now inherit from saved query configs, with a preference
for export config specifications. That is to say an export config will inherity
a config attr from the saved query config only if a value hasn't been supplied
on the export config directly. Additionally because the saved query config has
a similar relationship with the project config, exports configs can inherit
from the project config (again with a preference for export config specifications).

* Correct conditional in export config building for map schema to schema_name

I somehow wrote a really weird, but also valid, conditional statement. Previously
the conditional was
```
if combined.get("schema") is not combined.get("schema_name") is None:
```
which basically checked whether `schema` was a boolean that didn't match
the boolean of whether `schema_name` was None. This would pretty much
always evaluate to True because `schema` should be a string or none, not
a bool, and thus would never match the right hand side. Crazy. It has now
been fixed to do the thing we want to it to do. If `schema` isn't `None`,
and `schema_name` is `None`, then set `schema_name` to have the value of
`schema`.

* Update parameter names in `_get_export_config` to be more verbose

(cherry picked from commit c2f7d75e9e)

Co-authored-by: Quigley Malcolm <QMalcolm@users.noreply.github.com>
2023-11-15 15:34:36 -08:00
github-actions[bot]
2a9c3689a4 test pinning ddtrace (#9090) (#9092)
(cherry picked from commit 3902137dfc)

Co-authored-by: Michelle Ark <MichelleArk@users.noreply.github.com>
2023-11-15 16:49:14 -06:00
github-actions[bot]
67d8ce398f [Backport 1.7.latest] Fix dbt deps failing on tarballs (#9075) 2023-11-15 12:07:13 -08:00
FishtownBuildBot
7eb6cdbbfb [Automated] Merged prep-release/1.7.1_6789802369 into target 1.7.latest during release process 2023-11-07 15:52:03 -05:00
Github Build Bot
6ba3dc211c Bumping version to 1.7.1 and generate changelog 2023-11-07 20:15:09 +00:00
github-actions[bot]
ce86238389 Support new agate Integer data_type in adapter code (#9004) (#9027) 2023-11-07 14:56:48 -05:00
github-actions[bot]
7d60d6361e Fix lock for git subduer (#9019) (#9028)
* wip

* add tests

* changelog

* nits

* pr feedback

* nits

(cherry picked from commit 01d481bc8d)

Co-authored-by: Chenyu Li <chenyu.li@dbtlabs.com>
2023-11-07 14:30:50 -05:00
github-actions[bot]
bebd6ca0e1 Use MANIFEST.in to identify package data, allows recursive include (#9021) (#9026)
* changelog

* use MANIFEST.in to identify package data

(cherry picked from commit 839c720e91)

Co-authored-by: Mike Alfare <13974384+mikealfare@users.noreply.github.com>
2023-11-07 14:05:43 -05:00
Peter Webb
66cb5a0e7c Fix back compat for run_results pre-v5 (#9009) (#9017)
* Fix back compat for run_results pre-v5

* Add type annotations

* Add functional testing

* Add inline annotations

* Add changelog entry.

* Consolidate upgrade_schema_version + upgrade_run_results_json

* Restore accidentally reverted test cases

* Pre-commit fixups

---------

Co-authored-by: Jeremy Cohen <jeremy@dbtlabs.com>
2023-11-06 19:26:53 -05:00
FishtownBuildBot
2401600e57 [Automated] Merged prep-release/1.7.0_6734243388 into target 1.7.latest during release process 2023-11-02 11:43:59 -04:00
Github Build Bot
a8fcf77831 Bumping version to 1.7.0 and generate changelog 2023-11-02 14:53:42 +00:00
Peter Webb
0995825793 Make relation filtering None-tolerant for maximal flexibility across adapters. (#8975) (#8978) 2023-11-01 18:14:23 -04:00
Quigley Malcolm
eeaaec4de9 DSI 0.4.0 and Saved Query Exports (#8950) (#8973) 2023-11-01 12:04:34 -07:00
github-actions[bot]
1cbab8079a ADAP-974: Fix issue where materialized views were not showing up in catalog queries (#8945) (#8963)
* changelog
* write test case demonstrating the issue
* update catalog query to reflect materialized views

(cherry picked from commit bb21403c9e)

Co-authored-by: Mike Alfare <13974384+mikealfare@users.noreply.github.com>
2023-11-01 14:29:53 -04:00
github-actions[bot]
333d793cf0 add a no-op runner for saved_query (#8937) (#8958)
(cherry picked from commit 211392c4a4)

Co-authored-by: Chenyu Li <chenyu.li@dbtlabs.com>
2023-11-01 09:06:07 -07:00
Peter Webb
b6f0eac2cd Backport Catalog Fix to 1.7.latest (#8953)
* Fix issues around new get_catalog_by_relations macro (#8856)

* Fix issues around new get_catalog_by_relations macro

* Add changelog entry

* Fix unit test.

* Additional unit testing

* Fix cased comparison in catalog-retrieval function (#8940)

* Fix cased comparison in catalog-retrieval function.

* Fix cased comparison in catalog-retrieval function.
2023-10-31 16:24:27 -04:00
github-actions[bot]
5e13698b2b [Backport 1.7.latest] Fix #8836: Add version to fqn when version==0 (#8922)
Co-authored-by: Kshitij Aranke <kshitij.aranke@dbtlabs.com>
2023-10-26 13:38:44 -05:00
github-actions[bot]
12c1cbbc64 Contract enforcement on temporary tables (#8889) (#8902)
* add test

* fix test

* first pass with constraint error

* add back column checks for temp tables

* changelog

* Update .changes/unreleased/Fixes-20231024-145504.yaml

(cherry picked from commit 98310b6612)

Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
2023-10-26 09:26:57 -05:00
github-actions[bot]
ced70d55c3 Fix partial parsing issue not working for changing semantic model name (#8865) (#8878)
* fix

* test

* changelog

(cherry picked from commit 35f46dac8c)

Co-authored-by: Chenyu Li <chenyu.li@dbtlabs.com>
2023-10-25 11:23:48 -07:00
FishtownBuildBot
1baebb423c [Automated] Merged prep-release/1.7.0rc1_6495873283 into target main during release process 2023-10-12 09:50:47 -04:00
Github Build Bot
462df8395e Bumping version to 1.7.0rc1 and generate changelog 2023-10-12 12:53:26 +00:00
Quigley Malcolm
35f214d9db Support dbt-semantic-interfaces 0.3.0 (#8820)
* changie doc for DSI 0.3.0 upgrade

* Gracefully handle v10 metric filters

* Fix iteration over metrics in `upgrade_v10_metric_filters`

* Update previous manifest version test fixtures to have more expressive metrics

* Regenerate the test v10 manifest artifact using the more expressive metrics from 904cc1ef

To do this I cherry-picked 904cc1ef onto my local 1.6.latest branch,
had the test regenerate the test v10 manifest artifact, and then over
wrote the test v10 manifest artifact on this branch (cherry-picking it
across the branches didn't work, had to copy paste :grimmace:)

* Regenerate test v11 manifest artifact using the fixture changes in 904cc1ef

* Update `upgrade_v10_metric_filters` to handled disabled metrics

Regenerating the v10 and v11 test manifest artifacts uncovered an
issue wherein we weren't handling disabled metrics that need to
get upgraded. This commit fixes that. Additionally, the
`upgrade_v10_metric_filters` was getting a bit unwieldy, so I broke
extracted the abstracted sub functions.

* Fix `test_backwards_compatible_versions` test

When we regenerated the v10 test manifest artifact, it started having
the `metricflow_time_sine` model, and it didn't previously. This caused
`test_backwards_compatible_versions` to start failing because it was
no longer identified as having modified state for v10. The test has
been altered accordingly
2023-10-11 16:46:26 -07:00
Quigley Malcolm
af0cbcb6a5 Add SavedQuery nodes (#8798)
* Bump to dbt-semantic-interfaces 0.3.0b1

* Update import path of `WhereFilterParser` from `dbt-semantic-interfaces`

In 0.3.x of `dbt-semantic-intefaces` the location of the WhereFilterParser
moved to be grouped in with a bunch of new adjacent code. As such,
we needed to correct our import path of it.

* Create basic `SavedQuery` node type based on `SavedQuery` protocol from DSI

* Add ability to add SavedQueries to the manifest

* Define unparsed SavedQuery node

* Begin parsing saved_query objects to manifest

* Skip jinja rendering of `SavedQuery.where` property

* Begin propagating `SavedQueries` on the manifest to the semantic manifest

* Add tests for basic saved query parsing

* Add custom pluralization handling of SavedQuery node type

* Add a config subclass to SavedQuery node

* Move the SavedQuery node to nodes.py

Unfortunately things are a bit too intertwined currently for SavedQuery
to be in it's own file. We need to add the SavedQuery node to the
GraphMemberNode, unfortunately with SavedQuery in it's own file,
importing it would have caused a circular dependency. We'll need
to separately come in and split things up as a cleanup portion of
work.

* Add basic plumbing of saved query configs to projects

* Add basic lookup utility for saved queries, SavedQueryLookup

* Handle disabled SavedQuery nodes in parsing and lookups

* Add SavedQuery nodes to grouping process

Our grouping logic seems to be in a weird spot. It seems liek we're
moving to setting the `group` for a node in the node's `config` however,
all of the logic around grouping is still focused on the top level `group`
property on a nodes. To get group stuff plumbed I've thus added `group`
as a top level property of the `SavedQuery` node, and populated it from
the config group value.

* Plumb through saved query in a lot more places

I don't like making scatter shot commits like this. However, a lot
of this commit was written ~4am, soooo yea. Things were broken, I wanted
things to be unbroken. I mostly searched for `semantic_models` and added
the equivalent necessary `saved_queries`. Some stuff is in support of
writing out the manifest, some stuff helps with node selection, it's a
lot of miscelaneous stuff that I don't fully understand.

* Add `depends_on` to `SavedQuery` nodes and populate from `metrics` property

* Add partial parsing support to SavedQuery nodes

* Add `docs` support for SavedQuery descriptions

* Support selctor methods for SavedQuery nodes

* Add `refs` property to SavedQuery node

We don't actually append anything to `refs` for SavedQuery nodes currently.
I'm not sure if anything needs to be appended to them. Regardless, we
access the `refs` property throughout the codebase while iterating over
nodes. It seems wise to support this attribute as to not accidently blow
something up with it not existing.

* Support `saved_queries` when upgrading from manifests <= v10 (and regenerate v11)

* Add changie doc for saved query node support

* Pin to dbt-semantic-interfaces 0.3.0b1 for saved query work

We're gonna release DSI 0.3.0, and if this PR automatically pulls that
in things will break. But the things that need fixing should be handled
separately from this PR. After releasing DSI 0.3.0 I'm going to create
a branch off/ontop of this one, and open a stacked PR with the associated
changes.

* Bump supported DSI version to 0.3.x

* Switch metric filters and saved query where to use ne WhereFilterIntersection

* Update schema yaml readers to create WhereFilterInterfaces

* Expand metric filters and saved query where property to handle both str and list of strs

* Update tests which were broken by where filter changes

* Regeneate v11 manifest

* Fixup: Update `SavedQueryLookup.perform_lookup` to operate on saved queries

I missed this when I was copy and pasting 🤦
2023-10-11 15:54:11 -07:00
Peter Webb
2e35426d11 Add support for getting freshness from DBMS metadata (#8795)
* Add support for getting freshness from DBMS metadata

* Add changelog entry

* Add simple test case

* Change parsing error to warning and add new event type for warning

* Code review simplification of capability dict.

* Revisions to the capability mechanism per review

* Move utility function.

* Reduce try/except scope

* Clean up imports.

* Simplify typing per review

* Unit test fix
2023-10-11 15:55:57 -04:00
Emily Rockman
bf10a29f06 update v10 manifest on main (#8834)
* update manifest

* add changelog
2023-10-11 14:52:01 -05:00
Gerda Shank
a7e2d9bc40 Partial parsing issue when adding groups and updating models at the same time (#8817) 2023-10-11 15:01:49 -04:00
Michelle Ark
a3777496b5 fix changelog (#8833) 2023-10-11 13:22:05 -04:00
dependabot[bot]
edf6aedc51 Bump docker/build-push-action from 4 to 5 (#8783)
* Bump docker/build-push-action from 4 to 5

Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  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>
2023-10-11 09:51:51 -07:00
Jeremy Cohen
53845d0277 Add warning_tag to UnversionedBreakingChange (#8828) 2023-10-11 17:54:16 +02:00
Mike Alfare
3d27483658 ADAP-850: Support test results as a view (#8653)
* add `store_failures_as` parameter to TestConfig, catch strategy parameter in test materialization
* create test results as views
* updated test expected values for new config option
* break up tests into reusable tests and adapter specific configuration, update test to check for relation type and confirm views update
* move test configuration into base test class
* allow `store_failures_as` to drive whether failures are stored
* update expected test config dicts to include the new default value for store_failures_as
* Add `store_failures_as` config for generic tests
* cover --store-failures on CLI gap
* add generic tests test case for store_failures_as
* update object names for generic test case tests for store_failures_as
* remove unique generic test, it was not testing `store_failures_as`
* pull generic run and assertion into base test class to turn tests into quasi-parameterized tests
* add ephemeral option for store_failures_as, as a way to easily turn off store_failures at the model level
* add compilation error for invalid setting of store_failures_as

---------

Co-authored-by: Doug Beatty <doug.beatty@dbtlabs.com>
2023-10-10 17:26:01 -04:00
Emily Rockman
4f9bd0cb38 Fix uncaught exception for group updates (#8792)
* add test

* write test

* fix test

* updating test

* add clean

* cleanup

* more tests, fix comment

* add new test, move fixtures
2023-10-10 15:37:47 -05:00
Kshitij Aranke
3f7f7de179 Fix #8682: Override path-like args in dbt retry (#8803) 2023-10-10 19:31:54 +01:00
Kshitij Aranke
6461f5aacf Fix #8022: Foreign key constraint on incremental model results in Database Error (#8768) 2023-10-10 18:32:51 +01:00
Doug Beatty
339957b42c Explanation of Parsing vs. Compilation vs. Runtime (#8744)
* Explanation of Parsing vs. Compilation vs. Runtime

* Update core/dbt/parser/parsing-vs-compilation-vs-runtime.md

* Update core/dbt/parser/parsing-vs-compilation-vs-runtime.md

* Update core/dbt/parser/parsing-vs-compilation-vs-runtime.md

* Update core/dbt/parser/parsing-vs-compilation-vs-runtime.md

* Update core/dbt/parser/parsing-vs-compilation-vs-runtime.md

* Update core/dbt/parser/parsing-vs-compilation-vs-runtime.md

* Apply suggestions from code review

Co-authored-by: Jeremy Cohen <jeremy@dbtlabs.com>

* Fix a couple markdown rendering issues

* Move to the "explain it like im 64" folder

When ELI5 just isnt detailed enough.

* Disambiguate Python references

Disambiguate Python references and delineate SQL models ("Jinja-SQL") from Python models ("dbt-py")

---------

Co-authored-by: Jeremy Cohen <jeremy@dbtlabs.com>
2023-10-10 10:51:54 -06:00
Gerda Shank
4391dc1a63 Type aliasing for model contract column data_type (#8592) 2023-10-10 11:43:26 -04:00
Michelle Ark
964e0e4e8a [Fix] respect project root when loading seeds (#8762) 2023-10-10 11:17:41 -04:00
Chenyu Li
549dbf3390 Deps lock by justbldwn (#8408)
*  adding installed_packages.json functionality

*  update test_simple_dependency_deps test

* 📝 adding changelog for deps feature via changie

*  restructure deps command, include lock/add

*  add new deps event types to sample_values

*  fix test_simple_dependency_deps test

* 🐛 attempting to fix cli commands

* 🐛 convert dbt deps to dbt deps install

also leave dbt deps as just a new click group

*  update test_command_mutually_exclusive_option

change deps command to deps install

*  update functional tests from deps > deps install

*  change missing deps to deps install

*  convert adapter tests to deps install from deps

* move back to deps and merge more with main

* fix-unittest

* add hash

* foramt yml and update command structure

* nits

* add new param

* nits

* nits

* nits

* fix_tests

* pr_feedback

* nits

* nits

* move_check

* Update Features-20230125-165933.yaml

---------

Co-authored-by: Justin Baldwin <91483530+justbldwn@users.noreply.github.com>
2023-10-09 21:05:00 -07:00
Quigley Malcolm
70b2e15a25 Add semantic model test to test_contracts_graph_parsed.py (#8654)
* Add semantic model test to `test_contracts_graph_parsed.py`

The tests in `test_contracts_graph_parsed.py` are meant to ensure
that we can go from objects to dictionaries and back without any
changes. We've had a desire to simplify these tests. Most tests in
this file have three to four fixtures, this test only has one. What
a test of this format ensures is that parsing a SemanticModel from
a dictionary doesn't add/drop any keys from the dictionary and that
when going back to the dictionary no keys are dropped. This style of
test will still break whenever the semantic model (or sub objects)
change. However now when that happens, only one fixture will have to
be updated (whereas previously we had to update 3-4 fixtures).

* Begin using hypothesis package for symmetry testing

Hypothesis is a python package for doing property testing. The `@given`
parameterizes a test, with it generating the arguements it has following
`strategies`. The main strategies we use is `builds` this takes in a callable
passes any sub strategies for named arguements, and will try to infer any
other arguments if the callable is typed. I found that even though the
test was run many many times, some of the `SemanticModel` properties
weren't being changed. For instance `dimensions`, `entities`, and `measures`
were always empty lists. Because of this I defined sub strategies for
some attributes of `SemanticModel`s.

* Update unittest readme to have details on test_contracts_graph_parsed methodology
2023-10-09 14:55:26 -07:00
Mark Scannell
bb249d612c Generate static index html documentation (#8615)
* Include option to generate static index.html

* Added changie

* Using DBT's system load / write file methods for better cross platform
support

* Updated docs tests with dbt.client.systems calls for file reading

* Writing out static_index.html as binary file to prevent line-ending
conversions on Windows. (similar behaviour as index.html)
2023-10-06 10:37:13 -07:00
Emily Rockman
17773bdb94 pin types-requests in dev-requirements (#8788)
* pin types-requests

* changelog
2023-10-06 09:53:08 -05:00
Gerda Shank
f30293359c Selectors in docs generate limits catalog generation (#8772) 2023-10-05 16:00:12 -04:00
Emily Rockman
0c85e6149f remove guild as codeowner (#8778) 2023-10-05 10:16:32 -05:00
Emily Rockman
ec57d7af94 add gha for dependabot checks (#8777) 2023-10-05 10:16:20 -05:00
Emily Rockman
df791f729c support doc blocks (#8771) 2023-10-05 08:12:28 -05:00
Emily Rockman
c6ff3abecd remove top level meta attribute (#8766) 2023-10-04 13:23:09 -05:00
Emily Rockman
eac13e3bd3 Add meta to SemanticModels (#8754)
* WIP

* changelog
2023-10-03 13:08:37 -05:00
Emily Rockman
46ee3f3d9c rebuild manifest missed fields (#8755)
* rebuild manifest missed fields

* changelogs
2023-10-02 09:38:50 -05:00
Peter Webb
5e1f0c5fbc Report Resource Usage Statistics When a dbt Command Finishes (#8671)
* Add performance metrics to the CommandCompleted event.

* Add changelog entry.

* Add flag for controling the log level of ResourceReport.

* Update changelog entry to reflect changes

* Remove outdated attributes

* Work around missing resource module on windows

* Fix corner case where flags are not set
2023-09-29 18:37:37 -04:00
Peter Webb
c4f09b160a Add new get_catalog_relations macro, Supporting Changes (#8648)
* Add new get_catalog_relations macro, allowing dbt to specify which relations in a schema the adapter should return data about

* Implement postgres adapter support for relation filtering on catalog queries

* Code review changes adding feature flag for catalog-by-relation-list support

* Use profile specified in --profile with dbt init (#7450)

* Use profile specified in --profile with dbt init

* Update .changes/unreleased/Fixes-20230424-161642.yaml

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>

* Refactor run() method into functions, replace exit() calls with exceptions

* Update help text for profile option

---------

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>

* add TestLargeEphemeralCompilation (#8376)

* Fix a couple of issues in the postgres implementation of get_catalog_relations

* Add relation count limit at which to fall back to batch retrieval

* Better feature detection mechanism for adapters.

* Code review changes to get_catalog_relations and adapter feature checking

* Add changelog entry

---------

Co-authored-by: ezraerb <ezraerb@alum.mit.edu>
Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>
Co-authored-by: Michelle Ark <MichelleArk@users.noreply.github.com>
2023-09-29 16:13:23 -04:00
Jeremy Cohen
48c97e86dd Fix tag: selection for projects with semantic models (#8750)
* Add unit test to repro regression

* Add defensive code for tag: selection

* Add changelog entry
2023-09-29 09:49:32 -07:00
Emily Rockman
416bc845ad fix duplication of docs issues (#8747)
* fix duplication of docs issues

* update conditional to only run on merged PRs
2023-09-29 09:40:34 -05:00
Michelle Ark
408a78985a Fix: avoid double-rendering sql_header in dbt show (#8740) 2023-09-28 19:35:14 +01:00
Emily Rockman
0c965c8115 update changelog kind (#8737) 2023-09-28 08:20:57 -07:00
Philippe Boyd
f65e4b6940 feat: resolve packages with same git repo and unique subdirectory (#8322) 2023-09-27 15:31:37 -04:00
Michelle Ark
a2d4424f92 Inline limit in SQL sent from dbt show (#8641) 2023-09-27 15:56:34 +01:00
Kshitij Aranke
997f839cd6 Disallow cleaning paths outside current working directory (#8469) 2023-09-27 15:47:00 +01:00
Emily Rockman
556fad50df Model contracts: raise warning for numeric types without specified scale (#8721)
* add warning when contracting fields don't have precision

* rename files

* changelog

* move tests out of adapter zone

* Update core/dbt/include/global_project/macros/adapters/columns.sql

Co-authored-by: colin-rogers-dbt <111200756+colin-rogers-dbt@users.noreply.github.com>

* Apply suggestions from code review

---------

Co-authored-by: colin-rogers-dbt <111200756+colin-rogers-dbt@users.noreply.github.com>
2023-09-27 08:00:12 -05:00
dave-connors-3
bb4214b5c2 Dc/8546 semantic models in graph selection (#8589) 2023-09-26 23:23:03 +01:00
Kshitij Aranke
f17c1f3fe7 Fix #6497: Support global flags passed in after subcommands (#8670) 2023-09-26 16:16:36 +01:00
Kshitij Aranke
d4fe9a8ad4 Fix #8509: Support doc blocks in nested semantic model YAML (#8709) 2023-09-26 16:02:21 +01:00
Emily Rockman
2910aa29e4 Automated Repo Cleanup (#8686)
* add workflow

* cleanup and rename workflow

* rename workflow in actions

* add more context
2023-09-26 09:35:29 -05:00
Renan Leme
89cc073ea8 CT-3144 Fix test edges type filter on Graph (#8696)
* CT-3144 Fix test edges filter

* CT-3144 Add changelog

* CT-3144 Remove duplicated line

* CT-3144 Remove duplicated line

* CT-3144 Rename vars

* CT-3144 Update filter to use get_edge_data

* Trigger cla
2023-09-26 09:55:45 -04:00
Quigley Malcolm
aa86fdfe71 Add date spine macros to core (#8616)
* Add `date_spine` macro (and macros it depends on) from dbt-utils to core

The macros added are
- date_spine
- get_intervals_between
- generate_series
- get_powers_of_two

We're adding these to core because they are becoming more prevalently used
with the increase usage in the semantic layer. Basically if you are
using the semantic layer currently, then it is almost a requirement
to use dbt-utils, which is undesireable given the SL is supported directly
in core. The primary focus of this was to just add `date_spine`. However,
because `date_spine` depends on other macros, these other macros were
also moved.

* Add adapter tests for `get_powers_of_two` macro

* Add adapter tests for `generate_series` macro

* Add adapter tests for `get_intervals_between` macro

* Add adapter tests for `date_spine` macro

* Improve test fixture for `date_spine` macro to work with multiple adapters

* Cast to types to date in fixture_date_spine when targeting redshift

* Improve test fixture for `get_intervals_between` macro to work with multiple adapters

* changie doc for adding date_spine macro
2023-09-25 12:20:05 -07:00
Quigley Malcolm
48e9ced781 Support null coalescing properties for metric nodes (#8700)
* Include 'join_to_timespine` and `fill_nulls_with` in metric fixture

* Support `join_to_timespine` and `fill_nulls_with` properties on measure inputs to metrics

* Assert new `fill_nulls_with` and `join_to_timespine` properties don't break associated DSI protocol

* Add doc for metric null coalescing improvements

* Fix unit test for unparsed metric objects

The `assert_symmetric` function asserts that dictionaries are mostly
equivalent. I say mostly equivalent because it drops keys that are
`None`. The issue is that that `join_to_timespine` gets defaulted
to `False`, so we have to specify it in the `get_ok_dict` so that
they match.
2023-09-25 11:02:47 -07:00
Doug Beatty
7b02bd1f02 Lower bound of 8.0.2 for click (#8684)
* Lower bound of `8.0.2` for `click`

* Changelog entry
2023-09-24 13:46:54 -06:00
Emily Rockman
417fc2a735 Support quoted parameter list for MultiOption cli options (#8665)
* allow multioption to be quoted

* changelog

* fix test

* remove list format

* fix tests

* fix list object

* review arg change

* fix quotes

* Update .changes/unreleased/Features-20230918-150855.yaml

* add types

* convert list to set in test

* make mypy happy

* mroe mypy happiness

* more mypy happiness

* last mypy change

* add node to test
2023-09-22 14:34:36 -05:00
Quigley Malcolm
317128f790 Update pull request template to include type annotations as part of checklist (#8687)
* Update pull request template to include type annotations as part of checklist

* Add to checklist item a link to python typing information
2023-09-22 11:30:28 -07:00
Emily Rockman
e3dfb09b10 Support labels for semantic_models, dimensions, measures and entities (#8646)
* first pass

* changelog

* changelog

* Delete .changes/unreleased/Features-20230913-155802.yaml

* Update .changes/unreleased/Features-20230914-074429.yaml
2023-09-22 10:56:57 -05:00
colin-rogers-dbt
d912654110 Allow adapters to include python package logging in dbt logs (#8643)
* add set_package_log_level functionality

* set package handler

* set package handler

* add logging about stting up logging

* test event log handler

* add event log handler

* add event log level

* rename package and add unit tests

* revert logfile config change

* cleanup and add code comments

* add changie

* swap function for dict

* add additional unit tests

* fix unit test
2023-09-20 09:27:30 -07:00
Peter Webb
34ab4cf9be More Type Annotations (#8536)
* Extend use of type annotations in the events module.

* Add return type of None to more __init__ definitions.

* Still more type annotations adding -> None to __init__

* Tweak per review
2023-09-20 11:35:22 -04:00
Michelle Ark
d597b80486 add TestLargeEphemeralCompilation (#8376) 2023-09-18 15:00:10 +01:00
ezraerb
3f5ebe81b9 Use profile specified in --profile with dbt init (#7450)
* Use profile specified in --profile with dbt init

* Update .changes/unreleased/Fixes-20230424-161642.yaml

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>

* Refactor run() method into functions, replace exit() calls with exceptions

* Update help text for profile option

---------

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>
2023-09-15 10:14:53 -05:00
Kshitij Aranke
f52bd9287b Fix #8160: Warn when --state == --target (#8638) 2023-09-14 17:04:32 +01:00
Gerda Shank
f5baeeea1c Allow setting access in config in addition to properties (#8635) 2023-09-14 11:09:41 -04:00
Mike Alfare
3cc7044fb3 Loosen type on replaceable_relation and renameable_relation and provide guidance in docstrings (#8647)
* loosen type on replaceable_relation and renameable_relation and provide guidance in docstrings
2023-09-13 20:17:33 -04:00
Gerda Shank
26c7675c28 Fix test_numeric_values of the show test (#8644) 2023-09-13 15:38:18 -04:00
colin-rogers-dbt
8aaed0e29f replace is_replaceable function with can_be_replaced call (#8637) 2023-09-13 10:19:09 -04:00
Emily Rockman
5182e3c40c split up test class (#8610) 2023-09-12 10:59:56 -05:00
Kshitij Aranke
1e252c7664 move codecov failure threshold to 0.1% (#8625) 2023-09-12 16:45:36 +01:00
Kshitij Aranke
05ef3b6e44 Audit potential circular dependencies (#8489) 2023-09-12 14:00:16 +01:00
leahwicz
ad04012b63 Revert "Merge branch 'main' into main" (#8622)
This reverts commit c93cba4603, reversing
changes made to 971669016f.
2023-09-11 22:18:12 -04:00
leahwicz
c93cba4603 Merge branch 'main' into main 2023-09-11 21:40:10 -04:00
Mike Alfare
971669016f ADAP-869: Support atomic replace in replace macro (#8539)
* move config changes into alter.sql in alignment with other adapters
* move shared relations macros to relations root
* move single models files to models root
* add table to replace
* move create file into relation directory
* implement replace for postgres
* move column specific macros into column directory
* add unit test for can_be_replaced
* update renameable_relations and replaceable_relations to frozensets to set defaults
* fixed tests for new defaults
2023-09-11 16:23:25 -04:00
Emily Rockman
6c6f245914 update PR template (#8613) 2023-09-11 13:48:25 -05:00
Quigley Malcolm
b39eeb328c Unskip and rename test_expression_metric (#8578)
* Add docstrings to `contracts/graph/metrics.py` functions to document what they do

Used [dbt-labs/dbt-core#5607](https://github.com/dbt-labs/dbt-core/pull/5607)
for context on what the functions should do.

* Add typing to `reverse_dag_parsing` and update function to work on 1.6+ metrics

* Add typing to `parent_metrics` and `parent_metrics_names`

* Add typing to `base_metric_dependency` and `derived_metric_dependency` and update functions to work on 1.6+ metrics

* Simplify implementations of `basic_metric_dependency` and `derived_metric_dependnecy`

* Add typing to `ResolvedMetricReference` initialization

* Add typing to `derived_metric_dependency_graph`

* Simplify conditional controls in `ResolvedMetricReference` functions

The functions in `ResolvedMetricReference` use `manifest.metric.get(...)`
which will only return either a `Metric` or `None`, never a different
node type. Thus we don't need to check that the returned metric is
a metric.

* Don't recurse on over `depends_on` for non-derived metrics in `reverse_dag_parsing`

The function `reverse_dag_parsing` only cares about derived metrics,
that is metrics that depend on other metrics. Metrics only depend on
other metrics if they are one of the `DERIVED_METRICS` types. Thus
doing a recursive call to `reverse_dag_parsing` for non `DERIVED_METRICS`
types is unnecessary. Previously we were iterating over a metric's
`depends_on` property regardless of whether the metric was a `DERIVED_METRICS`
type. Now we only do this work if the metric is of a `DERIVED_METRICS`
type.

* Simplify `parent_metrics_names` by having it call `parent_metrics`

* Unskip `TestMetricHelperFunctions.test_derived_metric` and update fixture setup

* Add changie doc for metric helper function updates

* Get manifest in `test_derived_metric` from the parse dbt_run invocation

* Remove `Relation` a intiatlization attribute for `ResolvedMetricReference`

* Add return typing to class `__` functions of `ResolvedMetricReference`

* Move from `manifest.metrics.get` to `manifest.expect` in metric helpers

Previously with `manifest.metrics.get` we were just skipping when `None`
was returned. Getting `None` back was expected in that `parent_unique_id`s
that didn't belong to metrics should return `None` when calling
`manifest.metrics.get`, and these are fine to skip. However, there's
an edgecase where a `parent_unique_id` is supposed to be a metric, but
isn't found, thus returning `None`. How likely this edge case could
get hit, I'm not sure, but it's a possible edge case. Using `manifest.metrics.get`
it we can't actually tell if we're in the edge case or not. By moving
to `manifest.expect` we get the error handling built in, and the only
trade off is that we need to change our conditional to skip returned
nodes that aren't metrics.
2023-09-07 15:28:36 -07:00
Emily Rockman
be94bf1f3c Preserve decimal places for dbt show (#8561)
* update `Number` class to handle integer values (#8306)

* add show test for json data

* oh changie my changie

* revert unecessary cahnge to fixture

* keep decimal class for precision methods, but return __int__ value

* jerco updates

* update integer type

* update other tests

* Update .changes/unreleased/Fixes-20230803-093502.yaml

---------

Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>

* account for integer vs number on table merges

* add tests for combining number with integer.

* add unit test when nulls are added

* cant none as an Integer

* fix null tests

---------

Co-authored-by: dave-connors-3 <73915542+dave-connors-3@users.noreply.github.com>
Co-authored-by: Dave Connors <dave.connors@fishtownanalytics.com>
2023-09-07 09:15:11 -05:00
Ben Mosher
e24a952e98 compile --no-inject-ephemeral-ctes flag (#8482) 2023-09-07 10:13:40 -04:00
Michelle Ark
89f20d12cf make UnparsedVersion.__lt__ order-agnostic (#8559) 2023-09-06 17:46:59 -04:00
Michelle Ark
ebeb0f1154 test advanced ref override (#8552) 2023-09-06 17:45:16 -04:00
Kshitij Aranke
d66fe214d9 Fix #8544: Parse the correct schema version from manifest (#8551)
* Fix #8544: Parse the correct schema version from manifest

* add changie

* add comment
2023-09-06 20:38:37 +01:00
Michelle Ark
75781503b8 add typing to partially typed methods in runnable.py (#8569) 2023-09-06 13:57:55 -04:00
Kshitij Aranke
9aff3ca274 Fix #8398: Add typing to __init__ in base.py (#8568) 2023-09-06 18:13:05 +01:00
Emily Rockman
7e2a08f3a5 remove label (#8557) 2023-09-05 19:12:16 -05:00
Emily Rockman
a0e13561b1 Support dbt-cloud config dict in dbt_project.yml (#8527)
* first pass at adding dbt-cloud config

* changelog

* fix test, add direct validation
2023-09-05 09:48:37 -05:00
FishtownBuildBot
7eedfcd274 [Automated] Merged prep-release/1.7.0b2_6049623160 into target main during release process 2023-09-01 08:48:37 -05:00
Github Build Bot
da779ac77c Bumping version to 1.7.0b2 and generate changelog 2023-09-01 12:51:37 +00:00
Mike Alfare
adfa3226e3 ADAP-814: Add support for replacing materialized views with tables/views and vice versa (#8449)
* first draft of adding in table - materialized view swap
* table/view/materialized view can all replace each other
* update renameable relations to a config
* migrate relations macros from `macros/adapters/relations` to `macros/relations` so that generics are close to the relation specific macros that they reference; also aligns with adapter macro files structure, to look more familiar
* move drop macro to drop macro file
* align the behavior of get_drop_sql and drop_relation, adopt existing default from drop_relation
* add explicit ddl for drop statements instead of inheriting the default from dbt-core
* update replace macro dependent macros to align with naming standards
* update type for mashumaro, update related test
2023-08-31 20:41:45 -04:00
Quigley Malcolm
e5e1a272ff Fix untyped functions in core/dbt/context/base.py (#8525)
* Improve typing of `ContextMember` functions

* Improve typing of `Var` functions

* Improve typing of `ContextMeta.__new__`

* Improve typing `BaseContext` and functions

In addition to just adding parameter typing and return typing to
`BaseContext` functions. We also declared `_context_members_` and
`_context_attrs_` as properites of `BaseContext` this was necessary
because they're being accessed in the classes functions. However,
because they were being indirectly instantiated by the metaclass
`ContextMeta`, the properties weren't actually known to exist. By
adding declaring the properties on the `BaseContext`, we let mypy
know they exist.

* Remove bare `invocations` of `@contextmember` and `@contextproperty`, and add typing to them

Previously `contextmember` and `contextproperty` were 2-in-1 decorators.
This meant they could be invoked either as `@contextmember` or
`@contextmember('some_string')`. This was fine until we wanted to return
typing to the functions. In the instance where the bare decorator was used
(i.e. no `(...)` were present) an object was expected to be returned. However
in the instance where parameters were passed on the invocation, a callable
was expected to be returned. Putting a union of both in the return type
made the invocations complain about each others' return type. To get around this
we've dropped the bare invocation as acceptable. The parenthesis are now always
required, but passing a string in them is optional.
2023-08-31 13:34:57 -07:00
Gerda Shank
d8e8a78368 Fix snapshot success message to display "INSERT 0 1" (for example) instead of success (#8524) 2023-08-31 13:09:15 -04:00
Emily Rockman
7ae3de1fa0 Semantic model configs - enable/disable + groups (#8502)
* WIP

* WIP

* get group and enabled added

* changelog

* cleanup

* getting measure lookup working

* missed file

* get project level working

* fix last test

* add groups to config tests

* more group tests

* fix path

* clean up manifest.py

* update error message

* fix test assert

* remove extra check

* resolve conflicts in manaifest

* update manifest

* resolve conflict

* add alias
2023-08-30 20:18:22 -05:00
Gerda Shank
72898c7211 Add return value to AdapterContainer.__init__ and AdapterMeta.__new__ (#8523) 2023-08-30 17:37:32 -04:00
Peter Webb
fc1a14a0e3 Include Compiled Node Attributes in run_results.json (#8492)
* Add compiled node properties to run_results.json

* Include compiled-node attributes in run_results.json

* Fix typo

* Bump schema version of run_results

* Fix test assertions

* Update expected run_results to reflect new attributes

* Code review changes

* Fix mypy warnings for ManifestLoader.load() (#8443)

* revert python version for docker images (#8445)

* revert python version for docker images

* add comment to not update python version, update changelog

* Bumping version to 1.7.0b1 and generate changelog

* [CT-3013]  Fix parsing of `window_groupings` (#8454)

* Update semantic model parsing tests to check measure non_additive_dimension spec

* Make `window_groupings` default to empty list if not specified on `non_additive_dimension`

* Add changie doc for `window_groupings`  parsing fix

* update `Number` class to handle integer values (#8306)

* add show test for json data

* oh changie my changie

* revert unecessary cahnge to fixture

* keep decimal class for precision methods, but return __int__ value

* jerco updates

* update integer type

* update other tests

* Update .changes/unreleased/Fixes-20230803-093502.yaml

---------

Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>

* Improve docker image README (#8212)

* Improve docker image README

- Fix unnecessary/missing newline escapes
- Remove double whitespace between parameters
- 2-space indent for extra lines in image build commands

* Add changelog entry for #8212

* ADAP-814: Refactor prep for MV updates (#8459)

* apply reformatting changes only for #8449
* add logging back to get_create_materialized_view_as_sql
* changie

* swap trigger (#8463)

* update the implementation template (#8466)

* update the implementation template

* add colon

* Split tests into classes (#8474)

* add flaky decorator

* split up tests into classes

* revert update agate for int (#8478)

* updated typing and methods to meet mypy standards (#8485)

* Convert error to conditional warning for unversioned contracted model, fix msg format (#8451)

* first pass, tests need updates

* update proto defn

* fixing tests

* more test fixes

* finish fixing test file

* reformat the message

* formatting messages

* changelog

* add event to unit test

* feedback on message structure

* WIP

* fix up event to take in all fields

* fix test

* Fix ambiguous reference error for duplicate model names across packages with tests (#8488)

* Safely remove external nodes from manifest (#8495)

* [CT-2840] Improved semantic layer protocol satisfaction tests (#8456)

* Test `SemanticModel` satisfies protocol when none of it's `Optionals` are specified

* Add tests ensuring SourceFileMetadata and FileSlice satisfiy DSI protocols

* Add test asserting Defaults obj satisfies protocol

* Add test asserting SemanticModel with optionals specified satisfies protocol

* Split dimension protocol satisfaction tests into with and without optionals

* Simplify DSI Protocol import strategy in protocol satisfaction tests

* Add test asserting DimensionValidtyParams satisfies protocol

* Add test asserting DimensionTypeParams satisfies protocol

* Split entity protocol satisfaction tests into with and without optionals

* Split measure protocol satisfication tests and add measure aggregation params satisficaition test

* Split metric protocol satisfaction test into optional specified an unspecified

Additionally, create where_filter pytest fixture

* Improve protocol satisfaction tests for MetricTypeParams and sub protocols

Specifically we added/improved protocol satisfaction tests for
- MetricTypeParams
- MetricInput
- MetricInputMeasure
- MetricTimeWindow

* Convert to using mashumaro jsonschema with acceptable performance (#8437)

* Regenerate run_results schema after merging in changes from main.

---------

Co-authored-by: Gerda Shank <gerda@dbtlabs.com>
Co-authored-by: Matthew McKnight <91097623+McKnight-42@users.noreply.github.com>
Co-authored-by: Github Build Bot <buildbot@fishtownanalytics.com>
Co-authored-by: Quigley Malcolm <QMalcolm@users.noreply.github.com>
Co-authored-by: dave-connors-3 <73915542+dave-connors-3@users.noreply.github.com>
Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
Co-authored-by: Jaime Martínez Rincón <jaime@jamezrin.name>
Co-authored-by: Mike Alfare <13974384+mikealfare@users.noreply.github.com>
Co-authored-by: Michelle Ark <MichelleArk@users.noreply.github.com>
2023-08-30 17:28:49 -04:00
Gerda Shank
f063e4e01c Convert to using mashumaro jsonschema with acceptable performance (#8437) 2023-08-30 14:06:59 -04:00
Quigley Malcolm
07372db906 [CT-2840] Improved semantic layer protocol satisfaction tests (#8456)
* Test `SemanticModel` satisfies protocol when none of it's `Optionals` are specified

* Add tests ensuring SourceFileMetadata and FileSlice satisfiy DSI protocols

* Add test asserting Defaults obj satisfies protocol

* Add test asserting SemanticModel with optionals specified satisfies protocol

* Split dimension protocol satisfaction tests into with and without optionals

* Simplify DSI Protocol import strategy in protocol satisfaction tests

* Add test asserting DimensionValidtyParams satisfies protocol

* Add test asserting DimensionTypeParams satisfies protocol

* Split entity protocol satisfaction tests into with and without optionals

* Split measure protocol satisfication tests and add measure aggregation params satisficaition test

* Split metric protocol satisfaction test into optional specified an unspecified

Additionally, create where_filter pytest fixture

* Improve protocol satisfaction tests for MetricTypeParams and sub protocols

Specifically we added/improved protocol satisfaction tests for
- MetricTypeParams
- MetricInput
- MetricInputMeasure
- MetricTimeWindow
2023-08-29 09:40:30 -07:00
Michelle Ark
48d04e8141 Safely remove external nodes from manifest (#8495) 2023-08-28 14:10:34 -04:00
Michelle Ark
6234267242 Fix ambiguous reference error for duplicate model names across packages with tests (#8488) 2023-08-25 09:15:07 -04:00
Emily Rockman
1afbb87e99 Convert error to conditional warning for unversioned contracted model, fix msg format (#8451)
* first pass, tests need updates

* update proto defn

* fixing tests

* more test fixes

* finish fixing test file

* reformat the message

* formatting messages

* changelog

* add event to unit test

* feedback on message structure

* WIP

* fix up event to take in all fields

* fix test
2023-08-24 19:29:31 -05:00
Mike Alfare
d18a74ddb7 updated typing and methods to meet mypy standards (#8485) 2023-08-24 16:25:42 -04:00
Michelle Ark
4d3c6d9c7c revert update agate for int (#8478) 2023-08-23 14:06:52 -04:00
Emily Rockman
10f9724827 Split tests into classes (#8474)
* add flaky decorator

* split up tests into classes
2023-08-23 11:27:34 -05:00
Emily Rockman
582faa129e update the implementation template (#8466)
* update the implementation template

* add colon
2023-08-22 13:58:25 -05:00
Emily Rockman
4ec87a01e0 swap trigger (#8463) 2023-08-21 15:48:37 -05:00
Mike Alfare
ff98685dd6 ADAP-814: Refactor prep for MV updates (#8459)
* apply reformatting changes only for #8449
* add logging back to get_create_materialized_view_as_sql
* changie
2023-08-21 14:44:47 -04:00
Jaime Martínez Rincón
424f3d218a Improve docker image README (#8212)
* Improve docker image README

- Fix unnecessary/missing newline escapes
- Remove double whitespace between parameters
- 2-space indent for extra lines in image build commands

* Add changelog entry for #8212
2023-08-18 14:14:59 -05:00
dave-connors-3
661623f9f7 update Number class to handle integer values (#8306)
* add show test for json data

* oh changie my changie

* revert unecessary cahnge to fixture

* keep decimal class for precision methods, but return __int__ value

* jerco updates

* update integer type

* update other tests

* Update .changes/unreleased/Fixes-20230803-093502.yaml

---------

Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
2023-08-18 14:01:08 -05:00
Quigley Malcolm
49397b4d7b [CT-3013] Fix parsing of window_groupings (#8454)
* Update semantic model parsing tests to check measure non_additive_dimension spec

* Make `window_groupings` default to empty list if not specified on `non_additive_dimension`

* Add changie doc for `window_groupings`  parsing fix
2023-08-18 10:57:29 -07:00
FishtownBuildBot
0553fd817c [Automated] Merged prep-release/1.7.0b1_5895067219 into target main during release process 2023-08-17 15:04:21 -05:00
Github Build Bot
7ad971f720 Bumping version to 1.7.0b1 and generate changelog 2023-08-17 19:25:44 +00:00
Matthew McKnight
f485c13035 revert python version for docker images (#8445)
* revert python version for docker images

* add comment to not update python version, update changelog
2023-08-17 14:18:28 -05:00
Gerda Shank
c30b691164 Fix mypy warnings for ManifestLoader.load() (#8443) 2023-08-17 14:34:22 -04:00
Quigley Malcolm
d088d4493e Add doc string context to Identifier validion regex rule (#8440) 2023-08-17 10:11:03 -07:00
Emily Rockman
770f804325 Fix test failures (#8432)
* fail job when anything fails in previous matrix

* tweak wording

* PR feedback
2023-08-17 07:20:07 -05:00
Emily Rockman
37a29073de change trigger (#8418) 2023-08-16 19:49:59 -05:00
Peter Webb
17cd145f09 Temporarily disable test. (#8434) 2023-08-16 16:42:48 -04:00
Kshitij Aranke
ac539fd5cf Ignore .github and .changes directories for code coverage (#8424)
Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
2023-08-16 16:19:12 +01:00
Gerda Shank
048553ddc3 Fix using project-dir with list command and path selector (#8388) 2023-08-16 10:39:22 -04:00
Gerda Shank
dfe6b71fd9 Add return values to functions to fix mypy warnings (#8416) 2023-08-16 10:37:46 -04:00
Peter Webb
18ee93ca3a Fix run_results.json Performance Regression (#8413)
* Remedy performance regression by only writing run_results.json once.

* Write results before cleaning up connections.
2023-08-15 16:41:14 -04:00
Emily Rockman
cb4bc2d6e9 Automate opening docs issues (#8373)
* first pass

* WIP

* update issue body

* fix triggering label

* fix docs

* add better run name

* reduce complexity

* update description

* fix PR title

* point at workflow on main

* fix wording

* add label
2023-08-15 14:20:46 -05:00
Quigley Malcolm
b0451806ef [CT-2526] Add ability to automatically create metrics from semantic model measures (#8310)
* Update semantic model parsing test to check `create_metric = true` functionality

* Add `create_metric` boolean property to unparsed measure objects

* Begin creating metrics from measures with `create_metric = True`

* Add test ensuring partial parsing handles metrics generated from measures

* Ensure partial parsing appropriately deletes metrics generated from semantic models

* Add changie doc for  addition

* Separate generated metrics from parsed metrics for partial parsing

I was doing a demo earlier today of this branch (minus this commit)
and noticed something odd. When I changes a semantic model, metrics
that should have been technically uneffected would get dropped. Basically
if I made a change to a semantic model which had metrics in the same
file, and then ran parse, those metrics defined in the same file
would get dropped. Then with no other changes, if I ran parse again
they would come back. What was happening was that parsed metrics
and generated metrics were getting tracked the same way on the file
objects for partial parsing. In 0787a7c7b67b10a55b0d7727eeb744df831d503c
we began dropping all metrics tracked in a file objects when changes
to semantic models were detected. Since parsed metrics and generated
metrics were being tracked together on the file object, the parsed
metrics were getting dropped as well. In this commit we begin separating
out the tracking of generated metrics and parsed metrics on the
file object, and now only drop the generated metrics when semantic
models have a detected change.

* Assert in test that  semantic model partial parsing doesn't clobber regular metrics
2023-08-14 12:42:11 -07:00
Kshitij Aranke
b514e4c249 Fix #8350: add connection status into list of statuses for dbt debug (#8351) 2023-08-14 18:13:10 +01:00
Michelle Ark
8350dfead3 add --no-partial-parse-file-diff flag (#8361)
* add --no-partial-parse-file-diff flag

* changelog entry
2023-08-14 13:24:16 +02:00
Michelle Ark
34e6edbb13 Fix: deleting models that depend on external nodes (#8330) 2023-08-14 10:31:58 +02:00
FishtownBuildBot
27be92903e Add new index.html and changelog yaml files from dbt-docs (#8346) 2023-08-14 13:12:37 +08:00
Michelle Ark
9388030182 fix ModelNodeArgs.fqn (#8364) 2023-08-11 23:21:44 -04:00
Emily Rockman
b7aee3f5a4 add env vars to tox.ini (#8365)
* add env vars to tox.ini

* revert test
2023-08-11 10:49:41 -05:00
Michelle Ark
83ff38ab24 track plugin.get_nodes (#8336) 2023-08-10 11:33:11 -04:00
Michelle Ark
6603a44151 Detect changes to model access, deprecation_date, and latest_version in state:modified (#8264) 2023-08-10 11:29:02 -04:00
Kshitij Aranke
e69d4e7f14 Fix #8245: Add flag to codecov report (#8341) 2023-08-09 18:14:52 +01:00
d-kaneshiro
506f65e880 fixed comment util.py (#8222)
* fixed comments util.py

* add CHANGELOG entries
2023-08-09 09:24:38 -04:00
Gerda Shank
41bb52762b Bump manifest jsonschema to v11, update v10 schema (#8335) 2023-08-08 22:55:47 -04:00
Anju
8c98ef3e70 Copy dir if symlink fails (#7447) 2023-08-08 21:37:36 -04:00
Emily Rockman
44d1e73b4f Fix missing quote in fixtures.py (#8324) 2023-08-07 15:46:44 -05:00
Emily Rockman
53794fbaba Update implementation-ticket.yml (#8332) 2023-08-07 14:09:37 -06:00
Emily Rockman
556b4043e9 Update implementation-ticket.yml to reference adapters (#8329) 2023-08-07 12:39:29 -05:00
Grant Murray
424c636533 [CT-2776] [Feature] Enable-post-parse-population-of-dbt-custom-env (#7998)
* patch(events/functions): enable-repopulation-of-metadata-vars

* changie new
2023-08-04 12:59:47 -07:00
Emily Rockman
f63709260e add formatting events into json logs (#8308)
* add formatting events into json logs

* changelog

* Delete Under the Hood-20230803-100811.yaml
2023-08-03 15:56:01 -05:00
Michelle Ark
991618dfc1 capitalize integration-report name (#8265) 2023-08-02 18:06:05 -04:00
Chenyu Li
1af489b1cd fix constructing param with 0 value (#8298)
* fix constructing param with 0 value

* Update core/dbt/cli/flags.py

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>

---------

Co-authored-by: Doug Beatty <44704949+dbeatty10@users.noreply.github.com>
2023-08-02 14:57:55 -07:00
Kshitij Aranke
a433c31d6e Fix #7179 (#8279) 2023-08-02 16:56:16 +01:00
Peter Webb
5814928e38 Issue One Event Per Node Failure (#8210)
* Replaced the FirstRunResultError and AfterFirstRunResultError events with RunResultError.

* Attempts at reasonable unit tests.

* Restore event manager after unit test.
2023-08-02 10:24:05 -04:00
Ramon Vermeulen
6130a6e1d0 Support configurable delimiter for seed files, default to comma (#3990) (#7186)
* Support configurable delimiter for seed files, default to comma (#3990)

* Update Features-20230317-144957.yaml

* Moved "delimiter" to seed config instead of node config

* Update core/dbt/clients/agate_helper.py

Co-authored-by: Cor <jczuurmond@protonmail.com>

* Update test_contracts_graph_parsed.py

* fixed integration tests

* Added functional tests for seed files with a unique delimiter

* Added docstrings

* Added a test for an empty string configured delimiter value

* whitespace

* ran black

* updated changie entry

* Update Features-20230317-144957.yaml

---------

Co-authored-by: Cor <jczuurmond@protonmail.com>
2023-08-01 09:15:43 -07:00
Quigley Malcolm
7872f6a670 Add tests for specifcally checking the population of SemanticModel.depends_on (#8226) 2023-07-31 14:15:09 -07:00
Emily Rockman
f230e418aa hard code test splits (#8258)
* change trigger

* add logic for different targets

* fix comment

* hard code test splits
2023-07-31 13:26:17 -05:00
Quigley Malcolm
518eb73f88 [CT-2888] Support dbt-semantic-interfaces 0.2.0 (#8250)
* Upgrade DSI dependency to ~=0.2.0

* Allow users to specify `primary_entity` on semantic models

* Add `primary_entity` and `primary_entity_reference` to SemanticModel node

* Plumb primary_entity from unparsed to parsed semantic nodes

* Fix metric filter specifications in existing tests

* Add changie doc about supporting DSI 0.2.0
2023-07-28 14:36:51 -07:00
Emily Rockman
5b6d21d7da loosen the click pin (#8232)
* loosen the click pin

* changelog

* separate out sqlparse pin

* remove changelog

* add ignores
2023-07-28 15:53:27 -05:00
Michelle Ark
410506f448 [Fix] raise execution errors for runnable tasks (#8237) 2023-07-28 13:18:51 -04:00
dependabot[bot]
3cb44d37c0 Bump mypy from 1.4.0 to 1.4.1 (#8219)
* Bump mypy from 1.4.0 to 1.4.1

Bumps [mypy](https://github.com/python/mypy) from 1.4.0 to 1.4.1.
- [Commits](https://github.com/python/mypy/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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

* Add automated changelog yaml from template for bot PR

* update pre-commit

---------

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>
Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
2023-07-28 07:33:16 -05:00
Quigley Malcolm
f977ed7471 [CT-2879] Fix unbound variable error in checked_agg_time_dimension_for_measure (#8235)
* Fix unbound variable error in `checked_agg_time_dimension_for_measure`

* Improve assertion error message in `checked_agg_time_dimension_for_measure`

* Add changie doc for checked_agg_time_dimension_for_measure unbound variable fix

* Add unit tests for checking functionality of `checked_agg_time_dimension_for_measure`
2023-07-27 14:58:59 -07:00
Emily Rockman
3f5617b569 pin upper bound for sqlparse (#8236)
* pin upper bound for sqlparse

* changelog
2023-07-27 16:29:42 -05:00
Gerda Shank
fe9c875d32 Ensure that target_schema from snapshot config is promoted to node level (#8117) 2023-07-27 13:40:52 -04:00
Michelle Ark
23b16ad6d2 Split integration tests into parallel groups / jobs (#6346) 2023-07-27 11:27:34 -04:00
Gerda Shank
fdeccfaf24 Initialize sqlparse lexer and tweak order of setting compilation fields (#8215) 2023-07-26 17:29:51 -04:00
dependabot[bot]
fecde23da5 Bump mypy from 1.3.0 to 1.4.0 (#7912)
* Bump mypy from 1.3.0 to 1.4.0

Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.4.0.
- [Commits](https://github.com/python/mypy/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

* Add automated changelog yaml from template for bot PR

* add to pre-commit config

---------

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>
Co-authored-by: Emily Rockman <emily.rockman@dbtlabs.com>
2023-07-26 15:15:40 -05:00
Emily Rockman
b1d931337e up timeout (#8218) 2023-07-26 13:50:03 -05:00
Michelle Ark
39542336b8 Update implementation-ticket.yml (#8208) 2023-07-25 18:50:13 -04:00
Peter Webb
799588cada Update Core for Latest dbt-extractor with Version Parsing (#8206)
* Update dbt-extractor requirement, and adjust ref handling accordingly

* Add changelog entry.
2023-07-25 15:47:29 -04:00
Chenyu Li
f392add4b8 add param to control maxBytes for single dbt.log file (#8200)
* add param to control maxBytes for single dbt.log file

* nits

* nits

* Update core/dbt/cli/params.py

Co-authored-by: Peter Webb <peter.webb@dbtlabs.com>

---------

Co-authored-by: Peter Webb <peter.webb@dbtlabs.com>
2023-07-25 12:30:55 -07:00
Gerda Shank
49560bf2a2 Check for existing_relation immediately prior to renaming (#8193) 2023-07-25 12:59:18 -04:00
Quigley Malcolm
44b3ed5ae9 [CT-2594] Fix serialization of warn_error_options on Contexts (#8180)
* Add test ensuring `warn_error_options` is dictified in `invocation_args_dict` of contexts

* Add dictification specific to `warn_error_options` in `args_to_dict`

* Changie doc for serialization changes of warn_error_options
2023-07-24 13:35:27 -07:00
Quigley Malcolm
6235145641 [CT-1483] Let macro names include word materialization (#8181)
* Add test asserting that a macro with the work materializtion doesn't cause issues

* Let macro names include the word `materialization`

Previously we were checking if a macro included a materialization
based on if the macro name included the word `materialization`. However,
a macro name included the word `materialization` isn't guarnteed to
actually have a materialization, and a macro that doesn't have
`materialization` in the name isn't guaranteed to not have a materialization.
This change is to detect macros with materializations based on the
detected block type of the macro.

* Add changie doc materialization in macro detection
2023-07-24 13:10:42 -07:00
lllong33
ff5cb7ba51 Fixed double underline (#7944)
* Fixed double-underline
* backward compatibility postgres_get_relations
* Remove invalid comments
* compatibility adapter and get_relation
* fix generic for call
* fix adapter dispatch grammar issue
2023-07-21 17:28:27 -04:00
Quigley Malcolm
1e2b9ae962 [CT-1849] _connection_exception_retry handles EOFError exceptions (#8182)
* Add test for checking that `_connection_exception_retry` handles `EOFError`s

* Update `_connection_exception_retry` to handle `EOFError` exceptions

* Add changie docs for `_connection_exception_retry` handling `EOFError` exceptions
2023-07-21 11:34:06 -07:00
Michelle Ark
8cab58d248 Add implementation issue template (#8176) 2023-07-21 13:50:53 -04:00
Chenyu Li
0d645c227f add a node status (#8174) 2023-07-20 14:38:01 -07:00
Pádraic Slattery
fb6c349677 Correcting spelling of partition (#8101)
* Correcting spelling of partition

* Changie entry
2023-07-20 11:05:11 -07:00
Kshitij Aranke
eeb057085c Hotfix for 372: Use JSONEncoder in json.dumps (#8151) 2023-07-19 18:32:47 -05:00
Michelle Ark
121371f4a4 format exception from dbtPlugin.initialize (#8143) 2023-07-19 17:39:26 -04:00
Gerda Shank
a32713198b Rearrange pp_versioned_models test (#8150) 2023-07-19 17:05:09 -04:00
Jeremy Cohen
a1b067c683 Rm docs changelog entry (#8147) 2023-07-19 12:53:14 +02:00
leahwicz
22c40a4766 Update release-docker.yml 2023-05-03 09:33:31 -04:00
leahwicz
bcf140b3c1 Update release-docker.yml 2023-05-02 23:21:08 -04:00
leahwicz
e3692a6a3d Update release.yml 2023-05-02 23:11:40 -04:00
leahwicz
e7489383a2 Update release-docker.yml 2023-05-02 23:11:27 -04:00
leahwicz
70246c3f86 Update release.yml 2023-05-02 22:50:35 -04:00
leahwicz
0796c84da5 Update release-docker.yml 2023-05-02 22:50:18 -04:00
leahwicz
718482fb02 Update release.yml 2023-05-02 22:46:13 -04:00
leahwicz
a3fb66daa4 Update release-docker.yml 2023-05-02 22:45:41 -04:00
leahwicz
da34b80c26 Update release-docker.yml 2023-05-02 22:43:59 -04:00
leahwicz
ba5ab21140 Update release-docker.yml 2023-05-02 22:27:57 -04:00
leahwicz
65f41a1e36 Update testing.yml 2023-05-02 22:12:39 -04:00
leahwicz
0930c9c059 Update release.yml 2023-05-02 22:08:11 -04:00
leahwicz
1d193a9ab9 Update release-docker.yml 2023-05-02 22:07:53 -04:00
leahwicz
3adc6dca61 Update release-docker.yml 2023-05-02 22:03:15 -04:00
leahwicz
36d9f841d6 Update release-docker.yml 2023-05-02 22:01:27 -04:00
leahwicz
48ad13de00 Update release.yml 2023-05-02 21:57:19 -04:00
leahwicz
42935cce05 Update release.yml 2023-05-02 21:52:56 -04:00
leahwicz
e77f1c3b0f Update release.yml 2023-05-02 19:24:59 -04:00
leahwicz
388838aa99 Update testing.yml 2023-05-02 19:20:06 -04:00
leahwicz
d4d0990072 Update release.yml 2023-05-02 19:04:40 -04:00
leahwicz
4210d17f14 Update testing.yml 2023-05-02 19:03:15 -04:00
leahwicz
fbd12e78c9 Update testing.yml 2023-05-02 18:40:41 -04:00
leahwicz
83d3421e72 Update testing.yml 2023-05-02 17:55:56 -04:00
leahwicz
8bcbf73aaa Update release-docker.yml 2023-05-02 17:31:20 -04:00
leahwicz
cc5f15885d Update release.yml 2023-05-02 17:29:33 -04:00
leahwicz
20fdf55bf6 Update testing.yml 2023-05-02 17:15:07 -04:00
leahwicz
955dcec68b Update testing.yml 2023-05-02 17:03:23 -04:00
leahwicz
2b8564b16f Update testing.yml 2023-05-02 16:55:21 -04:00
leahwicz
57da3e51cd Update testing.yml 2023-05-02 13:14:50 -04:00
leahwicz
dede0e9747 Update testing.yml 2023-05-02 13:11:57 -04:00
leahwicz
35d2fc1158 Update testing.yml 2023-05-02 13:10:04 -04:00
leahwicz
c5267335a3 Update testing.yml 2023-05-02 13:08:00 -04:00
leahwicz
15c7b589c2 Update testing.yml 2023-05-02 13:06:35 -04:00
leahwicz
0ada5e8bf7 Create testing.yml 2023-05-02 12:17:26 -04:00
leahwicz
412ac8d1b9 Update release.yml 2023-04-19 09:08:39 -04:00
leahwicz
5df501a281 Update release.yml 2023-04-18 21:48:49 -04:00
leahwicz
3e4c61d020 Update release.yml 2023-04-18 21:46:26 -04:00
leahwicz
cc39fe51b3 Update release-docker.yml 2023-04-18 21:44:08 -04:00
leahwicz
89cd24388d Update release.yml 2023-04-18 21:40:55 -04:00
leahwicz
d5da0a8093 Update release.yml 2023-04-18 21:36:29 -04:00
leahwicz
88ae1f8871 Update release-docker.yml 2023-04-18 09:15:19 -04:00
leahwicz
50b3d1deaa Update release-docker.yml 2023-04-17 21:22:55 -04:00
leahwicz
3b3def5b8a Update release-docker.yml 2023-04-17 21:11:34 -04:00
leahwicz
4f068a45ff Update release-docker.yml 2023-04-17 21:06:40 -04:00
leahwicz
23a9504a51 Update release-docker.yml 2023-04-17 21:03:29 -04:00
leahwicz
d0d4eba477 Update release-docker.yml 2023-04-17 21:02:10 -04:00
leahwicz
a3fab0b5a9 Update release-docker.yml 2023-04-17 20:57:20 -04:00
397 changed files with 36702 additions and 4853 deletions

View File

@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.7.0a1
current_version = 1.7.8
parse = (?P<major>[\d]+) # major version number
\.(?P<minor>[\d]+) # minor version number
\.(?P<patch>[\d]+) # patch version number

157
.changes/1.7.0.md Normal file
View File

@@ -0,0 +1,157 @@
## dbt-core 1.7.0 - November 02, 2023
### Breaking Changes
- Removed the FirstRunResultError and AfterFirstRunResultError event types, using the existing RunResultError in their place. ([#7963](https://github.com/dbt-labs/dbt-core/issues/7963))
### Features
- add log file of installed packages via dbt deps ([#6643](https://github.com/dbt-labs/dbt-core/issues/6643))
- Enable re-population of metadata vars post-environment change during programmatic invocation ([#8010](https://github.com/dbt-labs/dbt-core/issues/8010))
- Added support to configure a delimiter for a seed file, defaults to comma ([#3990](https://github.com/dbt-labs/dbt-core/issues/3990))
- Allow specification of `create_metric: true` on measures ([#8125](https://github.com/dbt-labs/dbt-core/issues/8125))
- Add node attributes related to compilation to run_results.json ([#7519](https://github.com/dbt-labs/dbt-core/issues/7519))
- Add --no-inject-ephemeral-ctes flag for `compile` command, for usage by linting. ([#8480](https://github.com/dbt-labs/dbt-core/issues/8480))
- Support configuration of semantic models with the addition of enable/disable and group enablement. ([#7968](https://github.com/dbt-labs/dbt-core/issues/7968))
- Accept a `dbt-cloud` config in dbt_project.yml ([#8438](https://github.com/dbt-labs/dbt-core/issues/8438))
- Support atomic replace in the global replace macro ([#8539](https://github.com/dbt-labs/dbt-core/issues/8539))
- Use translate_type on data_type in model.columns in templates by default, remove no op `TYPE_LABELS` ([#8007](https://github.com/dbt-labs/dbt-core/issues/8007))
- Add an option to generate static documentation ([#8614](https://github.com/dbt-labs/dbt-core/issues/8614))
- Allow setting "access" as a config in addition to as a property ([#8383](https://github.com/dbt-labs/dbt-core/issues/8383))
- Loosen typing requirement on renameable/replaceable relations to Iterable to allow adapters more flexibility in registering relation types, include docstrings as suggestions ([#8647](https://github.com/dbt-labs/dbt-core/issues/8647))
- Add support for optional label in semantic_models, measures, dimensions and entities. ([#8595](https://github.com/dbt-labs/dbt-core/issues/8595), [#8755](https://github.com/dbt-labs/dbt-core/issues/8755))
- Allow adapters to include package logs in dbt standard logging ([#7859](https://github.com/dbt-labs/dbt-core/issues/7859))
- Support storing test failures as views ([#6914](https://github.com/dbt-labs/dbt-core/issues/6914))
- resolve packages with same git repo and unique subdirectory ([#5374](https://github.com/dbt-labs/dbt-core/issues/5374))
- Add new ResourceReport event to record memory/cpu/io metrics ([#8342](https://github.com/dbt-labs/dbt-core/issues/8342))
- Adding `date_spine` macro (and supporting macros) from dbt-utils to dbt-core ([#8172](https://github.com/dbt-labs/dbt-core/issues/8172))
- Support `fill_nulls_with` and `join_to_timespine` for metric nodes ([#8593](https://github.com/dbt-labs/dbt-core/issues/8593), [#8755](https://github.com/dbt-labs/dbt-core/issues/8755))
- Raise a warning when a contracted model has a numeric field without scale defined ([#8183](https://github.com/dbt-labs/dbt-core/issues/8183))
- Added support for retrieving partial catalog information from a schema ([#8521](https://github.com/dbt-labs/dbt-core/issues/8521))
- Add meta attribute to SemanticModels config ([#8511](https://github.com/dbt-labs/dbt-core/issues/8511))
- Selectors with docs generate limits catalog generation ([#6014](https://github.com/dbt-labs/dbt-core/issues/6014))
- Allow freshness to be determined via DBMS metadata for supported adapters ([#8704](https://github.com/dbt-labs/dbt-core/issues/8704))
- Add support semantic layer SavedQuery node type ([#8594](https://github.com/dbt-labs/dbt-core/issues/8594))
- Add exports to SavedQuery spec ([#8892](https://github.com/dbt-labs/dbt-core/issues/8892))
### Fixes
- Copy dir during `dbt deps` if symlink fails ([#7428](https://github.com/dbt-labs/dbt-core/issues/7428), [#8223](https://github.com/dbt-labs/dbt-core/issues/8223))
- If --profile specified with dbt-init, create the project with the specified profile ([#6154](https://github.com/dbt-labs/dbt-core/issues/6154))
- Fixed double-underline ([#5301](https://github.com/dbt-labs/dbt-core/issues/5301))
- Copy target_schema from config into snapshot node ([#6745](https://github.com/dbt-labs/dbt-core/issues/6745))
- Enable converting deprecation warnings to errors ([#8130](https://github.com/dbt-labs/dbt-core/issues/8130))
- Add status to Parse Inline Error ([#8173](https://github.com/dbt-labs/dbt-core/issues/8173))
- Ensure `warn_error_options` get serialized in `invocation_args_dict` ([#7694](https://github.com/dbt-labs/dbt-core/issues/7694))
- Stop detecting materialization macros based on macro name ([#6231](https://github.com/dbt-labs/dbt-core/issues/6231))
- Update `dbt deps` download retry logic to handle `EOFError` exceptions ([#6653](https://github.com/dbt-labs/dbt-core/issues/6653))
- Improve handling of CTE injection with ephemeral models ([#8213](https://github.com/dbt-labs/dbt-core/issues/8213))
- Fix unbound local variable error in `checked_agg_time_dimension_for_measure` ([#8230](https://github.com/dbt-labs/dbt-core/issues/8230))
- Ensure runtime errors are raised for graph runnable tasks (compile, show, run, etc) ([#8166](https://github.com/dbt-labs/dbt-core/issues/8166))
- Fix retry not working with log-file-max-bytes ([#8297](https://github.com/dbt-labs/dbt-core/issues/8297))
- Add explicit support for integers for the show command ([#8153](https://github.com/dbt-labs/dbt-core/issues/8153))
- Detect changes to model access, version, or latest_version in state:modified ([#8189](https://github.com/dbt-labs/dbt-core/issues/8189))
- Add connection status into list of statuses for dbt debug ([#8350](https://github.com/dbt-labs/dbt-core/issues/8350))
- fix fqn-selection for external versioned models ([#8374](https://github.com/dbt-labs/dbt-core/issues/8374))
- Fix: DbtInternalError after model that previously ref'd external model is deleted ([#8375](https://github.com/dbt-labs/dbt-core/issues/8375))
- Fix using list command with path selector and project-dir ([#8385](https://github.com/dbt-labs/dbt-core/issues/8385))
- Remedy performance regression by only writing run_results.json once. ([#8360](https://github.com/dbt-labs/dbt-core/issues/8360))
- Add support for swapping materialized views with tables/views and vice versa ([#8449](https://github.com/dbt-labs/dbt-core/issues/8449))
- Turn breaking changes to contracted models into warnings for unversioned models ([#8384](https://github.com/dbt-labs/dbt-core/issues/8384), [#8282](https://github.com/dbt-labs/dbt-core/issues/8282))
- Ensure parsing does not break when `window_groupings` is not specified for `non_additive_dimension` ([#8453](https://github.com/dbt-labs/dbt-core/issues/8453))
- fix ambiguous reference error for tests and versions when model name is duplicated across packages ([#8327](https://github.com/dbt-labs/dbt-core/issues/8327), [#8493](https://github.com/dbt-labs/dbt-core/issues/8493))
- Fix "Internal Error: Expected node <unique-id> not found in manifest" when depends_on set on ModelNodeArgs ([#8506](https://github.com/dbt-labs/dbt-core/issues/8506))
- Fix snapshot success message ([#7583](https://github.com/dbt-labs/dbt-core/issues/7583))
- Parse the correct schema version from manifest ([#8544](https://github.com/dbt-labs/dbt-core/issues/8544))
- make version comparison insensitive to order ([#8571](https://github.com/dbt-labs/dbt-core/issues/8571))
- Update metric helper functions to work with new semantic layer metrics ([#8134](https://github.com/dbt-labs/dbt-core/issues/8134))
- Disallow cleaning paths outside current working directory ([#8318](https://github.com/dbt-labs/dbt-core/issues/8318))
- Warn when --state == --target ([#8160](https://github.com/dbt-labs/dbt-core/issues/8160))
- update dbt show to include limit in DWH query ([#8496,](https://github.com/dbt-labs/dbt-core/issues/8496,), [#8417](https://github.com/dbt-labs/dbt-core/issues/8417))
- Support quoted parameter list for MultiOption CLI options. ([#8598](https://github.com/dbt-labs/dbt-core/issues/8598))
- Support global flags passed in after subcommands ([#6497](https://github.com/dbt-labs/dbt-core/issues/6497))
- Lower bound of `8.0.2` for `click` ([#8683](https://github.com/dbt-labs/dbt-core/issues/8683))
- Fixes test type edges filter ([#8692](https://github.com/dbt-labs/dbt-core/issues/8692))
- semantic models in graph selection ([#8589](https://github.com/dbt-labs/dbt-core/issues/8589))
- Support doc blocks in nested semantic model YAML ([#8509](https://github.com/dbt-labs/dbt-core/issues/8509))
- avoid double-rendering sql_header in dbt show ([#8739](https://github.com/dbt-labs/dbt-core/issues/8739))
- Fix tag selection for projects with semantic models ([#8749](https://github.com/dbt-labs/dbt-core/issues/8749))
- Foreign key constraint on incremental model results in Database Error ([#8022](https://github.com/dbt-labs/dbt-core/issues/8022))
- Support docs blocks on versioned model column descriptions ([#8540](https://github.com/dbt-labs/dbt-core/issues/8540))
- Enable seeds to be handled from stored manifest data ([#6875](https://github.com/dbt-labs/dbt-core/issues/6875))
- Override path-like args in dbt retry ([#8682](https://github.com/dbt-labs/dbt-core/issues/8682))
- Group updates on unmodified nodes are handled gracefully for state:modified ([#8371](https://github.com/dbt-labs/dbt-core/issues/8371))
- Partial parsing fix for adding groups and updating models at the same time ([#8697](https://github.com/dbt-labs/dbt-core/issues/8697))
- Fix partial parsing not working for semantic model change ([#8859](https://github.com/dbt-labs/dbt-core/issues/8859))
- Rework get_catalog implementation to retain previous adapter interface semantics ([#8846](https://github.com/dbt-labs/dbt-core/issues/8846))
- Add back contract enforcement for temporary tables on postgres ([#8857](https://github.com/dbt-labs/dbt-core/issues/8857))
- Add version to fqn when version==0 ([#8836](https://github.com/dbt-labs/dbt-core/issues/8836))
- Fix cased comparison in catalog-retrieval function. ([#8939](https://github.com/dbt-labs/dbt-core/issues/8939))
- Catalog queries now assign the correct type to materialized views ([#8864](https://github.com/dbt-labs/dbt-core/issues/8864))
- Make relation filtering None-tolerant for maximal flexibility across adapters. ([#8974](https://github.com/dbt-labs/dbt-core/issues/8974))
### Docs
- Corrected spelling of "Partiton" ([dbt-docs/#8100](https://github.com/dbt-labs/dbt-docs/issues/8100))
- Remove static SQL codeblock for metrics ([dbt-docs/#436](https://github.com/dbt-labs/dbt-docs/issues/436))
- fixed comment util.py ([dbt-docs/#None](https://github.com/dbt-labs/dbt-docs/issues/None))
- Fix newline escapes and improve formatting in docker README ([dbt-docs/#8211](https://github.com/dbt-labs/dbt-docs/issues/8211))
- Display contract and column constraints on the model page ([dbt-docs/#433](https://github.com/dbt-labs/dbt-docs/issues/433))
- Display semantic model details in docs ([dbt-docs/#431](https://github.com/dbt-labs/dbt-docs/issues/431))
### Under the Hood
- Switch from hologram to mashumaro jsonschema ([#8426](https://github.com/dbt-labs/dbt-core/issues/8426))
- Refactor flaky test pp_versioned_models ([#7781](https://github.com/dbt-labs/dbt-core/issues/7781))
- format exception from dbtPlugin.initialize ([#8152](https://github.com/dbt-labs/dbt-core/issues/8152))
- A way to control maxBytes for a single dbt.log file ([#8199](https://github.com/dbt-labs/dbt-core/issues/8199))
- Ref expressions with version can now be processed by the latest version of the high-performance dbt-extractor library. ([#7688](https://github.com/dbt-labs/dbt-core/issues/7688))
- Bump manifest schema version to v11, freeze manifest v10 ([#8333](https://github.com/dbt-labs/dbt-core/issues/8333))
- add tracking for plugin.get_nodes calls ([#8344](https://github.com/dbt-labs/dbt-core/issues/8344))
- add internal flag: --no-partial-parse-file-diff to inform whether to compute a file diff during partial parsing ([#8363](https://github.com/dbt-labs/dbt-core/issues/8363))
- Add return values to a number of functions for mypy ([#8389](https://github.com/dbt-labs/dbt-core/issues/8389))
- Fix mypy warnings for ManifestLoader.load() ([#8401](https://github.com/dbt-labs/dbt-core/issues/8401))
- Use python version 3.10.7 in Docker image. ([#8444](https://github.com/dbt-labs/dbt-core/issues/8444))
- Re-organize jinja macros: relation-specific in /macros/adapters/relations/<relation>, relation agnostic in /macros/relations ([#8449](https://github.com/dbt-labs/dbt-core/issues/8449))
- Update typing to meet mypy standards ([#8396](https://github.com/dbt-labs/dbt-core/issues/8396))
- Mypy errors - adapters/factory.py ([#8387](https://github.com/dbt-labs/dbt-core/issues/8387))
- Added more type annotations. ([#8537](https://github.com/dbt-labs/dbt-core/issues/8537))
- Audit potential circular dependencies ([#8349](https://github.com/dbt-labs/dbt-core/issues/8349))
- Add functional test for advanced ref override ([#8566](https://github.com/dbt-labs/dbt-core/issues/8566))
- Add typing to __init__ in base.py ([#8398](https://github.com/dbt-labs/dbt-core/issues/8398))
- Fix untyped functions in task/runnable.py (mypy warning) ([#8402](https://github.com/dbt-labs/dbt-core/issues/8402))
- add a test for ephemeral cte injection ([#8225](https://github.com/dbt-labs/dbt-core/issues/8225))
- Fix test_numeric_values to look for more specific strings ([#8470](https://github.com/dbt-labs/dbt-core/issues/8470))
- Pin types-requests<2.31.0 in `dev-requirements.txt` ([#8789](https://github.com/dbt-labs/dbt-core/issues/8789))
- Add warning_tag to UnversionedBreakingChange ([#8827](https://github.com/dbt-labs/dbt-core/issues/8827))
- Update v10 manifest schema to match 1.6 for testing schema compatibility ([#8835](https://github.com/dbt-labs/dbt-core/issues/8835))
- Add a no-op runner for Saved Qeury ([#8893](https://github.com/dbt-labs/dbt-core/issues/8893))
### Dependencies
- Bump mypy from 1.3.0 to 1.4.0 ([#7912](https://github.com/dbt-labs/dbt-core/pull/7912))
- Bump mypy from 1.4.0 to 1.4.1 ([#8219](https://github.com/dbt-labs/dbt-core/pull/8219))
- Update pin for click<9 ([#8232](https://github.com/dbt-labs/dbt-core/pull/8232))
- Add upper bound to sqlparse pin of <0.5 ([#8236](https://github.com/dbt-labs/dbt-core/pull/8236))
- Support dbt-semantic-interfaces 0.2.0 ([#8250](https://github.com/dbt-labs/dbt-core/pull/8250))
- Bump docker/build-push-action from 4 to 5 ([#8783](https://github.com/dbt-labs/dbt-core/pull/8783))
- Upgrade dbt-semantic-interfaces dep to 0.3.0 ([#8819](https://github.com/dbt-labs/dbt-core/pull/8819))
- Begin using DSI 0.4.x ([#8892](https://github.com/dbt-labs/dbt-core/pull/8892))
### Contributors
- [@anjutiwari](https://github.com/anjutiwari) ([#7428](https://github.com/dbt-labs/dbt-core/issues/7428), [#8223](https://github.com/dbt-labs/dbt-core/issues/8223))
- [@benmosher](https://github.com/benmosher) ([#8480](https://github.com/dbt-labs/dbt-core/issues/8480))
- [@d-kaneshiro](https://github.com/d-kaneshiro) ([#None](https://github.com/dbt-labs/dbt-core/issues/None))
- [@dave-connors-3](https://github.com/dave-connors-3) ([#8153](https://github.com/dbt-labs/dbt-core/issues/8153), [#8589](https://github.com/dbt-labs/dbt-core/issues/8589))
- [@dylan-murray](https://github.com/dylan-murray) ([#8683](https://github.com/dbt-labs/dbt-core/issues/8683))
- [@ezraerb](https://github.com/ezraerb) ([#6154](https://github.com/dbt-labs/dbt-core/issues/6154))
- [@gem7318](https://github.com/gem7318) ([#8010](https://github.com/dbt-labs/dbt-core/issues/8010))
- [@jamezrin](https://github.com/jamezrin) ([#8211](https://github.com/dbt-labs/dbt-core/issues/8211))
- [@jusbaldw](https://github.com/jusbaldw) ([#6643](https://github.com/dbt-labs/dbt-core/issues/6643))
- [@lllong33](https://github.com/lllong33) ([#5301](https://github.com/dbt-labs/dbt-core/issues/5301))
- [@marcodamore](https://github.com/marcodamore) ([#436](https://github.com/dbt-labs/dbt-core/issues/436))
- [@mescanne](https://github.com/mescanne) ([#8614](https://github.com/dbt-labs/dbt-core/issues/8614))
- [@pgoslatara](https://github.com/pgoslatara) ([#8100](https://github.com/dbt-labs/dbt-core/issues/8100))
- [@philippeboyd](https://github.com/philippeboyd) ([#5374](https://github.com/dbt-labs/dbt-core/issues/5374))
- [@ramonvermeulen](https://github.com/ramonvermeulen) ([#3990](https://github.com/dbt-labs/dbt-core/issues/3990))
- [@renanleme](https://github.com/renanleme) ([#8692](https://github.com/dbt-labs/dbt-core/issues/8692))

8
.changes/1.7.1.md Normal file
View File

@@ -0,0 +1,8 @@
## dbt-core 1.7.1 - November 07, 2023
### Fixes
- Fix compilation exception running empty seed file and support new Integer agate data_type ([#8895](https://github.com/dbt-labs/dbt-core/issues/8895))
- Update run_results.json from previous versions of dbt to support deferral and rerun from failure ([#9010](https://github.com/dbt-labs/dbt-core/issues/9010))
- Use MANIFEST.in to recursively include all jinja templates; fixes issue where some templates were not included in the distribution ([#9016](https://github.com/dbt-labs/dbt-core/issues/9016))
- Fix git repository with subdirectory for Deps ([#9000](https://github.com/dbt-labs/dbt-core/issues/9000))

16
.changes/1.7.2.md Normal file
View File

@@ -0,0 +1,16 @@
## dbt-core 1.7.2 - November 16, 2023
### Features
- Support setting export configs hierarchically via saved query and project configs ([#8956](https://github.com/dbt-labs/dbt-core/issues/8956))
### Fixes
- Fix formatting of tarball information in packages-lock.yml ([#9062](https://github.com/dbt-labs/dbt-core/issues/9062))
### Under the Hood
- Treat SystemExit as an interrupt if raised during node execution. ([#n/a](https://github.com/dbt-labs/dbt-core/issues/n/a))
### Contributors
- [@benmosher](https://github.com/benmosher) ([#n/a](https://github.com/dbt-labs/dbt-core/issues/n/a))

7
.changes/1.7.3.md Normal file
View File

@@ -0,0 +1,7 @@
## dbt-core 1.7.3 - November 29, 2023
### Fixes
- deps: Lock git packages to commit SHA during resolution ([#9050](https://github.com/dbt-labs/dbt-core/issues/9050))
- deps: Use PackageRenderer to read package-lock.json ([#9127](https://github.com/dbt-labs/dbt-core/issues/9127))
- Get sources working again in dbt docs generate ([#9119](https://github.com/dbt-labs/dbt-core/issues/9119))

12
.changes/1.7.4.md Normal file
View File

@@ -0,0 +1,12 @@
## dbt-core 1.7.4 - December 14, 2023
### Features
- Adds support for parsing conversion metric related properties for the semantic layer. ([#9203](https://github.com/dbt-labs/dbt-core/issues/9203))
### Fixes
- Ensure we produce valid jsonschema schemas for manifest, catalog, run-results, and sources ([#8991](https://github.com/dbt-labs/dbt-core/issues/8991))
### Contributors
- [@WilliamDee](https://github.com/WilliamDee) ([#9203](https://github.com/dbt-labs/dbt-core/issues/9203))

8
.changes/1.7.5.md Normal file
View File

@@ -0,0 +1,8 @@
## dbt-core 1.7.5 - January 18, 2024
### Fixes
- Preserve the value of vars and the --full-refresh flags when using retry. ([#9112](https://github.com/dbt-labs/dbt-core/issues/9112))
### Contributors
- [@peterallenwebb,](https://github.com/peterallenwebb,) ([#9112](https://github.com/dbt-labs/dbt-core/issues/9112))

6
.changes/1.7.6.md Normal file
View File

@@ -0,0 +1,6 @@
## dbt-core 1.7.6 - January 25, 2024
### Fixes
- Handle unknown `type_code` for model contracts ([#8877](https://github.com/dbt-labs/dbt-core/issues/8877), [#8353](https://github.com/dbt-labs/dbt-core/issues/8353))
- Fix retry command run from CLI ([#9444](https://github.com/dbt-labs/dbt-core/issues/9444))

6
.changes/1.7.7.md Normal file
View File

@@ -0,0 +1,6 @@
## dbt-core 1.7.7 - February 01, 2024
### Fixes
- Fix seed and source selection in `dbt docs generate` ([#9161](https://github.com/dbt-labs/dbt-core/issues/9161))
- Add TestGenerateCatalogWithExternalNodes, include empty nodes in node selection during docs generate ([#9456](https://github.com/dbt-labs/dbt-core/issues/9456))

6
.changes/1.7.8.md Normal file
View File

@@ -0,0 +1,6 @@
## dbt-core 1.7.8 - February 14, 2024
### Fixes
- When patching versioned models, set constraints after config ([#9364](https://github.com/dbt-labs/dbt-core/issues/9364))
- Store node_info in node associated logging events ([#9557](https://github.com/dbt-labs/dbt-core/issues/9557))

View File

@@ -1,6 +0,0 @@
kind: Docs
body: Fix for column tests not rendering on quoted columns
time: 2023-05-31T11:54:19.687363-04:00
custom:
Author: drewbanin
Issue: "201"

View File

@@ -1,6 +0,0 @@
kind: Docs
body: Remove static SQL codeblock for metrics
time: 2023-07-18T19:24:22.155323+02:00
custom:
Author: marcodamore
Issue: "436"

View File

@@ -1,6 +0,0 @@
kind: Fixes
body: Enable converting deprecation warnings to errors
time: 2023-07-18T12:55:18.03914-04:00
custom:
Author: michelleark
Issue: "8130"

19
.github/CODEOWNERS vendored
View File

@@ -13,23 +13,6 @@
# the core team as a whole will be assigned
* @dbt-labs/core-team
### OSS Tooling Guild
/.github/ @dbt-labs/guild-oss-tooling
.bumpversion.cfg @dbt-labs/guild-oss-tooling
.changie.yaml @dbt-labs/guild-oss-tooling
pre-commit-config.yaml @dbt-labs/guild-oss-tooling
pytest.ini @dbt-labs/guild-oss-tooling
tox.ini @dbt-labs/guild-oss-tooling
pyproject.toml @dbt-labs/guild-oss-tooling
requirements.txt @dbt-labs/guild-oss-tooling
dev_requirements.txt @dbt-labs/guild-oss-tooling
/core/setup.py @dbt-labs/guild-oss-tooling
/core/MANIFEST.in @dbt-labs/guild-oss-tooling
### ADAPTERS
# Adapter interface ("base" + "sql" adapter defaults, cache)
@@ -40,7 +23,7 @@ dev_requirements.txt @dbt-labs/guild-oss-tooling
# Postgres plugin
/plugins/ @dbt-labs/core-adapters
/plugins/postgres/setup.py @dbt-labs/core-adapters @dbt-labs/guild-oss-tooling
/plugins/postgres/setup.py @dbt-labs/core-adapters
# Functional tests for adapter plugins
/tests/adapter @dbt-labs/core-adapters

View File

@@ -0,0 +1,58 @@
name: 🛠️ Implementation
description: This is an implementation ticket intended for use by the maintainers of dbt-core
title: "[<project>] <title>"
labels: ["user docs"]
body:
- type: markdown
attributes:
value: This is an implementation ticket intended for use by the maintainers of dbt-core
- type: checkboxes
attributes:
label: Housekeeping
description: >
A couple friendly reminders:
1. Remove the `user docs` label if the scope of this work does not require changes to https://docs.getdbt.com/docs: no end-user interface (e.g. yml spec, CLI, error messages, etc) or functional changes
2. Link any blocking issues in the "Blocked on" field under the "Core devs & maintainers" project.
options:
- label: I am a maintainer of dbt-core
required: true
- type: textarea
attributes:
label: Short description
description: |
Describe the scope of the ticket, a high-level implementation approach and any tradeoffs to consider
validations:
required: true
- type: textarea
attributes:
label: Acceptance criteria
description: |
What is the definition of done for this ticket? Include any relevant edge cases and/or test cases
validations:
required: true
- type: textarea
attributes:
label: Impact to Other Teams
description: |
Will this change impact other teams? Include details of the kinds of changes required (new tests, code changes, related tickets) and _add the relevant `Impact:[team]` label_.
placeholder: |
Example: This change impacts `dbt-redshift` because the tests will need to be modified. The `Impact:[Adapter]` label has been added.
validations:
required: true
- type: textarea
attributes:
label: Will backports be required?
description: |
Will this change need to be backported to previous versions? Add details, possible blockers to backporting and _add the relevant backport labels `backport 1.x.latest`_
placeholder: |
Example: Backport to 1.6.latest, 1.5.latest and 1.4.latest. Since 1.4 isn't using click, the backport may be complicated. The `backport 1.6.latest`, `backport 1.5.latest` and `backport 1.4.latest` labels have been added.
validations:
required: true
- type: textarea
attributes:
label: Context
description: |
Provide the "why", motivation, and alternative approaches considered -- linking to previous refinement issues, spikes, Notion docs as appropriate
validations:
validations:
required: false

View File

@@ -28,3 +28,10 @@ updates:
schedule:
interval: "weekly"
rebase-strategy: "disabled"
# github dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
rebase-strategy: "disabled"

View File

@@ -1,15 +1,12 @@
resolves #
[docs](https://github.com/dbt-labs/docs.getdbt.com/issues/new/choose) dbt-labs/docs.getdbt.com/#
resolves #
<!---
Include the number of the issue addressed by this PR above if applicable.
PRs for code changes without an associated issue *will not be merged*.
See CONTRIBUTING.md for more information.
Include the number of the docs issue that was opened for this PR. If
this change has no user-facing implications, "N/A" suffices instead. New
docs tickets can be created by clicking the link above or by going to
https://github.com/dbt-labs/docs.getdbt.com/issues/new/choose.
Add the `user docs` label to this PR if it will need docs changes. An
issue will get opened in docs.getdbt.com upon successful merge of this PR.
-->
### Problem
@@ -33,3 +30,4 @@ resolves #
- [ ] I have run this code in development and it appears to resolve the stated issue
- [ ] This PR includes tests, or tests are not required/relevant for this PR
- [ ] This PR has no interface changes (e.g. macros, cli, logs, json artifacts, config files, adapter interface, etc) or this PR has already received feedback and approval from Product or DX
- [ ] This PR includes [type annotations](https://docs.python.org/3/library/typing.html) for new and modified functions

View File

@@ -2,10 +2,8 @@
# Checks that a file has been committed under the /.changes directory
# as a new CHANGELOG entry. Cannot check for a specific filename as
# it is dynamically generated by change type and timestamp.
# This workflow should not require any secrets since it runs for PRs
# from forked repos.
# By default, secrets are not passed to workflows running from
# a forked repo.
# This workflow runs on pull_request_target because it requires
# secrets to post comments.
# **why?**
# Ensure code change gets reflected in the CHANGELOG.
@@ -19,7 +17,7 @@
name: Check Changelog Entry
on:
pull_request:
pull_request_target:
types: [opened, reopened, labeled, unlabeled, synchronize]
workflow_dispatch:

43
.github/workflows/docs-issue.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
# **what?**
# Open an issue in docs.getdbt.com when a PR is labeled `user docs`
# **why?**
# To reduce barriers for keeping docs up to date
# **when?**
# When a PR is labeled `user docs` and is merged. Runs on pull_request_target to run off the workflow already merged,
# not the workflow that existed on the PR branch. This allows old PRs to get comments.
name: Open issues in docs.getdbt.com repo when a PR is labeled
run-name: "Open an issue in docs.getdbt.com for PR #${{ github.event.pull_request.number }}"
on:
pull_request_target:
types: [labeled, closed]
defaults:
run:
shell: bash
permissions:
issues: write # opens new issues
pull-requests: write # comments on PRs
jobs:
open_issues:
# we only want to run this when the PR has been merged or the label in the labeled event is `user docs`. Otherwise it runs the
# risk of duplicaton of issues being created due to merge and label both triggering this workflow to run and neither having
# generating the comment before the other runs. This lives here instead of the shared workflow because this is where we
# decide if it should run or not.
if: |
(github.event.pull_request.merged == true) &&
((github.event.action == 'closed' && contains( github.event.pull_request.labels.*.name, 'user docs')) ||
(github.event.action == 'labeled' && github.event.label.name == 'user docs'))
uses: dbt-labs/actions/.github/workflows/open-issue-in-repo.yml@main
with:
issue_repository: "dbt-labs/docs.getdbt.com"
issue_title: "Docs Changes Needed from ${{ github.event.repository.name }} PR #${{ github.event.pull_request.number }}"
issue_body: "At a minimum, update body to include a link to the page on docs.getdbt.com requiring updates and what part(s) of the page you would like to see updated."
secrets: inherit

View File

@@ -33,6 +33,11 @@ defaults:
run:
shell: bash
# top-level adjustments can be made here
env:
# number of parallel processes to spawn for python integration testing
PYTHON_INTEGRATION_TEST_WORKERS: 5
jobs:
code-quality:
name: code-quality
@@ -103,26 +108,59 @@ jobs:
- name: Upload Unit Test Coverage to Codecov
if: ${{ matrix.python-version == '3.11' }}
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unit
integration-metadata:
name: integration test metadata generation
runs-on: ubuntu-latest
outputs:
split-groups: ${{ steps.generate-split-groups.outputs.split-groups }}
include: ${{ steps.generate-include.outputs.include }}
steps:
- name: generate split-groups
id: generate-split-groups
run: |
MATRIX_JSON="["
for B in $(seq 1 ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }}); do
MATRIX_JSON+=$(sed 's/^/"/;s/$/"/' <<< "${B}")
done
MATRIX_JSON="${MATRIX_JSON//\"\"/\", \"}"
MATRIX_JSON+="]"
echo "split-groups=${MATRIX_JSON}"
echo "split-groups=${MATRIX_JSON}" >> $GITHUB_OUTPUT
- name: generate include
id: generate-include
run: |
INCLUDE=('"python-version":"3.8","os":"windows-latest"' '"python-version":"3.8","os":"macos-latest"' )
INCLUDE_GROUPS="["
for include in ${INCLUDE[@]}; do
for group in $(seq 1 ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }}); do
INCLUDE_GROUPS+=$(sed 's/$/, /' <<< "{\"split-group\":\"${group}\",${include}}")
done
done
INCLUDE_GROUPS=$(echo $INCLUDE_GROUPS | sed 's/,*$//g')
INCLUDE_GROUPS+="]"
echo "include=${INCLUDE_GROUPS}"
echo "include=${INCLUDE_GROUPS}" >> $GITHUB_OUTPUT
integration:
name: integration test / python ${{ matrix.python-version }} / ${{ matrix.os }}
name: (${{ matrix.split-group }}) integration test / python ${{ matrix.python-version }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
timeout-minutes: 30
needs:
- integration-metadata
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
os: [ubuntu-20.04]
include:
- python-version: 3.8
os: windows-latest
- python-version: 3.8
os: macos-latest
split-group: ${{ fromJson(needs.integration-metadata.outputs.split-groups) }}
include: ${{ fromJson(needs.integration-metadata.outputs.include) }}
env:
TOXENV: integration
DBT_INVOCATION_ENV: github-actions
@@ -165,6 +203,8 @@ jobs:
- name: Run tests
run: tox -- --ddtrace
env:
PYTEST_ADDOPTS: ${{ format('--splits {0} --group {1}', env.PYTHON_INTEGRATION_TEST_WORKERS, matrix.split-group) }}
- name: Get current date
if: always()
@@ -182,8 +222,26 @@ jobs:
- name: Upload Integration Test Coverage to Codecov
if: ${{ matrix.python-version == '3.11' }}
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: integration
integration-report:
if: ${{ always() }}
name: Integration Test Suite
runs-on: ubuntu-latest
needs: integration
steps:
- name: "Integration Tests Failed"
if: ${{ contains(needs.integration.result, 'failure') || contains(needs.integration.result, 'cancelled') }}
# when this is true the next step won't execute
run: |
echo "::notice title='Integration test suite failed'"
exit 1
- name: "Integration Tests Passed"
run: |
echo "::notice title='Integration test suite passed'"
build:
name: build packages

View File

@@ -83,7 +83,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push MAJOR.MINOR.PATCH tag
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
file: docker/Dockerfile
push: True
@@ -94,7 +94,7 @@ jobs:
ghcr.io/dbt-labs/${{ github.event.inputs.package }}:${{ github.event.inputs.version_number }}
- name: Build and push MINOR.latest tag
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
if: ${{ needs.get_version_meta.outputs.minor_latest == 'True' }}
with:
file: docker/Dockerfile
@@ -106,7 +106,7 @@ jobs:
ghcr.io/dbt-labs/${{ github.event.inputs.package }}:${{ needs.get_version_meta.outputs.major }}.${{ needs.get_version_meta.outputs.minor }}.latest
- name: Build and push latest tag
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
if: ${{ needs.get_version_meta.outputs.latest == 'True' }}
with:
file: docker/Dockerfile

View File

@@ -0,0 +1,30 @@
# **what?**
# Cleanup branches left over from automation and testing. Also cleanup
# draft releases from release testing.
# **why?**
# The automations are leaving behind branches and releases that clutter
# the repository. Sometimes we need them to debug processes so we don't
# want them immediately deleted. Running on Saturday to avoid running
# at the same time as an actual release to prevent breaking a release
# mid-release.
# **when?**
# Mainly on a schedule of 12:00 Saturday.
# Manual trigger can also run on demand
name: Repository Cleanup
on:
schedule:
- cron: '0 12 * * SAT' # At 12:00 on Saturday - details in `why` above
workflow_dispatch: # for manual triggering
permissions:
contents: write
jobs:
cleanup-repo:
uses: dbt-labs/actions/.github/workflows/repository-cleanup.yml@main
secrets: inherit

View File

@@ -18,11 +18,41 @@ on:
permissions: read-all
# top-level adjustments can be made here
env:
# number of parallel processes to spawn for python testing
PYTHON_INTEGRATION_TEST_WORKERS: 5
jobs:
integration-metadata:
name: integration test metadata generation
runs-on: ubuntu-latest
outputs:
split-groups: ${{ steps.generate-split-groups.outputs.split-groups }}
steps:
- name: generate split-groups
id: generate-split-groups
run: |
MATRIX_JSON="["
for B in $(seq 1 ${{ env.PYTHON_INTEGRATION_TEST_WORKERS }}); do
MATRIX_JSON+=$(sed 's/^/"/;s/$/"/' <<< "${B}")
done
MATRIX_JSON="${MATRIX_JSON//\"\"/\", \"}"
MATRIX_JSON+="]"
echo "split-groups=${MATRIX_JSON}" >> $GITHUB_OUTPUT
# run the performance measurements on the current or default branch
test-schema:
name: Test Log Schema
runs-on: ubuntu-20.04
timeout-minutes: 30
needs:
- integration-metadata
strategy:
fail-fast: false
matrix:
split-group: ${{ fromJson(needs.integration-metadata.outputs.split-groups) }}
env:
# turns warnings into errors
RUSTFLAGS: "-D warnings"
@@ -65,3 +95,14 @@ jobs:
# we actually care if these pass, because the normal test run doesn't usually include many json log outputs
- name: Run integration tests
run: tox -e integration -- -nauto
env:
PYTEST_ADDOPTS: ${{ format('--splits {0} --group {1}', env.PYTHON_INTEGRATION_TEST_WORKERS, matrix.split-group) }}
test-schema-report:
name: Log Schema Test Suite
runs-on: ubuntu-latest
needs: test-schema
steps:
- name: "[Notification] Log test suite passes"
run: |
echo "::notice title="Log test suite passes""

View File

@@ -37,7 +37,7 @@ repos:
alias: flake8-check
stages: [manual]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0
rev: v1.4.1
hooks:
- id: mypy
# N.B.: Mypy is... a bit fragile.

View File

@@ -5,6 +5,243 @@
- "Breaking changes" listed under a version may require action from end users or external maintainers when upgrading to that version.
- Do not edit this file directly. This file is auto-generated using [changie](https://github.com/miniscruff/changie). For details on how to document a change, see [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-changelog-entry)
## dbt-core 1.7.8 - February 14, 2024
### Fixes
- When patching versioned models, set constraints after config ([#9364](https://github.com/dbt-labs/dbt-core/issues/9364))
- Store node_info in node associated logging events ([#9557](https://github.com/dbt-labs/dbt-core/issues/9557))
## dbt-core 1.7.7 - February 01, 2024
### Fixes
- Fix seed and source selection in `dbt docs generate` ([#9161](https://github.com/dbt-labs/dbt-core/issues/9161))
- Add TestGenerateCatalogWithExternalNodes, include empty nodes in node selection during docs generate ([#9456](https://github.com/dbt-labs/dbt-core/issues/9456))
## dbt-core 1.7.6 - January 25, 2024
### Fixes
- Handle unknown `type_code` for model contracts ([#8877](https://github.com/dbt-labs/dbt-core/issues/8877), [#8353](https://github.com/dbt-labs/dbt-core/issues/8353))
- Fix retry command run from CLI ([#9444](https://github.com/dbt-labs/dbt-core/issues/9444))
## dbt-core 1.7.5 - January 18, 2024
### Fixes
- Preserve the value of vars and the --full-refresh flags when using retry. ([#9112](https://github.com/dbt-labs/dbt-core/issues/9112))
### Contributors
- [@peterallenwebb,](https://github.com/peterallenwebb,) ([#9112](https://github.com/dbt-labs/dbt-core/issues/9112))
## dbt-core 1.7.4 - December 14, 2023
### Features
- Adds support for parsing conversion metric related properties for the semantic layer. ([#9203](https://github.com/dbt-labs/dbt-core/issues/9203))
### Fixes
- Ensure we produce valid jsonschema schemas for manifest, catalog, run-results, and sources ([#8991](https://github.com/dbt-labs/dbt-core/issues/8991))
### Contributors
- [@WilliamDee](https://github.com/WilliamDee) ([#9203](https://github.com/dbt-labs/dbt-core/issues/9203))
## dbt-core 1.7.3 - November 29, 2023
### Fixes
- deps: Lock git packages to commit SHA during resolution ([#9050](https://github.com/dbt-labs/dbt-core/issues/9050))
- deps: Use PackageRenderer to read package-lock.json ([#9127](https://github.com/dbt-labs/dbt-core/issues/9127))
- Get sources working again in dbt docs generate ([#9119](https://github.com/dbt-labs/dbt-core/issues/9119))
## dbt-core 1.7.2 - November 16, 2023
### Features
- Support setting export configs hierarchically via saved query and project configs ([#8956](https://github.com/dbt-labs/dbt-core/issues/8956))
### Fixes
- Fix formatting of tarball information in packages-lock.yml ([#9062](https://github.com/dbt-labs/dbt-core/issues/9062))
### Under the Hood
- Treat SystemExit as an interrupt if raised during node execution. ([#n/a](https://github.com/dbt-labs/dbt-core/issues/n/a))
### Contributors
- [@benmosher](https://github.com/benmosher) ([#n/a](https://github.com/dbt-labs/dbt-core/issues/n/a))
## dbt-core 1.7.1 - November 07, 2023
### Fixes
- Fix compilation exception running empty seed file and support new Integer agate data_type ([#8895](https://github.com/dbt-labs/dbt-core/issues/8895))
- Update run_results.json from previous versions of dbt to support deferral and rerun from failure ([#9010](https://github.com/dbt-labs/dbt-core/issues/9010))
- Use MANIFEST.in to recursively include all jinja templates; fixes issue where some templates were not included in the distribution ([#9016](https://github.com/dbt-labs/dbt-core/issues/9016))
- Fix git repository with subdirectory for Deps ([#9000](https://github.com/dbt-labs/dbt-core/issues/9000))
## dbt-core 1.7.0 - November 02, 2023
### Breaking Changes
- Removed the FirstRunResultError and AfterFirstRunResultError event types, using the existing RunResultError in their place. ([#7963](https://github.com/dbt-labs/dbt-core/issues/7963))
### Features
- add log file of installed packages via dbt deps ([#6643](https://github.com/dbt-labs/dbt-core/issues/6643))
- Enable re-population of metadata vars post-environment change during programmatic invocation ([#8010](https://github.com/dbt-labs/dbt-core/issues/8010))
- Added support to configure a delimiter for a seed file, defaults to comma ([#3990](https://github.com/dbt-labs/dbt-core/issues/3990))
- Allow specification of `create_metric: true` on measures ([#8125](https://github.com/dbt-labs/dbt-core/issues/8125))
- Add node attributes related to compilation to run_results.json ([#7519](https://github.com/dbt-labs/dbt-core/issues/7519))
- Add --no-inject-ephemeral-ctes flag for `compile` command, for usage by linting. ([#8480](https://github.com/dbt-labs/dbt-core/issues/8480))
- Support configuration of semantic models with the addition of enable/disable and group enablement. ([#7968](https://github.com/dbt-labs/dbt-core/issues/7968))
- Accept a `dbt-cloud` config in dbt_project.yml ([#8438](https://github.com/dbt-labs/dbt-core/issues/8438))
- Support atomic replace in the global replace macro ([#8539](https://github.com/dbt-labs/dbt-core/issues/8539))
- Use translate_type on data_type in model.columns in templates by default, remove no op `TYPE_LABELS` ([#8007](https://github.com/dbt-labs/dbt-core/issues/8007))
- Add an option to generate static documentation ([#8614](https://github.com/dbt-labs/dbt-core/issues/8614))
- Allow setting "access" as a config in addition to as a property ([#8383](https://github.com/dbt-labs/dbt-core/issues/8383))
- Loosen typing requirement on renameable/replaceable relations to Iterable to allow adapters more flexibility in registering relation types, include docstrings as suggestions ([#8647](https://github.com/dbt-labs/dbt-core/issues/8647))
- Add support for optional label in semantic_models, measures, dimensions and entities. ([#8595](https://github.com/dbt-labs/dbt-core/issues/8595), [#8755](https://github.com/dbt-labs/dbt-core/issues/8755))
- Allow adapters to include package logs in dbt standard logging ([#7859](https://github.com/dbt-labs/dbt-core/issues/7859))
- Support storing test failures as views ([#6914](https://github.com/dbt-labs/dbt-core/issues/6914))
- resolve packages with same git repo and unique subdirectory ([#5374](https://github.com/dbt-labs/dbt-core/issues/5374))
- Add new ResourceReport event to record memory/cpu/io metrics ([#8342](https://github.com/dbt-labs/dbt-core/issues/8342))
- Adding `date_spine` macro (and supporting macros) from dbt-utils to dbt-core ([#8172](https://github.com/dbt-labs/dbt-core/issues/8172))
- Support `fill_nulls_with` and `join_to_timespine` for metric nodes ([#8593](https://github.com/dbt-labs/dbt-core/issues/8593), [#8755](https://github.com/dbt-labs/dbt-core/issues/8755))
- Raise a warning when a contracted model has a numeric field without scale defined ([#8183](https://github.com/dbt-labs/dbt-core/issues/8183))
- Added support for retrieving partial catalog information from a schema ([#8521](https://github.com/dbt-labs/dbt-core/issues/8521))
- Add meta attribute to SemanticModels config ([#8511](https://github.com/dbt-labs/dbt-core/issues/8511))
- Selectors with docs generate limits catalog generation ([#6014](https://github.com/dbt-labs/dbt-core/issues/6014))
- Allow freshness to be determined via DBMS metadata for supported adapters ([#8704](https://github.com/dbt-labs/dbt-core/issues/8704))
- Add support semantic layer SavedQuery node type ([#8594](https://github.com/dbt-labs/dbt-core/issues/8594))
- Add exports to SavedQuery spec ([#8892](https://github.com/dbt-labs/dbt-core/issues/8892))
### Fixes
- Copy dir during `dbt deps` if symlink fails ([#7428](https://github.com/dbt-labs/dbt-core/issues/7428), [#8223](https://github.com/dbt-labs/dbt-core/issues/8223))
- If --profile specified with dbt-init, create the project with the specified profile ([#6154](https://github.com/dbt-labs/dbt-core/issues/6154))
- Fixed double-underline ([#5301](https://github.com/dbt-labs/dbt-core/issues/5301))
- Copy target_schema from config into snapshot node ([#6745](https://github.com/dbt-labs/dbt-core/issues/6745))
- Enable converting deprecation warnings to errors ([#8130](https://github.com/dbt-labs/dbt-core/issues/8130))
- Add status to Parse Inline Error ([#8173](https://github.com/dbt-labs/dbt-core/issues/8173))
- Ensure `warn_error_options` get serialized in `invocation_args_dict` ([#7694](https://github.com/dbt-labs/dbt-core/issues/7694))
- Stop detecting materialization macros based on macro name ([#6231](https://github.com/dbt-labs/dbt-core/issues/6231))
- Update `dbt deps` download retry logic to handle `EOFError` exceptions ([#6653](https://github.com/dbt-labs/dbt-core/issues/6653))
- Improve handling of CTE injection with ephemeral models ([#8213](https://github.com/dbt-labs/dbt-core/issues/8213))
- Fix unbound local variable error in `checked_agg_time_dimension_for_measure` ([#8230](https://github.com/dbt-labs/dbt-core/issues/8230))
- Ensure runtime errors are raised for graph runnable tasks (compile, show, run, etc) ([#8166](https://github.com/dbt-labs/dbt-core/issues/8166))
- Fix retry not working with log-file-max-bytes ([#8297](https://github.com/dbt-labs/dbt-core/issues/8297))
- Add explicit support for integers for the show command ([#8153](https://github.com/dbt-labs/dbt-core/issues/8153))
- Detect changes to model access, version, or latest_version in state:modified ([#8189](https://github.com/dbt-labs/dbt-core/issues/8189))
- Add connection status into list of statuses for dbt debug ([#8350](https://github.com/dbt-labs/dbt-core/issues/8350))
- fix fqn-selection for external versioned models ([#8374](https://github.com/dbt-labs/dbt-core/issues/8374))
- Fix: DbtInternalError after model that previously ref'd external model is deleted ([#8375](https://github.com/dbt-labs/dbt-core/issues/8375))
- Fix using list command with path selector and project-dir ([#8385](https://github.com/dbt-labs/dbt-core/issues/8385))
- Remedy performance regression by only writing run_results.json once. ([#8360](https://github.com/dbt-labs/dbt-core/issues/8360))
- Add support for swapping materialized views with tables/views and vice versa ([#8449](https://github.com/dbt-labs/dbt-core/issues/8449))
- Turn breaking changes to contracted models into warnings for unversioned models ([#8384](https://github.com/dbt-labs/dbt-core/issues/8384), [#8282](https://github.com/dbt-labs/dbt-core/issues/8282))
- Ensure parsing does not break when `window_groupings` is not specified for `non_additive_dimension` ([#8453](https://github.com/dbt-labs/dbt-core/issues/8453))
- fix ambiguous reference error for tests and versions when model name is duplicated across packages ([#8327](https://github.com/dbt-labs/dbt-core/issues/8327), [#8493](https://github.com/dbt-labs/dbt-core/issues/8493))
- Fix "Internal Error: Expected node <unique-id> not found in manifest" when depends_on set on ModelNodeArgs ([#8506](https://github.com/dbt-labs/dbt-core/issues/8506))
- Fix snapshot success message ([#7583](https://github.com/dbt-labs/dbt-core/issues/7583))
- Parse the correct schema version from manifest ([#8544](https://github.com/dbt-labs/dbt-core/issues/8544))
- make version comparison insensitive to order ([#8571](https://github.com/dbt-labs/dbt-core/issues/8571))
- Update metric helper functions to work with new semantic layer metrics ([#8134](https://github.com/dbt-labs/dbt-core/issues/8134))
- Disallow cleaning paths outside current working directory ([#8318](https://github.com/dbt-labs/dbt-core/issues/8318))
- Warn when --state == --target ([#8160](https://github.com/dbt-labs/dbt-core/issues/8160))
- update dbt show to include limit in DWH query ([#8496,](https://github.com/dbt-labs/dbt-core/issues/8496,), [#8417](https://github.com/dbt-labs/dbt-core/issues/8417))
- Support quoted parameter list for MultiOption CLI options. ([#8598](https://github.com/dbt-labs/dbt-core/issues/8598))
- Support global flags passed in after subcommands ([#6497](https://github.com/dbt-labs/dbt-core/issues/6497))
- Lower bound of `8.0.2` for `click` ([#8683](https://github.com/dbt-labs/dbt-core/issues/8683))
- Fixes test type edges filter ([#8692](https://github.com/dbt-labs/dbt-core/issues/8692))
- semantic models in graph selection ([#8589](https://github.com/dbt-labs/dbt-core/issues/8589))
- Support doc blocks in nested semantic model YAML ([#8509](https://github.com/dbt-labs/dbt-core/issues/8509))
- avoid double-rendering sql_header in dbt show ([#8739](https://github.com/dbt-labs/dbt-core/issues/8739))
- Fix tag selection for projects with semantic models ([#8749](https://github.com/dbt-labs/dbt-core/issues/8749))
- Foreign key constraint on incremental model results in Database Error ([#8022](https://github.com/dbt-labs/dbt-core/issues/8022))
- Support docs blocks on versioned model column descriptions ([#8540](https://github.com/dbt-labs/dbt-core/issues/8540))
- Enable seeds to be handled from stored manifest data ([#6875](https://github.com/dbt-labs/dbt-core/issues/6875))
- Override path-like args in dbt retry ([#8682](https://github.com/dbt-labs/dbt-core/issues/8682))
- Group updates on unmodified nodes are handled gracefully for state:modified ([#8371](https://github.com/dbt-labs/dbt-core/issues/8371))
- Partial parsing fix for adding groups and updating models at the same time ([#8697](https://github.com/dbt-labs/dbt-core/issues/8697))
- Fix partial parsing not working for semantic model change ([#8859](https://github.com/dbt-labs/dbt-core/issues/8859))
- Rework get_catalog implementation to retain previous adapter interface semantics ([#8846](https://github.com/dbt-labs/dbt-core/issues/8846))
- Add back contract enforcement for temporary tables on postgres ([#8857](https://github.com/dbt-labs/dbt-core/issues/8857))
- Add version to fqn when version==0 ([#8836](https://github.com/dbt-labs/dbt-core/issues/8836))
- Fix cased comparison in catalog-retrieval function. ([#8939](https://github.com/dbt-labs/dbt-core/issues/8939))
- Catalog queries now assign the correct type to materialized views ([#8864](https://github.com/dbt-labs/dbt-core/issues/8864))
- Make relation filtering None-tolerant for maximal flexibility across adapters. ([#8974](https://github.com/dbt-labs/dbt-core/issues/8974))
### Docs
- Corrected spelling of "Partiton" ([dbt-docs/#8100](https://github.com/dbt-labs/dbt-docs/issues/8100))
- Remove static SQL codeblock for metrics ([dbt-docs/#436](https://github.com/dbt-labs/dbt-docs/issues/436))
- fixed comment util.py ([dbt-docs/#None](https://github.com/dbt-labs/dbt-docs/issues/None))
- Fix newline escapes and improve formatting in docker README ([dbt-docs/#8211](https://github.com/dbt-labs/dbt-docs/issues/8211))
- Display contract and column constraints on the model page ([dbt-docs/#433](https://github.com/dbt-labs/dbt-docs/issues/433))
- Display semantic model details in docs ([dbt-docs/#431](https://github.com/dbt-labs/dbt-docs/issues/431))
### Under the Hood
- Switch from hologram to mashumaro jsonschema ([#8426](https://github.com/dbt-labs/dbt-core/issues/8426))
- Refactor flaky test pp_versioned_models ([#7781](https://github.com/dbt-labs/dbt-core/issues/7781))
- format exception from dbtPlugin.initialize ([#8152](https://github.com/dbt-labs/dbt-core/issues/8152))
- A way to control maxBytes for a single dbt.log file ([#8199](https://github.com/dbt-labs/dbt-core/issues/8199))
- Ref expressions with version can now be processed by the latest version of the high-performance dbt-extractor library. ([#7688](https://github.com/dbt-labs/dbt-core/issues/7688))
- Bump manifest schema version to v11, freeze manifest v10 ([#8333](https://github.com/dbt-labs/dbt-core/issues/8333))
- add tracking for plugin.get_nodes calls ([#8344](https://github.com/dbt-labs/dbt-core/issues/8344))
- add internal flag: --no-partial-parse-file-diff to inform whether to compute a file diff during partial parsing ([#8363](https://github.com/dbt-labs/dbt-core/issues/8363))
- Add return values to a number of functions for mypy ([#8389](https://github.com/dbt-labs/dbt-core/issues/8389))
- Fix mypy warnings for ManifestLoader.load() ([#8401](https://github.com/dbt-labs/dbt-core/issues/8401))
- Use python version 3.10.7 in Docker image. ([#8444](https://github.com/dbt-labs/dbt-core/issues/8444))
- Re-organize jinja macros: relation-specific in /macros/adapters/relations/<relation>, relation agnostic in /macros/relations ([#8449](https://github.com/dbt-labs/dbt-core/issues/8449))
- Update typing to meet mypy standards ([#8396](https://github.com/dbt-labs/dbt-core/issues/8396))
- Mypy errors - adapters/factory.py ([#8387](https://github.com/dbt-labs/dbt-core/issues/8387))
- Added more type annotations. ([#8537](https://github.com/dbt-labs/dbt-core/issues/8537))
- Audit potential circular dependencies ([#8349](https://github.com/dbt-labs/dbt-core/issues/8349))
- Add functional test for advanced ref override ([#8566](https://github.com/dbt-labs/dbt-core/issues/8566))
- Add typing to __init__ in base.py ([#8398](https://github.com/dbt-labs/dbt-core/issues/8398))
- Fix untyped functions in task/runnable.py (mypy warning) ([#8402](https://github.com/dbt-labs/dbt-core/issues/8402))
- add a test for ephemeral cte injection ([#8225](https://github.com/dbt-labs/dbt-core/issues/8225))
- Fix test_numeric_values to look for more specific strings ([#8470](https://github.com/dbt-labs/dbt-core/issues/8470))
- Pin types-requests<2.31.0 in `dev-requirements.txt` ([#8789](https://github.com/dbt-labs/dbt-core/issues/8789))
- Add warning_tag to UnversionedBreakingChange ([#8827](https://github.com/dbt-labs/dbt-core/issues/8827))
- Update v10 manifest schema to match 1.6 for testing schema compatibility ([#8835](https://github.com/dbt-labs/dbt-core/issues/8835))
- Add a no-op runner for Saved Qeury ([#8893](https://github.com/dbt-labs/dbt-core/issues/8893))
### Dependencies
- Bump mypy from 1.3.0 to 1.4.0 ([#7912](https://github.com/dbt-labs/dbt-core/pull/7912))
- Bump mypy from 1.4.0 to 1.4.1 ([#8219](https://github.com/dbt-labs/dbt-core/pull/8219))
- Update pin for click<9 ([#8232](https://github.com/dbt-labs/dbt-core/pull/8232))
- Add upper bound to sqlparse pin of <0.5 ([#8236](https://github.com/dbt-labs/dbt-core/pull/8236))
- Support dbt-semantic-interfaces 0.2.0 ([#8250](https://github.com/dbt-labs/dbt-core/pull/8250))
- Bump docker/build-push-action from 4 to 5 ([#8783](https://github.com/dbt-labs/dbt-core/pull/8783))
- Upgrade dbt-semantic-interfaces dep to 0.3.0 ([#8819](https://github.com/dbt-labs/dbt-core/pull/8819))
- Begin using DSI 0.4.x ([#8892](https://github.com/dbt-labs/dbt-core/pull/8892))
### Contributors
- [@anjutiwari](https://github.com/anjutiwari) ([#7428](https://github.com/dbt-labs/dbt-core/issues/7428), [#8223](https://github.com/dbt-labs/dbt-core/issues/8223))
- [@benmosher](https://github.com/benmosher) ([#8480](https://github.com/dbt-labs/dbt-core/issues/8480))
- [@d-kaneshiro](https://github.com/d-kaneshiro) ([#None](https://github.com/dbt-labs/dbt-core/issues/None))
- [@dave-connors-3](https://github.com/dave-connors-3) ([#8153](https://github.com/dbt-labs/dbt-core/issues/8153), [#8589](https://github.com/dbt-labs/dbt-core/issues/8589))
- [@dylan-murray](https://github.com/dylan-murray) ([#8683](https://github.com/dbt-labs/dbt-core/issues/8683))
- [@ezraerb](https://github.com/ezraerb) ([#6154](https://github.com/dbt-labs/dbt-core/issues/6154))
- [@gem7318](https://github.com/gem7318) ([#8010](https://github.com/dbt-labs/dbt-core/issues/8010))
- [@jamezrin](https://github.com/jamezrin) ([#8211](https://github.com/dbt-labs/dbt-core/issues/8211))
- [@jusbaldw](https://github.com/jusbaldw) ([#6643](https://github.com/dbt-labs/dbt-core/issues/6643))
- [@lllong33](https://github.com/lllong33) ([#5301](https://github.com/dbt-labs/dbt-core/issues/5301))
- [@marcodamore](https://github.com/marcodamore) ([#436](https://github.com/dbt-labs/dbt-core/issues/436))
- [@mescanne](https://github.com/mescanne) ([#8614](https://github.com/dbt-labs/dbt-core/issues/8614))
- [@pgoslatara](https://github.com/pgoslatara) ([#8100](https://github.com/dbt-labs/dbt-core/issues/8100))
- [@philippeboyd](https://github.com/philippeboyd) ([#5374](https://github.com/dbt-labs/dbt-core/issues/5374))
- [@ramonvermeulen](https://github.com/ramonvermeulen) ([#3990](https://github.com/dbt-labs/dbt-core/issues/3990))
- [@renanleme](https://github.com/renanleme) ([#8692](https://github.com/dbt-labs/dbt-core/issues/8692))
## Previous Releases
For information on prior major and minor releases, see their changelogs:

View File

@@ -0,0 +1,13 @@
ignore:
- ".github"
- ".changes"
coverage:
status:
project:
default:
target: auto
threshold: 0.1% # Reduce noise by ignoring rounding errors in coverage drops
patch:
default:
target: auto
threshold: 80%

View File

@@ -7,12 +7,12 @@ from dbt.exceptions import DbtRuntimeError
@dataclass
class Column:
# Note: This is automatically used by contract code
# No-op conversions (INTEGER => INT) have been removed.
# Any adapter that wants to take advantage of "translate_type"
# should create a ClassVar with the appropriate conversions.
TYPE_LABELS: ClassVar[Dict[str, str]] = {
"STRING": "TEXT",
"TIMESTAMP": "TIMESTAMP",
"FLOAT": "FLOAT",
"INTEGER": "INT",
"BOOLEAN": "BOOLEAN",
}
column: str
dtype: str

View File

@@ -72,7 +72,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
TYPE: str = NotImplemented
def __init__(self, profile: AdapterRequiredConfig):
def __init__(self, profile: AdapterRequiredConfig) -> None:
self.profile = profile
self.thread_connections: Dict[Hashable, Connection] = {}
self.lock: RLock = flags.MP_CONTEXT.RLock()
@@ -400,7 +400,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
@abc.abstractmethod
def execute(
self, sql: str, auto_begin: bool = False, fetch: bool = False
self, sql: str, auto_begin: bool = False, fetch: bool = False, limit: Optional[int] = None
) -> Tuple[AdapterResponse, agate.Table]:
"""Execute the given SQL.
@@ -408,7 +408,28 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
:param bool auto_begin: If set, and dbt is not currently inside a
transaction, automatically begin one.
:param bool fetch: If set, fetch results.
:param int limit: If set, limits the result set
:return: A tuple of the query status and results (empty if fetch=False).
:rtype: Tuple[AdapterResponse, agate.Table]
"""
raise dbt.exceptions.NotImplementedError("`execute` is not implemented for this adapter!")
def add_select_query(self, sql: str) -> Tuple[Connection, Any]:
"""
This was added here because base.impl.BaseAdapter.get_column_schema_from_query expects it to be here.
That method wouldn't work unless the adapter used sql.impl.SQLAdapter, sql.connections.SQLConnectionManager
or defined this method on <Adapter>ConnectionManager before passing it in to <Adapter>Adapter.
See https://github.com/dbt-labs/dbt-core/issues/8396 for more information.
"""
raise dbt.exceptions.NotImplementedError(
"`add_select_query` is not implemented for this adapter!"
)
@classmethod
def data_type_code_to_name(cls, type_code: Union[int, str]) -> str:
"""Get the string representation of the data type from the type_code."""
# https://peps.python.org/pep-0249/#type-objects
raise dbt.exceptions.NotImplementedError(
"`data_type_code_to_name` is not implemented for this adapter!"
)

View File

@@ -17,9 +17,11 @@ from typing import (
Set,
Tuple,
Type,
TypedDict,
Union,
)
from dbt.adapters.capability import Capability, CapabilityDict
from dbt.contracts.graph.nodes import ColumnLevelConstraint, ConstraintType, ModelLevelConstraint
import agate
@@ -43,8 +45,14 @@ from dbt.exceptions import (
UnexpectedNullError,
)
from dbt.adapters.protocol import AdapterConfig, ConnectionManagerProtocol
from dbt.clients.agate_helper import empty_table, merge_tables, table_from_rows
from dbt.adapters.protocol import AdapterConfig
from dbt.clients.agate_helper import (
empty_table,
get_column_value_uncased,
merge_tables,
table_from_rows,
Integer,
)
from dbt.clients.jinja import MacroGenerator
from dbt.contracts.graph.manifest import Manifest, MacroManifest
from dbt.contracts.graph.nodes import ResultNode
@@ -60,7 +68,7 @@ from dbt.events.types import (
)
from dbt.utils import filter_null_values, executor, cast_to_str, AttrDict
from dbt.adapters.base.connections import Connection, AdapterResponse
from dbt.adapters.base.connections import Connection, AdapterResponse, BaseConnectionManager
from dbt.adapters.base.meta import AdapterMeta, available
from dbt.adapters.base.relation import (
ComponentName,
@@ -74,7 +82,9 @@ from dbt.adapters.cache import RelationsCache, _make_ref_key_dict
from dbt import deprecations
GET_CATALOG_MACRO_NAME = "get_catalog"
GET_CATALOG_RELATIONS_MACRO_NAME = "get_catalog_relations"
FRESHNESS_MACRO_NAME = "collect_freshness"
GET_RELATION_LAST_MODIFIED_MACRO_NAME = "get_relation_last_modified"
class ConstraintSupport(str, Enum):
@@ -109,7 +119,7 @@ def _catalog_filter_schemas(manifest: Manifest) -> Callable[[agate.Row], bool]:
return test
def _utc(dt: Optional[datetime], source: BaseRelation, field_name: str) -> datetime:
def _utc(dt: Optional[datetime], source: Optional[BaseRelation], field_name: str) -> datetime:
"""If dt has a timezone, return a new datetime that's in UTC. Otherwise,
assume the datetime is already for UTC and add the timezone.
"""
@@ -161,6 +171,12 @@ class PythonJobHelper:
raise NotImplementedError("PythonJobHelper submit function is not implemented yet")
class FreshnessResponse(TypedDict):
max_loaded_at: datetime
snapshotted_at: datetime
age: float # age in seconds
class BaseAdapter(metaclass=AdapterMeta):
"""The BaseAdapter provides an abstract base class for adapters.
@@ -208,7 +224,7 @@ class BaseAdapter(metaclass=AdapterMeta):
Relation: Type[BaseRelation] = BaseRelation
Column: Type[BaseColumn] = BaseColumn
ConnectionManager: Type[ConnectionManagerProtocol]
ConnectionManager: Type[BaseConnectionManager]
# A set of clobber config fields accepted by this adapter
# for use in materializations
@@ -222,7 +238,11 @@ class BaseAdapter(metaclass=AdapterMeta):
ConstraintType.foreign_key: ConstraintSupport.ENFORCED,
}
def __init__(self, config):
# This static member variable can be overriden in concrete adapter
# implementations to indicate adapter support for optional capabilities.
_capabilities = CapabilityDict({})
def __init__(self, config) -> None:
self.config = config
self.cache = RelationsCache()
self.connections = self.ConnectionManager(config)
@@ -315,14 +335,21 @@ class BaseAdapter(metaclass=AdapterMeta):
@available.parse(lambda *a, **k: ("", empty_table()))
def get_partitions_metadata(self, table: str) -> Tuple[agate.Table]:
"""Obtain partitions metadata for a BigQuery partitioned table.
"""
TODO: Can we move this to dbt-bigquery?
Obtain partitions metadata for a BigQuery partitioned table.
:param str table_id: a partitioned table id, in standard SQL format.
:param str table: a partitioned table id, in standard SQL format.
:return: a partition metadata tuple, as described in
https://cloud.google.com/bigquery/docs/creating-partitioned-tables#getting_partition_metadata_using_meta_tables.
:rtype: agate.Table
"""
return self.connections.get_partitions_metadata(table=table)
if hasattr(self.connections, "get_partitions_metadata"):
return self.connections.get_partitions_metadata(table=table)
else:
raise NotImplementedError(
"`get_partitions_metadata` is not implemented for this adapter!"
)
###
# Methods that should never be overridden
@@ -408,7 +435,30 @@ class BaseAdapter(metaclass=AdapterMeta):
lowercase strings.
"""
info_schema_name_map = SchemaSearchMap()
nodes: Iterator[ResultNode] = chain(
relations = self._get_catalog_relations(manifest)
for relation in relations:
info_schema_name_map.add(relation)
# result is a map whose keys are information_schema Relations without
# identifiers that have appropriate database prefixes, and whose values
# are sets of lowercase schema names that are valid members of those
# databases
return info_schema_name_map
def _get_catalog_relations_by_info_schema(
self, relations
) -> Dict[InformationSchema, List[BaseRelation]]:
relations_by_info_schema: Dict[InformationSchema, List[BaseRelation]] = dict()
for relation in relations:
info_schema = relation.information_schema_only()
if info_schema not in relations_by_info_schema:
relations_by_info_schema[info_schema] = []
relations_by_info_schema[info_schema].append(relation)
return relations_by_info_schema
def _get_catalog_relations(self, manifest: Manifest) -> List[BaseRelation]:
nodes = chain(
[
node
for node in manifest.nodes.values()
@@ -416,14 +466,9 @@ class BaseAdapter(metaclass=AdapterMeta):
],
manifest.sources.values(),
)
for node in nodes:
relation = self.Relation.create_from(self.config, node)
info_schema_name_map.add(relation)
# result is a map whose keys are information_schema Relations without
# identifiers that have appropriate database prefixes, and whose values
# are sets of lowercase schema names that are valid members of those
# databases
return info_schema_name_map
relations = [self.Relation.create_from(self.config, n) for n in nodes]
return relations
def _relations_cache_for_schemas(
self, manifest: Manifest, cache_schemas: Optional[Set[BaseRelation]] = None
@@ -453,9 +498,10 @@ class BaseAdapter(metaclass=AdapterMeta):
# it's possible that there were no relations in some schemas. We want
# to insert the schemas we query into the cache's `.schemas` attribute
# so we can check it later
cache_update: Set[Tuple[Optional[str], Optional[str]]] = set()
cache_update: Set[Tuple[Optional[str], str]] = set()
for relation in cache_schemas:
cache_update.add((relation.database, relation.schema))
if relation.schema:
cache_update.add((relation.database, relation.schema))
self.cache.update_schemas(cache_update)
def set_relations_cache(
@@ -917,6 +963,17 @@ class BaseAdapter(metaclass=AdapterMeta):
"""
raise NotImplementedError("`convert_number_type` is not implemented for this adapter!")
@classmethod
def convert_integer_type(cls, agate_table: agate.Table, col_idx: int) -> str:
"""Return the type in the database that best maps to the agate.Number
type for the given agate table and column index.
:param agate_table: The table
:param col_idx: The index into the agate table for the column.
:return: The name of the type in the database
"""
return "integer"
@classmethod
@abc.abstractmethod
def convert_boolean_type(cls, agate_table: agate.Table, col_idx: int) -> str:
@@ -974,6 +1031,7 @@ class BaseAdapter(metaclass=AdapterMeta):
def convert_agate_type(cls, agate_table: agate.Table, col_idx: int) -> Optional[str]:
agate_type: Type = agate_table.column_types[col_idx]
conversions: List[Tuple[Type, Callable[..., str]]] = [
(Integer, cls.convert_integer_type),
(agate.Text, cls.convert_text_type),
(agate.Number, cls.convert_number_type),
(agate.Boolean, cls.convert_boolean_type),
@@ -1085,25 +1143,108 @@ class BaseAdapter(metaclass=AdapterMeta):
results = self._catalog_filter_table(table, manifest) # type: ignore[arg-type]
return results
def get_catalog(self, manifest: Manifest) -> Tuple[agate.Table, List[Exception]]:
schema_map = self._get_catalog_schemas(manifest)
def _get_one_catalog_by_relations(
self,
information_schema: InformationSchema,
relations: List[BaseRelation],
manifest: Manifest,
) -> agate.Table:
kwargs = {
"information_schema": information_schema,
"relations": relations,
}
table = self.execute_macro(
GET_CATALOG_RELATIONS_MACRO_NAME,
kwargs=kwargs,
# pass in the full manifest, so we get any local project
# overrides
manifest=manifest,
)
results = self._catalog_filter_table(table, manifest) # type: ignore[arg-type]
return results
def get_filtered_catalog(
self, manifest: Manifest, relations: Optional[Set[BaseRelation]] = None
):
catalogs: agate.Table
if (
relations is None
or len(relations) > 100
or not self.supports(Capability.SchemaMetadataByRelations)
):
# Do it the traditional way. We get the full catalog.
catalogs, exceptions = self.get_catalog(manifest)
else:
# Do it the new way. We try to save time by selecting information
# only for the exact set of relations we are interested in.
catalogs, exceptions = self.get_catalog_by_relations(manifest, relations)
if relations and catalogs:
relation_map = {
(
r.database.casefold() if r.database else None,
r.schema.casefold() if r.schema else None,
r.identifier.casefold() if r.identifier else None,
)
for r in relations
}
def in_map(row: agate.Row):
d = _expect_row_value("table_database", row)
s = _expect_row_value("table_schema", row)
i = _expect_row_value("table_name", row)
d = d.casefold() if d is not None else None
s = s.casefold() if s is not None else None
i = i.casefold() if i is not None else None
return (d, s, i) in relation_map
catalogs = catalogs.where(in_map)
return catalogs, exceptions
def row_matches_relation(self, row: agate.Row, relations: Set[BaseRelation]):
pass
def get_catalog(self, manifest: Manifest) -> Tuple[agate.Table, List[Exception]]:
with executor(self.config) as tpe:
futures: List[Future[agate.Table]] = []
schema_map: SchemaSearchMap = self._get_catalog_schemas(manifest)
for info, schemas in schema_map.items():
if len(schemas) == 0:
continue
name = ".".join([str(info.database), "information_schema"])
fut = tpe.submit_connected(
self, name, self._get_one_catalog, info, schemas, manifest
)
futures.append(fut)
catalogs, exceptions = catch_as_completed(futures)
catalogs, exceptions = catch_as_completed(futures)
return catalogs, exceptions
def get_catalog_by_relations(
self, manifest: Manifest, relations: Set[BaseRelation]
) -> Tuple[agate.Table, List[Exception]]:
with executor(self.config) as tpe:
futures: List[Future[agate.Table]] = []
relations_by_schema = self._get_catalog_relations_by_info_schema(relations)
for info_schema in relations_by_schema:
name = ".".join([str(info_schema.database), "information_schema"])
relations = set(relations_by_schema[info_schema])
fut = tpe.submit_connected(
self,
name,
self._get_one_catalog_by_relations,
info_schema,
relations,
manifest,
)
futures.append(fut)
catalogs, exceptions = catch_as_completed(futures)
return catalogs, exceptions
def cancel_open_connections(self):
"""Cancel all open connections."""
return self.connections.cancel_open()
@@ -1114,7 +1255,7 @@ class BaseAdapter(metaclass=AdapterMeta):
loaded_at_field: str,
filter: Optional[str],
manifest: Optional[Manifest] = None,
) -> Tuple[Optional[AdapterResponse], Dict[str, Any]]:
) -> Tuple[Optional[AdapterResponse], FreshnessResponse]:
"""Calculate the freshness of sources in dbt, and return it"""
kwargs: Dict[str, Any] = {
"source": source,
@@ -1149,13 +1290,52 @@ class BaseAdapter(metaclass=AdapterMeta):
snapshotted_at = _utc(table[0][1], source, loaded_at_field)
age = (snapshotted_at - max_loaded_at).total_seconds()
freshness = {
freshness: FreshnessResponse = {
"max_loaded_at": max_loaded_at,
"snapshotted_at": snapshotted_at,
"age": age,
}
return adapter_response, freshness
def calculate_freshness_from_metadata(
self,
source: BaseRelation,
manifest: Optional[Manifest] = None,
) -> Tuple[Optional[AdapterResponse], FreshnessResponse]:
kwargs: Dict[str, Any] = {
"information_schema": source.information_schema_only(),
"relations": [source],
}
result = self.execute_macro(
GET_RELATION_LAST_MODIFIED_MACRO_NAME, kwargs=kwargs, manifest=manifest
)
adapter_response, table = result.response, result.table # type: ignore[attr-defined]
try:
row = table[0]
last_modified_val = get_column_value_uncased("last_modified", row)
snapshotted_at_val = get_column_value_uncased("snapshotted_at", row)
except Exception:
raise MacroResultError(GET_RELATION_LAST_MODIFIED_MACRO_NAME, table)
if last_modified_val is None:
# Interpret missing value as "infinitely long ago"
max_loaded_at = datetime(1, 1, 1, 0, 0, 0, tzinfo=pytz.UTC)
else:
max_loaded_at = _utc(last_modified_val, None, "last_modified")
snapshotted_at = _utc(snapshotted_at_val, None, "snapshotted_at")
age = (snapshotted_at - max_loaded_at).total_seconds()
freshness: FreshnessResponse = {
"max_loaded_at": max_loaded_at,
"snapshotted_at": snapshotted_at,
"age": age,
}
return adapter_response, freshness
def pre_model_hook(self, config: Mapping[str, Any]) -> Any:
"""A hook for running some operation before the model materialization
runs. The hook can assume it has a connection available.
@@ -1429,6 +1609,14 @@ class BaseAdapter(metaclass=AdapterMeta):
else:
return None
@classmethod
def capabilities(cls) -> CapabilityDict:
return cls._capabilities
@classmethod
def supports(cls, capability: Capability) -> bool:
return bool(cls.capabilities()[capability])
COLUMNS_EQUAL_SQL = """
with diff_count as (

View File

@@ -93,7 +93,7 @@ class AdapterMeta(abc.ABCMeta):
_available_: FrozenSet[str]
_parse_replacements_: Dict[str, Callable]
def __new__(mcls, name, bases, namespace, **kwargs):
def __new__(mcls, name, bases, namespace, **kwargs) -> "AdapterMeta":
# mypy does not like the `**kwargs`. But `ABCMeta` itself takes
# `**kwargs` in its argspec here (and passes them to `type.__new__`.
# I'm not sure there is any benefit to it after poking around a bit,

View File

@@ -29,7 +29,7 @@ class AdapterPlugin:
credentials: Type[Credentials],
include_path: str,
dependencies: Optional[List[str]] = None,
):
) -> None:
self.adapter: Type[AdapterProtocol] = adapter
self.credentials: Type[Credentials] = credentials

View File

@@ -11,7 +11,7 @@ from dbt.exceptions import DbtRuntimeError
class NodeWrapper:
def __init__(self, node):
def __init__(self, node) -> None:
self._inner_node = node
def __getattr__(self, name):
@@ -25,9 +25,9 @@ class _QueryComment(local):
- a source_name indicating what set the current thread's query comment
"""
def __init__(self, initial):
def __init__(self, initial) -> None:
self.query_comment: Optional[str] = initial
self.append = False
self.append: bool = False
def add(self, sql: str) -> str:
if not self.query_comment:
@@ -57,7 +57,7 @@ QueryStringFunc = Callable[[str, Optional[NodeWrapper]], str]
class MacroQueryStringSetter:
def __init__(self, config: AdapterRequiredConfig, manifest: Manifest):
def __init__(self, config: AdapterRequiredConfig, manifest: Manifest) -> None:
self.manifest = manifest
self.config = config

View File

@@ -1,6 +1,6 @@
from collections.abc import Hashable
from dataclasses import dataclass, field
from typing import Optional, TypeVar, Any, Type, Dict, Iterator, Tuple, Set
from typing import Optional, TypeVar, Any, Type, Dict, Iterator, Tuple, Set, Union, FrozenSet
from dbt.contracts.graph.nodes import SourceDefinition, ManifestNode, ResultNode, ParsedNode
from dbt.contracts.relation import (
@@ -23,6 +23,7 @@ import dbt.exceptions
Self = TypeVar("Self", bound="BaseRelation")
SerializableIterable = Union[Tuple, FrozenSet]
@dataclass(frozen=True, eq=False, repr=False)
@@ -36,6 +37,18 @@ class BaseRelation(FakeAPIObject, Hashable):
quote_policy: Policy = field(default_factory=lambda: Policy())
dbt_created: bool = False
# register relation types that can be renamed for the purpose of replacing relations using stages and backups
# adding a relation type here also requires defining the associated rename macro
# e.g. adding RelationType.View in dbt-postgres requires that you define:
# include/postgres/macros/relations/view/rename.sql::postgres__get_rename_view_sql()
renameable_relations: SerializableIterable = ()
# register relation types that are atomically replaceable, e.g. they have "create or replace" syntax
# adding a relation type here also requires defining the associated replace macro
# e.g. adding RelationType.View in dbt-postgres requires that you define:
# include/postgres/macros/relations/view/replace.sql::postgres__get_replace_view_sql()
replaceable_relations: SerializableIterable = ()
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
if self.dbt_created and self.quote_policy.get_part(field) is False:
return self.path.get_lowered_part(field) == value.lower()
@@ -169,7 +182,6 @@ class BaseRelation(FakeAPIObject, Hashable):
return self.include(identifier=False).replace_path(identifier=None)
def _render_iterator(self) -> Iterator[Tuple[Optional[ComponentName], Optional[str]]]:
for key in ComponentName:
path_part: Optional[str] = None
if self.include_policy.get_part(key):
@@ -286,6 +298,14 @@ class BaseRelation(FakeAPIObject, Hashable):
)
return cls.from_dict(kwargs)
@property
def can_be_renamed(self) -> bool:
return self.type in self.renameable_relations
@property
def can_be_replaced(self) -> bool:
return self.type in self.replaceable_relations
def __repr__(self) -> str:
return "<{} {}>".format(self.__class__.__name__, self.render())
@@ -439,11 +459,11 @@ class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
self[key].add(schema)
def search(self) -> Iterator[Tuple[InformationSchema, Optional[str]]]:
for information_schema_name, schemas in self.items():
for information_schema, schemas in self.items():
for schema in schemas:
yield information_schema_name, schema
yield information_schema, schema
def flatten(self, allow_multiple_databases: bool = False):
def flatten(self, allow_multiple_databases: bool = False) -> "SchemaSearchMap":
new = self.__class__()
# make sure we don't have multiple databases if allow_multiple_databases is set to False

View File

@@ -38,8 +38,8 @@ class _CachedRelation:
:attr BaseRelation inner: The underlying dbt relation.
"""
def __init__(self, inner):
self.referenced_by = {}
def __init__(self, inner) -> None:
self.referenced_by: Dict[_ReferenceKey, _CachedRelation] = {}
self.inner = inner
def __str__(self) -> str:

View File

@@ -0,0 +1,52 @@
from dataclasses import dataclass
from enum import Enum
from typing import Optional, DefaultDict, Mapping
class Capability(str, Enum):
"""Enumeration of optional adapter features which can be probed using BaseAdapter.has_feature()"""
SchemaMetadataByRelations = "SchemaMetadataByRelations"
"""Indicates efficient support for retrieving schema metadata for a list of relations, rather than always retrieving
all the relations in a schema."""
TableLastModifiedMetadata = "TableLastModifiedMetadata"
"""Indicates support for determining the time of the last table modification by querying database metadata."""
class Support(str, Enum):
Unknown = "Unknown"
"""The adapter has not declared whether this capability is a feature of the underlying DBMS."""
Unsupported = "Unsupported"
"""This capability is not possible with the underlying DBMS, so the adapter does not implement related macros."""
NotImplemented = "NotImplemented"
"""This capability is available in the underlying DBMS, but support has not yet been implemented in the adapter."""
Versioned = "Versioned"
"""Some versions of the DBMS supported by the adapter support this capability and the adapter has implemented any
macros needed to use it."""
Full = "Full"
"""All versions of the DBMS supported by the adapter support this capability and the adapter has implemented any
macros needed to use it."""
@dataclass
class CapabilitySupport:
support: Support
first_version: Optional[str] = None
def __bool__(self):
return self.support == Support.Versioned or self.support == Support.Full
class CapabilityDict(DefaultDict[Capability, CapabilitySupport]):
def __init__(self, vals: Mapping[Capability, CapabilitySupport]):
super().__init__(self._default)
self.update(vals)
@staticmethod
def _default():
return CapabilitySupport(support=Support.Unknown)

View File

@@ -19,7 +19,7 @@ Adapter = AdapterProtocol
class AdapterContainer:
def __init__(self):
def __init__(self) -> None:
self.lock = threading.Lock()
self.adapters: Dict[str, Adapter] = {}
self.plugins: Dict[str, AdapterPlugin] = {}

View File

@@ -90,7 +90,7 @@ class AdapterProtocol( # type: ignore[misc]
ConnectionManager: Type[ConnectionManager_T]
connections: ConnectionManager_T
def __init__(self, config: AdapterRequiredConfig):
def __init__(self, config: AdapterRequiredConfig) -> None:
...
@classmethod

View File

@@ -1,6 +1,6 @@
import abc
import time
from typing import List, Optional, Tuple, Any, Iterable, Dict, Union
from typing import List, Optional, Tuple, Any, Iterable, Dict
import agate
@@ -131,14 +131,6 @@ class SQLConnectionManager(BaseConnectionManager):
return dbt.clients.agate_helper.table_from_data_flat(data, column_names)
@classmethod
def data_type_code_to_name(cls, type_code: Union[int, str]) -> str:
"""Get the string representation of the data type from the type_code."""
# https://peps.python.org/pep-0249/#type-objects
raise dbt.exceptions.NotImplementedError(
"`data_type_code_to_name` is not implemented for this adapter!"
)
def execute(
self, sql: str, auto_begin: bool = False, fetch: bool = False, limit: Optional[int] = None
) -> Tuple[AdapterResponse, agate.Table]:

View File

@@ -75,6 +75,10 @@ class SQLAdapter(BaseAdapter):
decimals = agate_table.aggregate(agate.MaxPrecision(col_idx)) # type: ignore[attr-defined]
return "float8" if decimals else "integer"
@classmethod
def convert_integer_type(cls, agate_table: agate.Table, col_idx: int) -> str:
return "integer"
@classmethod
def convert_boolean_type(cls, agate_table: agate.Table, col_idx: int) -> str:
return "boolean"

View File

@@ -24,6 +24,7 @@ if os.name != "nt":
FLAGS_DEFAULTS = {
"INDIRECT_SELECTION": "eager",
"TARGET_PATH": None,
"WARN_ERROR": None,
# Cli args without user_config or env var option.
"FULL_REFRESH": False,
"STRICT_MODE": False,
@@ -57,11 +58,10 @@ def args_to_context(args: List[str]) -> Context:
from dbt.cli.main import cli
cli_ctx = cli.make_context(cli.name, args)
# Split args if they're a comma seperated string.
# Split args if they're a comma separated string.
if len(args) == 1 and "," in args[0]:
args = args[0].split(",")
sub_command_name, sub_command, args = cli.resolve_command(cli_ctx, args)
# Handle source and docs group.
if isinstance(sub_command, Group):
sub_command_name, sub_command, args = sub_command.resolve_command(cli_ctx, args)
@@ -79,7 +79,6 @@ class Flags:
def __init__(
self, ctx: Optional[Context] = None, user_config: Optional[UserConfig] = None
) -> None:
# Set the default flags.
for key, value in FLAGS_DEFAULTS.items():
object.__setattr__(self, key, value)
@@ -121,7 +120,6 @@ class Flags:
# respected over DBT_PRINT or --print.
new_name: Union[str, None] = None
if param_name in DEPRECATED_PARAMS:
# Deprecated env vars can only be set via env var.
# We use the deprecated option in click to serialize the value
# from the env var string.
@@ -316,10 +314,8 @@ def command_params(command: CliCommand, args_dict: Dict[str, Any]) -> CommandPar
default_args = set([x.lower() for x in FLAGS_DEFAULTS.keys()])
res = command.to_list()
for k, v in args_dict.items():
k = k.lower()
# if a "which" value exists in the args dict, it should match the command provided
if k == WHICH_KEY:
if v != command.value:
@@ -329,7 +325,9 @@ def command_params(command: CliCommand, args_dict: Dict[str, Any]) -> CommandPar
continue
# param was assigned from defaults and should not be included
if k not in (cmd_args | prnt_args) - default_args:
if k not in (cmd_args | prnt_args) or (
k in default_args and v == FLAGS_DEFAULTS[k.upper()]
):
continue
# if the param is in parent args, it should come before the arg name
@@ -342,9 +340,14 @@ def command_params(command: CliCommand, args_dict: Dict[str, Any]) -> CommandPar
spinal_cased = k.replace("_", "-")
# MultiOption flags come back as lists, but we want to pass them as space separated strings
if isinstance(v, list):
v = " ".join(v)
if k == "macro" and command == CliCommand.RUN_OPERATION:
add_fn(v)
elif v in (None, False):
# None is a Singleton, False is a Flyweight, only one instance of each.
elif v is None or v is False:
add_fn(f"--no-{spinal_cased}")
elif v is True:
add_fn(f"--{spinal_cased}")

View File

@@ -1,3 +1,4 @@
import functools
from copy import copy
from dataclasses import dataclass
from typing import Callable, List, Optional, Union
@@ -64,7 +65,7 @@ class dbtRunner:
self,
manifest: Optional[Manifest] = None,
callbacks: Optional[List[Callable[[EventMsg], None]]] = None,
):
) -> None:
self.manifest = manifest
if callbacks is None:
@@ -118,6 +119,44 @@ class dbtRunner:
)
# approach from https://github.com/pallets/click/issues/108#issuecomment-280489786
def global_flags(func):
@p.cache_selected_only
@p.debug
@p.deprecated_print
@p.enable_legacy_logger
@p.fail_fast
@p.log_cache_events
@p.log_file_max_bytes
@p.log_format_file
@p.log_level
@p.log_level_file
@p.log_path
@p.macro_debugging
@p.partial_parse
@p.partial_parse_file_path
@p.partial_parse_file_diff
@p.populate_cache
@p.print
@p.printer_width
@p.quiet
@p.record_timing_info
@p.send_anonymous_usage_stats
@p.single_threaded
@p.static_parser
@p.use_colors
@p.use_colors_file
@p.use_experimental_parser
@p.version
@p.version_check
@p.write_json
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# dbt
@click.group(
context_settings={"help_option_names": ["-h", "--help"]},
@@ -126,36 +165,11 @@ class dbtRunner:
epilog="Specify one of these sub-commands and you can find more help from there.",
)
@click.pass_context
@p.cache_selected_only
@p.debug
@p.deprecated_print
@p.enable_legacy_logger
@p.fail_fast
@p.log_cache_events
@p.log_format
@p.log_format_file
@p.log_level
@p.log_level_file
@p.log_path
@p.macro_debugging
@p.partial_parse
@p.partial_parse_file_path
@p.populate_cache
@p.print
@p.printer_width
@p.quiet
@p.record_timing_info
@p.send_anonymous_usage_stats
@p.single_threaded
@p.static_parser
@p.use_colors
@p.use_colors_file
@p.use_experimental_parser
@p.version
@p.version_check
@global_flags
@p.warn_error
@p.warn_error_options
@p.write_json
@p.log_format
@p.show_resource_report
def cli(ctx, **kwargs):
"""An ELT tool for managing your SQL transformations and data models.
For more documentation on these commands, visit: docs.getdbt.com
@@ -165,13 +179,14 @@ def cli(ctx, **kwargs):
# dbt build
@cli.command("build")
@click.pass_context
@global_flags
@p.defer
@p.deprecated_defer
@p.exclude
@p.fail_fast
@p.favor_state
@p.deprecated_favor_state
@p.full_refresh
@p.include_saved_query
@p.indirect_selection
@p.profile
@p.profiles_dir
@@ -188,7 +203,6 @@ def cli(ctx, **kwargs):
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -211,6 +225,8 @@ def build(ctx, **kwargs):
# dbt clean
@cli.command("clean")
@click.pass_context
@global_flags
@p.clean_project_files_only
@p.profile
@p.profiles_dir
@p.project_dir
@@ -233,6 +249,7 @@ def clean(ctx, **kwargs):
# dbt docs
@cli.group()
@click.pass_context
@global_flags
def docs(ctx, **kwargs):
"""Generate or serve the documentation website for your project"""
@@ -240,6 +257,7 @@ def docs(ctx, **kwargs):
# dbt docs generate
@docs.command("generate")
@click.pass_context
@global_flags
@p.compile_docs
@p.defer
@p.deprecated_defer
@@ -252,6 +270,7 @@ def docs(ctx, **kwargs):
@p.select
@p.selector
@p.empty_catalog
@p.static
@p.state
@p.defer_state
@p.deprecated_state
@@ -259,7 +278,6 @@ def docs(ctx, **kwargs):
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -282,6 +300,7 @@ def docs_generate(ctx, **kwargs):
# dbt docs serve
@docs.command("serve")
@click.pass_context
@global_flags
@p.browser
@p.port
@p.profile
@@ -310,6 +329,7 @@ def docs_serve(ctx, **kwargs):
# dbt compile
@cli.command("compile")
@click.pass_context
@global_flags
@p.defer
@p.deprecated_defer
@p.exclude
@@ -328,11 +348,11 @@ def docs_serve(ctx, **kwargs):
@p.state
@p.defer_state
@p.deprecated_state
@p.compile_inject_ephemeral_ctes
@p.target
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -356,6 +376,7 @@ def compile(ctx, **kwargs):
# dbt show
@cli.command("show")
@click.pass_context
@global_flags
@p.defer
@p.deprecated_defer
@p.exclude
@@ -379,7 +400,6 @@ def compile(ctx, **kwargs):
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -403,6 +423,7 @@ def show(ctx, **kwargs):
# dbt debug
@cli.command("debug")
@click.pass_context
@global_flags
@p.debug_connection
@p.config_dir
@p.profile
@@ -410,7 +431,6 @@ def show(ctx, **kwargs):
@p.project_dir
@p.target
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
def debug(ctx, **kwargs):
@@ -429,18 +449,46 @@ def debug(ctx, **kwargs):
# dbt deps
@cli.command("deps")
@click.pass_context
@global_flags
@p.profile
@p.profiles_dir_exists_false
@p.project_dir
@p.target
@p.vars
@p.source
@p.dry_run
@p.lock
@p.upgrade
@p.add_package
@requires.postflight
@requires.preflight
@requires.unset_profile
@requires.project
def deps(ctx, **kwargs):
"""Pull the most recent version of the dependencies listed in packages.yml"""
task = DepsTask(ctx.obj["flags"], ctx.obj["project"])
"""Install dbt packages specified.
In the following case, a new `package-lock.yml` will be generated and the packages are installed:
- user updated the packages.yml
- user specify the flag --update, which means for packages that are specified as a
range, dbt-core will try to install the newer version
Otherwise, deps will use `package-lock.yml` as source of truth to install packages.
There is a way to add new packages by providing an `--add-package` flag to deps command
which will allow user to specify a package they want to add in the format of packagename@version.
"""
flags = ctx.obj["flags"]
if flags.ADD_PACKAGE:
if not flags.ADD_PACKAGE["version"] and flags.SOURCE != "local":
raise BadOptionUsage(
message=f"Version is required in --add-package when a package when source is {flags.SOURCE}",
option_name="--add-package",
)
else:
if flags.DRY_RUN:
raise BadOptionUsage(
message="Invalid flag `--dry-run` when not using `--add-package`.",
option_name="--dry-run",
)
task = DepsTask(flags, ctx.obj["project"])
results = task.run()
success = task.interpret_results(results)
return results, success
@@ -449,6 +497,7 @@ def deps(ctx, **kwargs):
# dbt init
@cli.command("init")
@click.pass_context
@global_flags
# for backwards compatibility, accept 'project_name' as an optional positional argument
@click.argument("project_name", required=False)
@p.profile
@@ -471,6 +520,7 @@ def init(ctx, **kwargs):
# dbt list
@cli.command("list")
@click.pass_context
@global_flags
@p.exclude
@p.indirect_selection
@p.models
@@ -516,6 +566,7 @@ cli.add_command(ls, "ls")
# dbt parse
@cli.command("parse")
@click.pass_context
@global_flags
@p.profile
@p.profiles_dir
@p.project_dir
@@ -523,7 +574,6 @@ cli.add_command(ls, "ls")
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -533,19 +583,18 @@ cli.add_command(ls, "ls")
def parse(ctx, **kwargs):
"""Parses the project and provides information on performance"""
# manifest generation and writing happens in @requires.manifest
return ctx.obj["manifest"], True
# dbt run
@cli.command("run")
@click.pass_context
@global_flags
@p.defer
@p.deprecated_defer
@p.favor_state
@p.deprecated_favor_state
@p.exclude
@p.fail_fast
@p.full_refresh
@p.profile
@p.profiles_dir
@@ -559,7 +608,6 @@ def parse(ctx, **kwargs):
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -582,6 +630,7 @@ def run(ctx, **kwargs):
# dbt retry
@cli.command("retry")
@click.pass_context
@global_flags
@p.project_dir
@p.profiles_dir
@p.vars
@@ -589,19 +638,18 @@ def run(ctx, **kwargs):
@p.target
@p.state
@p.threads
@p.fail_fast
@p.full_refresh
@requires.postflight
@requires.preflight
@requires.profile
@requires.project
@requires.runtime_config
@requires.manifest
def retry(ctx, **kwargs):
"""Retry the nodes that failed in the previous run."""
# Retry will parse manifest inside the task after we consolidate the flags
task = RetryTask(
ctx.obj["flags"],
ctx.obj["runtime_config"],
ctx.obj["manifest"],
)
results = task.run()
@@ -612,6 +660,7 @@ def retry(ctx, **kwargs):
# dbt clone
@cli.command("clone")
@click.pass_context
@global_flags
@p.defer_state
@p.exclude
@p.full_refresh
@@ -626,7 +675,6 @@ def retry(ctx, **kwargs):
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.preflight
@requires.profile
@requires.project
@@ -649,6 +697,7 @@ def clone(ctx, **kwargs):
# dbt run operation
@cli.command("run-operation")
@click.pass_context
@global_flags
@click.argument("macro")
@p.args
@p.profile
@@ -680,6 +729,7 @@ def run_operation(ctx, **kwargs):
# dbt seed
@cli.command("seed")
@click.pass_context
@global_flags
@p.exclude
@p.full_refresh
@p.profile
@@ -695,7 +745,6 @@ def run_operation(ctx, **kwargs):
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile
@@ -717,6 +766,7 @@ def seed(ctx, **kwargs):
# dbt snapshot
@cli.command("snapshot")
@click.pass_context
@global_flags
@p.defer
@p.deprecated_defer
@p.exclude
@@ -756,6 +806,7 @@ def snapshot(ctx, **kwargs):
# dbt source
@cli.group()
@click.pass_context
@global_flags
def source(ctx, **kwargs):
"""Manage your project's sources"""
@@ -763,6 +814,7 @@ def source(ctx, **kwargs):
# dbt source freshness
@source.command("freshness")
@click.pass_context
@global_flags
@p.exclude
@p.output_path # TODO: Is this ok to re-use? We have three different output params, how much can we consolidate?
@p.profile
@@ -805,10 +857,10 @@ cli.commands["source"].add_command(snapshot_freshness, "snapshot-freshness") #
# dbt test
@cli.command("test")
@click.pass_context
@global_flags
@p.defer
@p.deprecated_defer
@p.exclude
@p.fail_fast
@p.favor_state
@p.deprecated_favor_state
@p.indirect_selection
@@ -825,7 +877,6 @@ cli.commands["source"].add_command(snapshot_freshness, "snapshot-freshness") #
@p.target_path
@p.threads
@p.vars
@p.version_check
@requires.postflight
@requires.preflight
@requires.profile

View File

@@ -22,6 +22,26 @@ class YAML(ParamType):
self.fail(f"String '{value}' is not valid YAML", param, ctx)
class Package(ParamType):
"""The Click STRING type. Converts string into dict with package name and version.
Example package:
package-name@1.0.0
package-name
"""
name = "NewPackage"
def convert(self, value, param, ctx):
# assume non-string values are a problem
if not isinstance(value, str):
self.fail(f"Cannot load Package from type {type(value)}", param, ctx)
try:
package_name, package_version = value.split("@")
return {"name": package_name, "version": package_version}
except ValueError:
return {"name": value, "version": None}
class WarnErrorOptionsType(YAML):
"""The Click WarnErrorOptions type. Converts YAML strings into objects."""

View File

@@ -2,19 +2,21 @@ import click
import inspect
import typing as t
from click import Context
from click.parser import OptionParser, ParsingState
from dbt.cli.option_types import ChoiceTuple
# 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)
class MultiOption(click.Option):
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
self.save_other_options = kwargs.pop("save_other_options", True)
nargs = kwargs.pop("nargs", -1)
assert nargs == -1, "nargs, if set, must be -1 not {}".format(nargs)
super(MultiOption, self).__init__(*args, **kwargs)
self._previous_parser_process = None
self._eat_all_parser = None
# this makes mypy happy, setting these to None causes mypy failures
self._previous_parser_process = lambda *args, **kwargs: None
self._eat_all_parser = lambda *args, **kwargs: None
# validate that multiple=True
multiple = kwargs.pop("multiple", None)
@@ -29,34 +31,35 @@ class MultiOption(click.Option):
else:
assert isinstance(option_type, ChoiceTuple), msg
def add_to_parser(self, parser, ctx):
def parser_process(value, state):
def add_to_parser(self, parser: OptionParser, ctx: Context):
def parser_process(value: str, state: ParsingState):
# method to hook to the parser.process
done = False
value = [value]
value_list = str.split(value, " ")
if self.save_other_options:
# grab everything up to the next option
while state.rargs and not done:
for prefix in self._eat_all_parser.prefixes:
for prefix in self._eat_all_parser.prefixes: # type: ignore[attr-defined]
if state.rargs[0].startswith(prefix):
done = True
if not done:
value.append(state.rargs.pop(0))
value_list.append(state.rargs.pop(0))
else:
# grab everything remaining
value += state.rargs
value_list += state.rargs
state.rargs[:] = []
value = tuple(value)
value_tuple = tuple(value_list)
# call the actual process
self._previous_parser_process(value, state)
self._previous_parser_process(value_tuple, state)
retval = super(MultiOption, self).add_to_parser(parser, ctx)
for name in self.opts:
our_parser = parser._long_opt.get(name) or parser._short_opt.get(name)
if our_parser:
self._eat_all_parser = our_parser
self._eat_all_parser = our_parser # type: ignore[assignment]
self._previous_parser_process = our_parser.process
our_parser.process = parser_process
# mypy doesnt like assingment to a method see https://github.com/python/mypy/issues/708
our_parser.process = parser_process # type: ignore[method-assign]
break
return retval

View File

@@ -2,10 +2,16 @@ from pathlib import Path
import click
from dbt.cli.options import MultiOption
from dbt.cli.option_types import YAML, ChoiceTuple, WarnErrorOptionsType
from dbt.cli.option_types import YAML, ChoiceTuple, WarnErrorOptionsType, Package
from dbt.cli.resolvers import default_project_dir, default_profiles_dir
from dbt.version import get_version_information
add_package = click.option(
"--add-package",
help="Add a package to current package spec, specify it as package-name@version. Change the source with --source flag.",
envvar=None,
type=Package(),
)
args = click.option(
"--args",
envvar=None,
@@ -40,6 +46,14 @@ compile_docs = click.option(
default=True,
)
compile_inject_ephemeral_ctes = click.option(
"--inject-ephemeral-ctes/--no-inject-ephemeral-ctes",
envvar=None,
help="Internal flag controlling injection of referenced ephemeral models' CTEs during `compile`.",
hidden=True,
default=True,
)
config_dir = click.option(
"--config-dir",
envvar=None,
@@ -69,6 +83,14 @@ deprecated_defer = click.option(
hidden=True,
)
dry_run = click.option(
"--dry-run",
envvar=None,
help="Option to run `dbt deps --add-package` without updating package-lock.yml file.",
is_flag=True,
)
enable_legacy_logger = click.option(
"--enable-legacy-logger/--no-enable-legacy-logger",
envvar="DBT_ENABLE_LEGACY_LOGGER",
@@ -119,6 +141,13 @@ indirect_selection = click.option(
default="eager",
)
lock = click.option(
"--lock",
envvar=None,
help="Generate the package-lock.yml file without install the packages.",
is_flag=True,
)
log_cache_events = click.option(
"--log-cache-events/--no-log-cache-events",
help="Enable verbose logging for relational cache events to help when debugging.",
@@ -171,6 +200,15 @@ use_colors_file = click.option(
default=True,
)
log_file_max_bytes = click.option(
"--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.",
default=10 * 1024 * 1024, # 10mb
type=click.INT,
hidden=True,
)
log_path = click.option(
"--log-path",
envvar="DBT_LOG_PATH",
@@ -248,6 +286,14 @@ partial_parse_file_path = click.option(
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
)
partial_parse_file_diff = click.option(
"--partial-parse-file-diff/--no-partial-parse-file-diff",
envvar="DBT_PARTIAL_PARSE_FILE_DIFF",
help="Internal flag for whether to compute a file diff during partial parsing.",
hidden=True,
default=True,
)
populate_cache = click.option(
"--populate-cache/--no-populate-cache",
envvar="DBT_POPULATE_CACHE",
@@ -290,7 +336,7 @@ printer_width = click.option(
profile = click.option(
"--profile",
envvar=None,
help="Which 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(
@@ -343,6 +389,7 @@ resource_type = click.option(
type=ChoiceTuple(
[
"metric",
"semantic_model",
"source",
"analysis",
"model",
@@ -360,6 +407,14 @@ resource_type = click.option(
default=(),
)
include_saved_query = click.option(
"--include-saved-query/--no-include-saved-query",
envvar="DBT_INCLUDE_SAVED_QUERY",
help="Include saved queries in the list of resources to be selected for build command",
is_flag=True,
hidden=True,
)
model_decls = ("-m", "--models", "--model")
select_decls = ("-s", "--select")
select_attrs = {
@@ -380,9 +435,9 @@ inline = click.option(
# 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.
# See https://github.com/dbt-labs/dbt-core/pull/6774#issuecomment-1408476095 for more info.
models = click.option(*model_decls, **select_attrs)
raw_select = click.option(*select_decls, **select_attrs)
select = click.option(*select_decls, *model_decls, **select_attrs)
models = click.option(*model_decls, **select_attrs) # type: ignore[arg-type]
raw_select = click.option(*select_decls, **select_attrs) # type: ignore[arg-type]
select = click.option(*select_decls, *model_decls, **select_attrs) # type: ignore[arg-type]
selector = click.option(
"--selector",
@@ -397,6 +452,13 @@ send_anonymous_usage_stats = click.option(
default=True,
)
clean_project_files_only = click.option(
"--clean-project-files-only / --no-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.",
default=True,
)
show = click.option(
"--show",
envvar=None,
@@ -432,6 +494,21 @@ empty_catalog = click.option(
is_flag=True,
)
source = click.option(
"--source",
envvar=None,
help="Source to download page from, must be one of hub, git, or local. Defaults to hub.",
type=click.Choice(["hub", "git", "local"], case_sensitive=True),
default="hub",
)
static = click.option(
"--static",
help="Generate an additional static_index.html with manifest and catalog built-in.",
default=False,
is_flag=True,
)
state = click.option(
"--state",
envvar="DBT_STATE",
@@ -500,6 +577,13 @@ target_path = click.option(
type=click.Path(),
)
upgrade = click.option(
"--upgrade",
envvar=None,
help="Upgrade packages to the latest version.",
is_flag=True,
)
debug_connection = click.option(
"--connection",
envvar=None,
@@ -581,3 +665,10 @@ write_json = click.option(
help="Whether or not to write the manifest.json and run_results.json files to the target directory",
default=True,
)
show_resource_report = click.option(
"--show-resource-report/--no-show-resource-report",
default=False,
envvar="DBT_SHOW_RESOURCE_REPORT",
hidden=True,
)

View File

@@ -1,6 +1,6 @@
import dbt.tracking
from dbt.version import installed as installed_version
from dbt.adapters.factory import adapter_management, register_adapter
from dbt.adapters.factory import adapter_management, register_adapter, get_adapter
from dbt.flags import set_flags, get_flag_dict
from dbt.cli.exceptions import (
ExceptionExit,
@@ -9,24 +9,28 @@ from dbt.cli.exceptions import (
from dbt.cli.flags import Flags
from dbt.config import RuntimeConfig
from dbt.config.runtime import load_project, load_profile, UnsetProfile
from dbt.events.base_types import EventLevel
from dbt.events.functions import fire_event, LOG_VERSION, set_invocation_id, setup_event_logger
from dbt.events.types import (
CommandCompleted,
MainReportVersion,
MainReportArgs,
MainTrackingUserState,
ResourceReport,
)
from dbt.events.helpers import get_json_string_utcnow
from dbt.events.types import MainEncounteredError, MainStackTrace
from dbt.exceptions import Exception as DbtException, DbtProjectError, FailFastError
from dbt.parser.manifest import ManifestLoader, write_manifest
from dbt.parser.manifest import parse_manifest
from dbt.profiler import profiler
from dbt.tracking import active_user, initialize_from_flags, track_run
from dbt.utils import cast_dict_to_dict_of_strings
from dbt.plugins import set_up_plugin_manager, get_plugin_manager
from dbt.plugins import set_up_plugin_manager
from click import Context
from functools import update_wrapper
import importlib.util
import time
import traceback
@@ -96,6 +100,28 @@ def postflight(func):
fire_event(MainStackTrace(stack_trace=traceback.format_exc()))
raise ExceptionExit(e)
finally:
# Fire ResourceReport, but only on systems which support the resource
# module. (Skip it on Windows).
if importlib.util.find_spec("resource") is not None:
import resource
rusage = resource.getrusage(resource.RUSAGE_SELF)
fire_event(
ResourceReport(
command_name=ctx.command.name,
command_success=success,
command_wall_clock_time=time.perf_counter() - start_func,
process_user_time=rusage.ru_utime,
process_kernel_time=rusage.ru_stime,
process_mem_max_rss=rusage.ru_maxrss,
process_in_blocks=rusage.ru_inblock,
process_out_blocks=rusage.ru_oublock,
),
EventLevel.INFO
if "flags" in ctx.obj and ctx.obj["flags"].SHOW_RESOURCE_REPORT
else None,
)
fire_event(
CommandCompleted(
command=ctx.command_path,
@@ -239,23 +265,16 @@ def manifest(*args0, write=True, write_perf_info=False):
raise DbtProjectError("profile, project, and runtime_config required for manifest")
runtime_config = ctx.obj["runtime_config"]
register_adapter(runtime_config)
# a manifest has already been set on the context, so don't overwrite it
if ctx.obj.get("manifest") is None:
manifest = ManifestLoader.get_full_manifest(
runtime_config,
write_perf_info=write_perf_info,
ctx.obj["manifest"] = parse_manifest(
runtime_config, write_perf_info, write, ctx.obj["flags"].write_json
)
ctx.obj["manifest"] = manifest
if write and ctx.obj["flags"].write_json:
write_manifest(manifest, runtime_config.project_target_path)
pm = get_plugin_manager(runtime_config.project_name)
plugin_artifacts = pm.get_manifest_artifacts(manifest)
for path, plugin_artifact in plugin_artifacts.items():
plugin_artifact.write(path)
else:
register_adapter(runtime_config)
adapter = get_adapter(runtime_config)
adapter.connections.set_query_header(ctx.obj["manifest"])
return func(*args, **kwargs)
return update_wrapper(wrapper, func)

View File

@@ -9,10 +9,23 @@ from typing import Iterable, List, Dict, Union, Optional, Any
from dbt.exceptions import DbtRuntimeError
BOM = BOM_UTF8.decode("utf-8") # '\ufeff'
class Integer(agate.data_types.DataType):
def cast(self, d):
# by default agate will cast none as a Number
# but we need to cast it as an Integer to preserve
# the type when merging and unioning tables
if type(d) == int or d is None:
return d
else:
raise agate.exceptions.CastError('Can not parse value "%s" as Integer.' % d)
def jsonify(self, d):
return d
class Number(agate.data_types.Number):
# undo the change in https://github.com/wireservice/agate/pull/733
# i.e. do not cast True and False to numeric 1 and 0
@@ -48,6 +61,7 @@ def build_type_tester(
) -> agate.TypeTester:
types = [
Integer(null_values=("null", "")),
Number(null_values=("null", "")),
agate.data_types.Date(null_values=("null", ""), date_format="%Y-%m-%d"),
agate.data_types.DateTime(null_values=("null", ""), datetime_format="%Y-%m-%d %H:%M:%S"),
@@ -135,12 +149,12 @@ def as_matrix(table):
return [r.values() for r in table.rows.values()]
def from_csv(abspath, text_columns):
def from_csv(abspath, text_columns, delimiter=","):
type_tester = build_type_tester(text_columns=text_columns)
with open(abspath, encoding="utf-8") as fp:
if fp.read(1) != BOM:
fp.seek(0)
return agate.Table.from_csv(fp, column_types=type_tester)
return agate.Table.from_csv(fp, column_types=type_tester, delimiter=delimiter)
class _NullMarker:
@@ -151,7 +165,7 @@ NullableAgateType = Union[agate.data_types.DataType, _NullMarker]
class ColumnTypeBuilder(Dict[str, NullableAgateType]):
def __init__(self):
def __init__(self) -> None:
super().__init__()
def __setitem__(self, key, value):
@@ -166,6 +180,13 @@ class ColumnTypeBuilder(Dict[str, NullableAgateType]):
elif isinstance(value, _NullMarker):
# use the existing value
return
# when one table column is Number while another is Integer, force the column to Number on merge
elif isinstance(value, Integer) and isinstance(existing_type, agate.data_types.Number):
# use the existing value
return
elif isinstance(existing_type, Integer) and isinstance(value, agate.data_types.Number):
# overwrite
super().__setitem__(key, value)
elif not isinstance(value, type(existing_type)):
# actual type mismatch!
raise DbtRuntimeError(
@@ -177,8 +198,9 @@ class ColumnTypeBuilder(Dict[str, NullableAgateType]):
result: Dict[str, agate.data_types.DataType] = {}
for key, value in self.items():
if isinstance(value, _NullMarker):
# this is what agate would do.
result[key] = agate.data_types.Number()
# agate would make it a Number but we'll make it Integer so that if this column
# gets merged with another Integer column, it won't get forced to a Number
result[key] = Integer()
else:
result[key] = value
return result
@@ -218,3 +240,12 @@ def merge_tables(tables: List[agate.Table]) -> agate.Table:
rows.append(agate.Row(data, column_names))
# _is_fork to tell agate that we already made things into `Row`s.
return agate.Table(rows, column_names, column_types, _is_fork=True)
def get_column_value_uncased(column_name: str, row: agate.Row) -> Any:
"""Get the value of a column in this row, ignoring the casing of the column name."""
for key, value in row.items():
if key.casefold() == column_name.casefold():
return value
raise KeyError

View File

@@ -111,7 +111,7 @@ def checkout(cwd, repo, revision=None):
def get_current_sha(cwd):
out, err = run_cmd(cwd, ["git", "rev-parse", "HEAD"], env={"LC_ALL": "C"})
return out.decode("utf-8")
return out.decode("utf-8").strip()
def remove_remote(cwd):

View File

@@ -191,7 +191,7 @@ NativeSandboxEnvironment.template_class = NativeSandboxTemplate # type: ignore
class TemplateCache:
def __init__(self):
def __init__(self) -> None:
self.file_cache: Dict[str, jinja2.Template] = {}
def get_node_template(self, node) -> jinja2.Template:

View File

@@ -4,7 +4,6 @@ import json
import networkx as nx # type: ignore
import os
import pickle
import sqlparse
from collections import defaultdict
from typing import List, Dict, Any, Tuple, Optional
@@ -36,6 +35,7 @@ from dbt.node_types import NodeType, ModelLanguage
from dbt.events.format import pluralize
import dbt.tracking
import dbt.task.list as list_task
import sqlparse
graph_file_name = "graph.gpickle"
@@ -125,7 +125,7 @@ def _get_tests_for_node(manifest: Manifest, unique_id: UniqueID) -> List[UniqueI
class Linker:
def __init__(self, data=None):
def __init__(self, data=None) -> None:
if data is None:
data = {}
self.graph = nx.DiGraph(**data)
@@ -183,14 +183,16 @@ class Linker:
def link_graph(self, manifest: Manifest):
for source in manifest.sources.values():
self.add_node(source.unique_id)
for semantic_model in manifest.semantic_models.values():
self.add_node(semantic_model.unique_id)
for node in manifest.nodes.values():
self.link_node(node, manifest)
for semantic_model in manifest.semantic_models.values():
self.link_node(semantic_model, manifest)
for exposure in manifest.exposures.values():
self.link_node(exposure, manifest)
for metric in manifest.metrics.values():
self.link_node(metric, manifest)
for saved_query in manifest.saved_queries.values():
self.link_node(saved_query, manifest)
cycle = self.find_cycles()
@@ -274,7 +276,7 @@ class Linker:
class Compiler:
def __init__(self, config):
def __init__(self, config) -> None:
self.config = config
def initialize(self):
@@ -320,6 +322,10 @@ class Compiler:
if model.compiled_code is None:
raise DbtRuntimeError("Cannot inject ctes into an uncompiled node", model)
# tech debt: safe flag/arg access (#6259)
if not getattr(self.config.args, "inject_ephemeral_ctes", True):
return (model, [])
# extra_ctes_injected flag says that we've already recursively injected the ctes
if model.extra_ctes_injected:
return (model, model.extra_ctes)
@@ -378,16 +384,16 @@ class Compiler:
_add_prepended_cte(prepended_ctes, InjectedCTE(id=cte.id, sql=sql))
injected_sql = inject_ctes_into_sql(
model.compiled_code,
prepended_ctes,
)
# Check again before updating for multi-threading
if not model.extra_ctes_injected:
injected_sql = inject_ctes_into_sql(
model.compiled_code,
prepended_ctes,
)
model.extra_ctes_injected = True
model._pre_injected_sql = model.compiled_code
model.compiled_code = injected_sql
model.extra_ctes = prepended_ctes
model.extra_ctes_injected = True
# if model.extra_ctes is not set to prepended ctes, something went wrong
return model, model.extra_ctes
@@ -523,6 +529,12 @@ class Compiler:
the node's raw_code into compiled_code, and then calls the
recursive method to "prepend" the ctes.
"""
# Make sure Lexer for sqlparse 0.4.4 is initialized
from sqlparse.lexer import Lexer # type: ignore
if hasattr(Lexer, "get_default_instance"):
Lexer.get_default_instance()
node = self._compile_code(node, manifest, extra_context)
node, _ = self._recursively_prepend_ctes(node, manifest, extra_context)

View File

@@ -83,7 +83,7 @@ class Profile(HasCredentials):
user_config: UserConfig,
threads: int,
credentials: Credentials,
):
) -> None:
"""Explicitly defining `__init__` to work around bug in Python 3.9.7
https://bugs.python.org/issue45081
"""

View File

@@ -16,8 +16,12 @@ import os
from dbt.flags import get_flags
from dbt import deprecations
from dbt.constants import DEPENDENCIES_FILE_NAME, PACKAGES_FILE_NAME
from dbt.clients.system import path_exists, resolve_path_from_base, load_file_contents
from dbt.constants import (
DEPENDENCIES_FILE_NAME,
PACKAGES_FILE_NAME,
PACKAGE_LOCK_HASH_KEY,
)
from dbt.clients.system import path_exists, load_file_contents
from dbt.clients.yaml_helper import load_yaml_text
from dbt.contracts.connection import QueryComment
from dbt.exceptions import (
@@ -94,16 +98,17 @@ def _load_yaml(path):
return load_yaml_text(contents)
def package_and_project_data_from_root(project_root):
package_filepath = resolve_path_from_base(PACKAGES_FILE_NAME, project_root)
dependencies_filepath = resolve_path_from_base(DEPENDENCIES_FILE_NAME, project_root)
def load_yml_dict(file_path):
ret = {}
if path_exists(file_path):
ret = _load_yaml(file_path) or {}
return ret
packages_yml_dict = {}
dependencies_yml_dict = {}
if path_exists(package_filepath):
packages_yml_dict = _load_yaml(package_filepath) or {}
if path_exists(dependencies_filepath):
dependencies_yml_dict = _load_yaml(dependencies_filepath) or {}
def package_and_project_data_from_root(project_root):
packages_yml_dict = load_yml_dict(f"{project_root}/{PACKAGES_FILE_NAME}")
dependencies_yml_dict = load_yml_dict(f"{project_root}/{DEPENDENCIES_FILE_NAME}")
if "packages" in packages_yml_dict and "packages" in dependencies_yml_dict:
msg = "The 'packages' key cannot be specified in both packages.yml and dependencies.yml"
@@ -123,10 +128,21 @@ def package_and_project_data_from_root(project_root):
return packages_dict, packages_specified_path
def package_config_from_data(packages_data: Dict[str, Any]) -> PackageConfig:
def package_config_from_data(
packages_data: Dict[str, Any],
unrendered_packages_data: Optional[Dict[str, Any]] = None,
) -> PackageConfig:
if not packages_data:
packages_data = {"packages": []}
# this depends on the two lists being in the same order
if unrendered_packages_data:
unrendered_packages_data = deepcopy(unrendered_packages_data)
for i in range(0, len(packages_data.get("packages", []))):
packages_data["packages"][i]["unrendered"] = unrendered_packages_data["packages"][i]
if PACKAGE_LOCK_HASH_KEY in packages_data:
packages_data.pop(PACKAGE_LOCK_HASH_KEY)
try:
PackageConfig.validate(packages_data)
packages = PackageConfig.from_dict(packages_data)
@@ -319,7 +335,7 @@ class PartialProject(RenderComponents):
def render_package_metadata(self, renderer: PackageRenderer) -> ProjectPackageMetadata:
packages_data = renderer.render_data(self.packages_dict)
packages_config = package_config_from_data(packages_data)
packages_config = package_config_from_data(packages_data, self.packages_dict)
if not self.project_name:
raise DbtProjectError("Package dbt_project.yml must have a name!")
return ProjectPackageMetadata(self.project_name, packages_config.packages)
@@ -426,8 +442,11 @@ class PartialProject(RenderComponents):
sources: Dict[str, Any]
tests: Dict[str, Any]
metrics: Dict[str, Any]
semantic_models: Dict[str, Any]
saved_queries: Dict[str, Any]
exposures: Dict[str, Any]
vars_value: VarProvider
dbt_cloud: Dict[str, Any]
dispatch = cfg.dispatch
models = cfg.models
@@ -436,6 +455,8 @@ class PartialProject(RenderComponents):
sources = cfg.sources
tests = cfg.tests
metrics = cfg.metrics
semantic_models = cfg.semantic_models
saved_queries = cfg.saved_queries
exposures = cfg.exposures
if cfg.vars is None:
vars_dict: Dict[str, Any] = {}
@@ -449,8 +470,9 @@ class PartialProject(RenderComponents):
on_run_end: List[str] = value_or(cfg.on_run_end, [])
query_comment = _query_comment_from_cfg(cfg.query_comment)
packages: PackageConfig = package_config_from_data(rendered.packages_dict)
packages: PackageConfig = package_config_from_data(
rendered.packages_dict, unrendered.packages_dict
)
selectors = selector_config_from_data(rendered.selectors_dict)
manifest_selectors: Dict[str, Any] = {}
if rendered.selectors_dict and rendered.selectors_dict["selectors"]:
@@ -459,6 +481,8 @@ class PartialProject(RenderComponents):
manifest_selectors = SelectorDict.parse_from_selectors_list(
rendered.selectors_dict["selectors"]
)
dbt_cloud = cfg.dbt_cloud
project = Project(
project_name=name,
version=version,
@@ -492,12 +516,15 @@ class PartialProject(RenderComponents):
sources=sources,
tests=tests,
metrics=metrics,
semantic_models=semantic_models,
saved_queries=saved_queries,
exposures=exposures,
vars=vars_value,
config_version=cfg.config_version,
unrendered=unrendered,
project_env_vars=project_env_vars,
restrict_access=cfg.restrict_access,
dbt_cloud=dbt_cloud,
)
# sanity check - this means an internal issue
project.validate()
@@ -541,6 +568,7 @@ class PartialProject(RenderComponents):
packages_specified_path,
) = package_and_project_data_from_root(project_root)
selectors_dict = selector_data_from_root(project_root)
return cls.from_dicts(
project_root=project_root,
project_dict=project_dict,
@@ -598,6 +626,8 @@ class Project:
sources: Dict[str, Any]
tests: Dict[str, Any]
metrics: Dict[str, Any]
semantic_models: Dict[str, Any]
saved_queries: Dict[str, Any]
exposures: Dict[str, Any]
vars: VarProvider
dbt_version: List[VersionSpecifier]
@@ -609,6 +639,7 @@ class Project:
unrendered: RenderComponents
project_env_vars: Dict[str, Any]
restrict_access: bool
dbt_cloud: Dict[str, Any]
@property
def all_source_paths(self) -> List[str]:
@@ -673,11 +704,14 @@ class Project:
"sources": self.sources,
"tests": self.tests,
"metrics": self.metrics,
"semantic-models": self.semantic_models,
"saved-queries": self.saved_queries,
"exposures": self.exposures,
"vars": self.vars.to_dict(),
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
"config-version": self.config_version,
"restrict-access": self.restrict_access,
"dbt-cloud": self.dbt_cloud,
}
)
if self.query_comment:

View File

@@ -74,7 +74,7 @@ def _list_if_none_or_string(value):
class ProjectPostprocessor(Dict[Keypath, Callable[[Any], Any]]):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self[("on-run-start",)] = _list_if_none_or_string

View File

@@ -167,6 +167,8 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
sources=project.sources,
tests=project.tests,
metrics=project.metrics,
semantic_models=project.semantic_models,
saved_queries=project.saved_queries,
exposures=project.exposures,
vars=project.vars,
config_version=project.config_version,
@@ -182,6 +184,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
args=args,
cli_vars=cli_vars,
dependencies=dependencies,
dbt_cloud=project.dbt_cloud,
)
# Called by 'load_projects' in this class
@@ -322,6 +325,8 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
"sources": self._get_config_paths(self.sources),
"tests": self._get_config_paths(self.tests),
"metrics": self._get_config_paths(self.metrics),
"semantic_models": self._get_config_paths(self.semantic_models),
"saved_queries": self._get_config_paths(self.saved_queries),
"exposures": self._get_config_paths(self.exposures),
}
@@ -404,7 +409,7 @@ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
class UnsetCredentials(Credentials):
def __init__(self):
def __init__(self) -> None:
super().__init__("", "")
@property

View File

@@ -9,8 +9,11 @@ PIN_PACKAGE_URL = (
"https://docs.getdbt.com/docs/package-management#section-specifying-package-versions"
)
DBT_PROJECT_FILE_NAME = "dbt_project.yml"
PACKAGES_FILE_NAME = "packages.yml"
DEPENDENCIES_FILE_NAME = "dependencies.yml"
PACKAGE_LOCK_FILE_NAME = "package-lock.yml"
MANIFEST_FILE_NAME = "manifest.json"
SEMANTIC_MANIFEST_FILE_NAME = "semantic_manifest.json"
PARTIAL_PARSE_FILE_NAME = "partial_parse.msgpack"
PACKAGE_LOCK_HASH_KEY = "sha1_hash"

View File

@@ -1,6 +1,8 @@
from __future__ import annotations
import json
import os
from typing import Any, Dict, NoReturn, Optional, Mapping, Iterable, Set, List
from typing import Any, Callable, Dict, NoReturn, Optional, Mapping, Iterable, Set, List
import threading
from dbt.flags import get_flags
@@ -86,33 +88,29 @@ def get_context_modules() -> Dict[str, Dict[str, Any]]:
class ContextMember:
def __init__(self, value, name=None):
def __init__(self, value: Any, name: Optional[str] = None) -> None:
self.name = name
self.inner = value
def key(self, default):
def key(self, default: str) -> str:
if self.name is None:
return default
return self.name
def contextmember(value):
if isinstance(value, str):
return lambda v: ContextMember(v, name=value)
return ContextMember(value)
def contextmember(value: Optional[str] = None) -> Callable:
return lambda v: ContextMember(v, name=value)
def contextproperty(value):
if isinstance(value, str):
return lambda v: ContextMember(property(v), name=value)
return ContextMember(property(value))
def contextproperty(value: Optional[str] = None) -> Callable:
return lambda v: ContextMember(property(v), name=value)
class ContextMeta(type):
def __new__(mcls, name, bases, dct):
context_members = {}
context_attrs = {}
new_dct = {}
def __new__(mcls, name, bases, dct: Dict[str, Any]) -> ContextMeta:
context_members: Dict[str, Any] = {}
context_attrs: Dict[str, Any] = {}
new_dct: Dict[str, Any] = {}
for base in bases:
context_members.update(getattr(base, "_context_members_", {}))
@@ -148,27 +146,28 @@ class Var:
return self._cli_vars
@property
def node_name(self):
def node_name(self) -> str:
if self._node is not None:
return self._node.name
else:
return "<Configuration>"
def get_missing_var(self, var_name):
raise RequiredVarNotFoundError(var_name, self._merged, self._node)
def get_missing_var(self, var_name: str) -> NoReturn:
# TODO function name implies a non exception resolution
raise RequiredVarNotFoundError(var_name, dict(self._merged), self._node)
def has_var(self, var_name: str):
def has_var(self, var_name: str) -> bool:
return var_name in self._merged
def get_rendered_var(self, var_name):
def get_rendered_var(self, var_name: str) -> Any:
raw = self._merged[var_name]
# if bool/int/float/etc are passed in, don't compile anything
if not isinstance(raw, str):
return raw
return get_rendered(raw, self._context)
return get_rendered(raw, dict(self._context))
def __call__(self, var_name, default=_VAR_NOTSET):
def __call__(self, var_name: str, default: Any = _VAR_NOTSET) -> Any:
if self.has_var(var_name):
return self.get_rendered_var(var_name)
elif default is not self._VAR_NOTSET:
@@ -178,13 +177,17 @@ class Var:
class BaseContext(metaclass=ContextMeta):
# subclass is TargetContext
def __init__(self, cli_vars):
self._ctx = {}
self.cli_vars = cli_vars
self.env_vars = {}
# Set by ContextMeta
_context_members_: Dict[str, Any]
_context_attrs_: Dict[str, Any]
def generate_builtins(self):
# subclass is TargetContext
def __init__(self, cli_vars: Dict[str, Any]) -> None:
self._ctx: Dict[str, Any] = {}
self.cli_vars: Dict[str, Any] = cli_vars
self.env_vars: Dict[str, Any] = {}
def generate_builtins(self) -> Dict[str, Any]:
builtins: Dict[str, Any] = {}
for key, value in self._context_members_.items():
if hasattr(value, "__get__"):
@@ -194,14 +197,14 @@ class BaseContext(metaclass=ContextMeta):
return builtins
# no dbtClassMixin so this is not an actual override
def to_dict(self):
def to_dict(self) -> Dict[str, Any]:
self._ctx["context"] = self._ctx
builtins = self.generate_builtins()
self._ctx["builtins"] = builtins
self._ctx.update(builtins)
return self._ctx
@contextproperty
@contextproperty()
def dbt_version(self) -> str:
"""The `dbt_version` variable returns the installed version of dbt that
is currently running. It can be used for debugging or auditing
@@ -221,7 +224,7 @@ class BaseContext(metaclass=ContextMeta):
"""
return dbt_version
@contextproperty
@contextproperty()
def var(self) -> Var:
"""Variables can be passed from your `dbt_project.yml` file into models
during compilation. These variables are useful for configuring packages
@@ -290,7 +293,7 @@ class BaseContext(metaclass=ContextMeta):
"""
return Var(self._ctx, self.cli_vars)
@contextmember
@contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str:
"""The env_var() function. Return the environment variable named 'var'.
If there is no such environment variable set, return the default.
@@ -318,7 +321,7 @@ class BaseContext(metaclass=ContextMeta):
if os.environ.get("DBT_MACRO_DEBUGGING"):
@contextmember
@contextmember()
@staticmethod
def debug():
"""Enter a debugger at this line in the compiled jinja code."""
@@ -357,7 +360,7 @@ class BaseContext(metaclass=ContextMeta):
"""
raise MacroReturn(data)
@contextmember
@contextmember()
@staticmethod
def fromjson(string: str, default: Any = None) -> Any:
"""The `fromjson` context method can be used to deserialize a json
@@ -378,7 +381,7 @@ class BaseContext(metaclass=ContextMeta):
except ValueError:
return default
@contextmember
@contextmember()
@staticmethod
def tojson(value: Any, default: Any = None, sort_keys: bool = False) -> Any:
"""The `tojson` context method can be used to serialize a Python
@@ -401,7 +404,7 @@ class BaseContext(metaclass=ContextMeta):
except ValueError:
return default
@contextmember
@contextmember()
@staticmethod
def fromyaml(value: str, default: Any = None) -> Any:
"""The fromyaml context method can be used to deserialize a yaml string
@@ -432,7 +435,7 @@ class BaseContext(metaclass=ContextMeta):
# safe_dump defaults to sort_keys=True, but we act like json.dumps (the
# opposite)
@contextmember
@contextmember()
@staticmethod
def toyaml(
value: Any, default: Optional[str] = None, sort_keys: bool = False
@@ -477,7 +480,7 @@ class BaseContext(metaclass=ContextMeta):
except TypeError:
return default
@contextmember
@contextmember()
@staticmethod
def set_strict(value: Iterable[Any]) -> Set[Any]:
"""The `set_strict` context method can be used to convert any iterable
@@ -519,7 +522,7 @@ class BaseContext(metaclass=ContextMeta):
except TypeError:
return default
@contextmember
@contextmember()
@staticmethod
def zip_strict(*args: Iterable[Any]) -> Iterable[Any]:
"""The `zip_strict` context method can be used to used to return
@@ -541,7 +544,7 @@ class BaseContext(metaclass=ContextMeta):
except TypeError as e:
raise ZipStrictWrongTypeError(e)
@contextmember
@contextmember()
@staticmethod
def log(msg: str, info: bool = False) -> str:
"""Logs a line to either the log file or stdout.
@@ -562,7 +565,7 @@ class BaseContext(metaclass=ContextMeta):
fire_event(JinjaLogDebug(msg=msg, node_info=get_node_info()))
return ""
@contextproperty
@contextproperty()
def run_started_at(self) -> Optional[datetime.datetime]:
"""`run_started_at` outputs the timestamp that this run started, e.g.
`2017-04-21 01:23:45.678`. The `run_started_at` variable is a Python
@@ -590,19 +593,19 @@ class BaseContext(metaclass=ContextMeta):
else:
return None
@contextproperty
@contextproperty()
def invocation_id(self) -> Optional[str]:
"""invocation_id outputs a UUID generated for this dbt run (useful for
auditing)
"""
return get_invocation_id()
@contextproperty
@contextproperty()
def thread_id(self) -> str:
"""thread_id outputs an ID for the current thread (useful for auditing)"""
return threading.current_thread().name
@contextproperty
@contextproperty()
def modules(self) -> Dict[str, Any]:
"""The `modules` variable in the Jinja context contains useful Python
modules for operating on data.
@@ -627,7 +630,7 @@ class BaseContext(metaclass=ContextMeta):
""" # noqa
return get_context_modules()
@contextproperty
@contextproperty()
def flags(self) -> Any:
"""The `flags` variable contains true/false values for flags provided
on the command line.
@@ -644,7 +647,7 @@ class BaseContext(metaclass=ContextMeta):
"""
return flags_module.get_flag_obj()
@contextmember
@contextmember()
@staticmethod
def print(msg: str) -> str:
"""Prints a line to stdout.
@@ -662,7 +665,7 @@ class BaseContext(metaclass=ContextMeta):
print(msg)
return ""
@contextmember
@contextmember()
@staticmethod
def diff_of_two_dicts(
dict_a: Dict[str, List[str]], dict_b: Dict[str, List[str]]
@@ -691,7 +694,7 @@ class BaseContext(metaclass=ContextMeta):
dict_diff.update({k: dict_a[k]})
return dict_diff
@contextmember
@contextmember()
@staticmethod
def local_md5(value: str) -> str:
"""Calculates an MD5 hash of the given string.

View File

@@ -19,7 +19,7 @@ class ConfiguredContext(TargetContext):
super().__init__(config.to_target_dict(), config.cli_vars)
self.config = config
@contextproperty
@contextproperty()
def project_name(self) -> str:
return self.config.project_name
@@ -80,11 +80,11 @@ class SchemaYamlContext(ConfiguredContext):
self._project_name = project_name
self.schema_yaml_vars = schema_yaml_vars
@contextproperty
@contextproperty()
def var(self) -> ConfiguredVar:
return ConfiguredVar(self._ctx, self.config, self._project_name)
@contextmember
@contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str:
return_value = None
if var.startswith(SECRET_ENV_PREFIX):
@@ -113,7 +113,7 @@ class MacroResolvingContext(ConfiguredContext):
def __init__(self, config):
super().__init__(config)
@contextproperty
@contextproperty()
def var(self) -> ConfiguredVar:
return ConfiguredVar(self._ctx, self.config, self.config.project_name)

View File

@@ -45,6 +45,10 @@ class UnrenderedConfig(ConfigSource):
model_configs = unrendered.get("tests")
elif resource_type == NodeType.Metric:
model_configs = unrendered.get("metrics")
elif resource_type == NodeType.SemanticModel:
model_configs = unrendered.get("semantic_models")
elif resource_type == NodeType.SavedQuery:
model_configs = unrendered.get("saved_queries")
elif resource_type == NodeType.Exposure:
model_configs = unrendered.get("exposures")
else:
@@ -70,6 +74,10 @@ class RenderedConfig(ConfigSource):
model_configs = self.project.tests
elif resource_type == NodeType.Metric:
model_configs = self.project.metrics
elif resource_type == NodeType.SemanticModel:
model_configs = self.project.semantic_models
elif resource_type == NodeType.SavedQuery:
model_configs = self.project.saved_queries
elif resource_type == NodeType.Exposure:
model_configs = self.project.exposures
else:
@@ -189,9 +197,21 @@ class ContextConfigGenerator(BaseContextConfigGenerator[C]):
def _update_from_config(self, result: C, partial: Dict[str, Any], validate: bool = False) -> C:
translated = self._active_project.credentials.translate_aliases(partial)
return result.update_from(
translated = self.translate_hook_names(translated)
updated = result.update_from(
translated, self._active_project.credentials.type, validate=validate
)
return updated
def translate_hook_names(self, project_dict):
# This is a kind of kludge because the fix for #6411 specifically allowed misspelling
# the hook field names in dbt_project.yml, which only ever worked because we didn't
# run validate on the dbt_project configs.
if "pre_hook" in project_dict:
project_dict["pre-hook"] = project_dict.pop("pre_hook")
if "post_hook" in project_dict:
project_dict["post-hook"] = project_dict.pop("post_hook")
return project_dict
def calculate_node_config_dict(
self,

View File

@@ -24,7 +24,7 @@ class DocsRuntimeContext(SchemaYamlContext):
self.node = node
self.manifest = manifest
@contextmember
@contextmember()
def doc(self, *args: str) -> str:
"""The `doc` function is used to reference docs blocks in schema.yml
files. It is analogous to the `ref` function. For more information,

View File

@@ -2,7 +2,6 @@ import functools
from typing import NoReturn
from dbt.events.functions import warn_or_error
from dbt.events.helpers import env_secrets, scrub_secrets
from dbt.events.types import JinjaLogWarning
from dbt.exceptions import (
@@ -26,6 +25,8 @@ from dbt.exceptions import (
ContractError,
ColumnTypeMissingError,
FailFastError,
scrub_secrets,
env_secrets,
)

View File

@@ -40,7 +40,7 @@ class MacroResolver:
self._build_internal_packages_namespace()
self._build_macros_by_name()
def _build_internal_packages_namespace(self):
def _build_internal_packages_namespace(self) -> None:
# Iterate in reverse-order and overwrite: the packages that are first
# in the list are the ones we want to "win".
self.internal_packages_namespace: MacroNamespace = {}
@@ -56,7 +56,7 @@ class MacroResolver:
# root package namespace
# non-internal packages (that aren't local or root)
# dbt internal packages
def _build_macros_by_name(self):
def _build_macros_by_name(self) -> None:
macros_by_name = {}
# all internal packages (already in the right order)
@@ -78,7 +78,7 @@ class MacroResolver:
self,
package_namespaces: Dict[str, MacroNamespace],
macro: Macro,
):
) -> None:
if macro.package_name in package_namespaces:
namespace = package_namespaces[macro.package_name]
else:
@@ -89,7 +89,7 @@ class MacroResolver:
raise DuplicateMacroNameError(macro, macro, macro.package_name)
package_namespaces[macro.package_name][macro.name] = macro
def add_macro(self, macro: Macro):
def add_macro(self, macro: Macro) -> None:
macro_name: str = macro.name
# internal macros (from plugins) will be processed separately from
@@ -103,11 +103,11 @@ class MacroResolver:
if macro.package_name == self.root_project_name:
self.root_package_macros[macro_name] = macro
def add_macros(self):
def add_macros(self) -> None:
for macro in self.macros.values():
self.add_macro(macro)
def get_macro(self, local_package, macro_name):
def get_macro(self, local_package, macro_name) -> Optional[Macro]:
local_package_macros = {}
# If the macro is explicitly prefixed with an internal namespace
# (e.g. 'dbt.some_macro'), look there first
@@ -125,7 +125,7 @@ class MacroResolver:
return self.macros_by_name[macro_name]
return None
def get_macro_id(self, local_package, macro_name):
def get_macro_id(self, local_package, macro_name) -> Optional[str]:
macro = self.get_macro(local_package, macro_name)
if macro is None:
return None

View File

@@ -67,7 +67,7 @@ class ManifestContext(ConfiguredContext):
dct.update(self.namespace)
return dct
@contextproperty
@contextproperty()
def context_macro_stack(self):
return self.macro_stack

View File

@@ -60,7 +60,6 @@ from dbt.exceptions import (
MetricArgsError,
MissingConfigError,
OperationsCannotRefEphemeralNodesError,
PackageNotInDepsError,
ParsingError,
RefBadContextError,
RefArgsError,
@@ -618,7 +617,7 @@ class RuntimeMetricResolver(BaseMetricResolver):
target_package=target_package,
)
return ResolvedMetricReference(target_metric, self.manifest, self.Relation)
return ResolvedMetricReference(target_metric, self.manifest)
# `var` implementations.
@@ -638,10 +637,8 @@ class ModelConfiguredVar(Var):
package_name = self._node.package_name
if package_name != self._config.project_name:
if package_name not in dependencies:
# I don't think this is actually reachable
raise PackageNotInDepsError(package_name, node=self._node)
yield dependencies[package_name]
if package_name in dependencies:
yield dependencies[package_name]
yield self._config
def _generate_merged(self) -> Mapping[str, Any]:
@@ -754,19 +751,19 @@ class ProviderContext(ManifestContext):
self.model,
)
@contextproperty
@contextproperty()
def dbt_metadata_envs(self) -> Dict[str, str]:
return get_metadata_vars()
@contextproperty
@contextproperty()
def invocation_args_dict(self):
return args_to_dict(self.config.args)
@contextproperty
@contextproperty()
def _sql_results(self) -> Dict[str, Optional[AttrDict]]:
return self.sql_results
@contextmember
@contextmember()
def load_result(self, name: str) -> Optional[AttrDict]:
if name in self.sql_results:
# handle the special case of "main" macro
@@ -787,7 +784,7 @@ class ProviderContext(ManifestContext):
# Handle trying to load a result that was never stored
return None
@contextmember
@contextmember()
def store_result(
self, name: str, response: Any, agate_table: Optional[agate.Table] = None
) -> str:
@@ -803,7 +800,7 @@ class ProviderContext(ManifestContext):
)
return ""
@contextmember
@contextmember()
def store_raw_result(
self,
name: str,
@@ -815,7 +812,7 @@ class ProviderContext(ManifestContext):
response = AdapterResponse(_message=message, code=code, rows_affected=rows_affected)
return self.store_result(name, response, agate_table)
@contextproperty
@contextproperty()
def validation(self):
def validate_any(*args) -> Callable[[T], None]:
def inner(value: T) -> None:
@@ -836,7 +833,7 @@ class ProviderContext(ManifestContext):
}
)
@contextmember
@contextmember()
def write(self, payload: str) -> str:
# macros/source defs aren't 'writeable'.
if isinstance(self.model, (Macro, SourceDefinition)):
@@ -845,11 +842,11 @@ class ProviderContext(ManifestContext):
self.model.write_node(self.config.project_root, self.model.build_path, payload)
return ""
@contextmember
@contextmember()
def render(self, string: str) -> str:
return get_rendered(string, self._ctx, self.model)
@contextmember
@contextmember()
def try_or_compiler_error(
self, message_if_exception: str, func: Callable, *args, **kwargs
) -> Any:
@@ -858,21 +855,32 @@ class ProviderContext(ManifestContext):
except Exception:
raise CompilationError(message_if_exception, self.model)
@contextmember
@contextmember()
def load_agate_table(self) -> agate.Table:
if not isinstance(self.model, SeedNode):
raise LoadAgateTableNotSeedError(self.model.resource_type, node=self.model)
assert self.model.root_path
path = os.path.join(self.model.root_path, self.model.original_file_path)
# include package_path for seeds defined in packages
package_path = (
os.path.join(self.config.packages_install_path, self.model.package_name)
if self.model.package_name != self.config.project_name
else "."
)
path = os.path.join(self.config.project_root, package_path, self.model.original_file_path)
if not os.path.exists(path):
assert self.model.root_path
path = os.path.join(self.model.root_path, self.model.original_file_path)
column_types = self.model.config.column_types
delimiter = self.model.config.delimiter
try:
table = agate_helper.from_csv(path, text_columns=column_types)
table = agate_helper.from_csv(path, text_columns=column_types, delimiter=delimiter)
except ValueError as e:
raise LoadAgateTableValueError(e, node=self.model)
table.original_abspath = os.path.abspath(path)
return table
@contextproperty
@contextproperty()
def ref(self) -> Callable:
"""The most important function in dbt is `ref()`; it's impossible to
build even moderately complex models without it. `ref()` is how you
@@ -913,11 +921,11 @@ class ProviderContext(ManifestContext):
"""
return self.provider.ref(self.db_wrapper, self.model, self.config, self.manifest)
@contextproperty
@contextproperty()
def source(self) -> Callable:
return self.provider.source(self.db_wrapper, self.model, self.config, self.manifest)
@contextproperty
@contextproperty()
def metric(self) -> Callable:
return self.provider.metric(self.db_wrapper, self.model, self.config, self.manifest)
@@ -978,7 +986,7 @@ class ProviderContext(ManifestContext):
""" # noqa
return self.provider.Config(self.model, self.context_config)
@contextproperty
@contextproperty()
def execute(self) -> bool:
"""`execute` is a Jinja variable that returns True when dbt is in
"execute" mode.
@@ -1039,7 +1047,7 @@ class ProviderContext(ManifestContext):
""" # noqa
return self.provider.execute
@contextproperty
@contextproperty()
def exceptions(self) -> Dict[str, Any]:
"""The exceptions namespace can be used to raise warnings and errors in
dbt userspace.
@@ -1077,15 +1085,15 @@ class ProviderContext(ManifestContext):
""" # noqa
return wrapped_exports(self.model)
@contextproperty
@contextproperty()
def database(self) -> str:
return self.config.credentials.database
@contextproperty
@contextproperty()
def schema(self) -> str:
return self.config.credentials.schema
@contextproperty
@contextproperty()
def var(self) -> ModelConfiguredVar:
return self.provider.Var(
context=self._ctx,
@@ -1102,22 +1110,22 @@ class ProviderContext(ManifestContext):
"""
return self.db_wrapper
@contextproperty
@contextproperty()
def api(self) -> Dict[str, Any]:
return {
"Relation": self.db_wrapper.Relation,
"Column": self.adapter.Column,
}
@contextproperty
@contextproperty()
def column(self) -> Type[Column]:
return self.adapter.Column
@contextproperty
@contextproperty()
def env(self) -> Dict[str, Any]:
return self.target
@contextproperty
@contextproperty()
def graph(self) -> Dict[str, Any]:
"""The `graph` context variable contains information about the nodes in
your dbt project. Models, sources, tests, and snapshots are all
@@ -1226,30 +1234,42 @@ class ProviderContext(ManifestContext):
@contextproperty("model")
def ctx_model(self) -> Dict[str, Any]:
ret = self.model.to_dict(omit_none=True)
model_dct = self.model.to_dict(omit_none=True)
# Maintain direct use of compiled_sql
# TODO add depreciation logic[CT-934]
if "compiled_code" in ret:
ret["compiled_sql"] = ret["compiled_code"]
return ret
if "compiled_code" in model_dct:
model_dct["compiled_sql"] = model_dct["compiled_code"]
@contextproperty
if (
hasattr(self.model, "contract")
and self.model.contract.alias_types is True
and "columns" in model_dct
):
for column in model_dct["columns"].values():
if "data_type" in column:
orig_data_type = column["data_type"]
# translate data_type to value in Column.TYPE_LABELS
new_data_type = self.adapter.Column.translate_type(orig_data_type)
column["data_type"] = new_data_type
return model_dct
@contextproperty()
def pre_hooks(self) -> Optional[List[Dict[str, Any]]]:
return None
@contextproperty
@contextproperty()
def post_hooks(self) -> Optional[List[Dict[str, Any]]]:
return None
@contextproperty
@contextproperty()
def sql(self) -> Optional[str]:
return None
@contextproperty
@contextproperty()
def sql_now(self) -> str:
return self.adapter.date_function()
@contextmember
@contextmember()
def adapter_macro(self, name: str, *args, **kwargs):
"""This was deprecated in v0.18 in favor of adapter.dispatch"""
msg = (
@@ -1261,7 +1281,7 @@ class ProviderContext(ManifestContext):
)
raise CompilationError(msg)
@contextmember
@contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str:
"""The env_var() function. Return the environment variable named 'var'.
If there is no such environment variable set, return the default.
@@ -1305,7 +1325,7 @@ class ProviderContext(ManifestContext):
else:
raise EnvVarMissingError(var)
@contextproperty
@contextproperty()
def selected_resources(self) -> List[str]:
"""The `selected_resources` variable contains a list of the resources
selected based on the parameters provided to the dbt command.
@@ -1314,7 +1334,7 @@ class ProviderContext(ManifestContext):
"""
return selected_resources.SELECTED_RESOURCES
@contextmember
@contextmember()
def submit_python_job(self, parsed_model: Dict, compiled_code: str) -> AdapterResponse:
# Check macro_stack and that the unique id is for a materialization macro
if not (
@@ -1357,7 +1377,7 @@ class MacroContext(ProviderContext):
class ModelContext(ProviderContext):
model: ManifestNode
@contextproperty
@contextproperty()
def pre_hooks(self) -> List[Dict[str, Any]]:
if self.model.resource_type in [NodeType.Source, NodeType.Test]:
return []
@@ -1366,7 +1386,7 @@ class ModelContext(ProviderContext):
h.to_dict(omit_none=True) for h in self.model.config.pre_hook # type: ignore[union-attr] # noqa
]
@contextproperty
@contextproperty()
def post_hooks(self) -> List[Dict[str, Any]]:
if self.model.resource_type in [NodeType.Source, NodeType.Test]:
return []
@@ -1375,7 +1395,7 @@ class ModelContext(ProviderContext):
h.to_dict(omit_none=True) for h in self.model.config.post_hook # type: ignore[union-attr] # noqa
]
@contextproperty
@contextproperty()
def sql(self) -> Optional[str]:
# only doing this in sql model for backward compatible
if self.model.language == ModelLanguage.sql: # type: ignore[union-attr]
@@ -1392,7 +1412,7 @@ class ModelContext(ProviderContext):
else:
return None
@contextproperty
@contextproperty()
def compiled_code(self) -> Optional[str]:
if getattr(self.model, "defer_relation", None):
# TODO https://github.com/dbt-labs/dbt-core/issues/7976
@@ -1403,15 +1423,15 @@ class ModelContext(ProviderContext):
else:
return None
@contextproperty
@contextproperty()
def database(self) -> str:
return getattr(self.model, "database", self.config.credentials.database)
@contextproperty
@contextproperty()
def schema(self) -> str:
return getattr(self.model, "schema", self.config.credentials.schema)
@contextproperty
@contextproperty()
def this(self) -> Optional[RelationProxy]:
"""`this` makes available schema information about the currently
executing model. It's is useful in any context in which you need to
@@ -1446,7 +1466,7 @@ class ModelContext(ProviderContext):
return None
return self.db_wrapper.Relation.create_from(self.config, self.model)
@contextproperty
@contextproperty()
def defer_relation(self) -> Optional[RelationProxy]:
"""
For commands which add information about this node's corresponding
@@ -1660,7 +1680,7 @@ class TestContext(ProviderContext):
)
self.namespace = macro_namespace
@contextmember
@contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str:
return_value = None
if var.startswith(SECRET_ENV_PREFIX):

View File

@@ -14,7 +14,7 @@ class SecretContext(BaseContext):
"""This context is used in profiles.yml + packages.yml. It can render secret
env vars that aren't usable elsewhere"""
@contextmember
@contextmember()
def env_var(self, var: str, default: Optional[str] = None) -> str:
"""The env_var() function. Return the environment variable named 'var'.
If there is no such environment variable set, return the default.

View File

@@ -9,7 +9,7 @@ class TargetContext(BaseContext):
super().__init__(cli_vars=cli_vars)
self.target_dict = target_dict
@contextproperty
@contextproperty()
def target(self) -> Dict[str, Any]:
"""`target` contains information about your connection to the warehouse
(specified in profiles.yml). Some configs are shared between all

View File

@@ -16,26 +16,21 @@ from dbt.utils import translate_aliases, md5
from dbt.events.functions import fire_event
from dbt.events.types import NewConnectionOpening
from dbt.events.contextvars import get_node_info
from typing_extensions import Protocol
from typing_extensions import Protocol, Annotated
from dbt.dataclass_schema import (
dbtClassMixin,
StrEnum,
ExtensibleDbtClassMixin,
HyphenatedDbtClassMixin,
ValidatedStringMixin,
register_pattern,
)
from dbt.contracts.util import Replaceable
from mashumaro.jsonschema.annotations import Pattern
class Identifier(ValidatedStringMixin):
ValidationRegex = r"^[A-Za-z_][A-Za-z0-9_]+$"
# we need register_pattern for jsonschema validation
register_pattern(Identifier, r"^[A-Za-z_][A-Za-z0-9_]+$")
@dataclass
class AdapterResponse(dbtClassMixin):
_message: str
@@ -55,7 +50,8 @@ class ConnectionState(StrEnum):
@dataclass(init=False)
class Connection(ExtensibleDbtClassMixin, Replaceable):
type: Identifier
# Annotated is used by mashumaro for jsonschema generation
type: Annotated[Identifier, Pattern(r"^[A-Za-z_][A-Za-z0-9_]+$")]
name: Optional[str] = None
state: ConnectionState = ConnectionState.INIT
transaction_open: bool = False
@@ -108,7 +104,7 @@ class LazyHandle:
connection, updating the handle on the Connection.
"""
def __init__(self, opener: Callable[[Connection], Connection]):
def __init__(self, opener: Callable[[Connection], Connection]) -> None:
self.opener = opener
def resolve(self, connection: Connection) -> Connection:
@@ -161,6 +157,7 @@ class Credentials(ExtensibleDbtClassMixin, Replaceable, metaclass=abc.ABCMeta):
@classmethod
def __pre_deserialize__(cls, data):
data = super().__pre_deserialize__(data)
# Need to fixup dbname => database, pass => password
data = cls.translate_aliases(data)
return data
@@ -220,10 +217,10 @@ DEFAULT_QUERY_COMMENT = """
@dataclass
class QueryComment(HyphenatedDbtClassMixin):
class QueryComment(dbtClassMixin):
comment: str = DEFAULT_QUERY_COMMENT
append: bool = False
job_label: bool = False
job_label: bool = field(default=False, metadata={"alias": "job-label"})
class AdapterRequiredConfig(HasCredentials, Protocol):

View File

@@ -225,10 +225,13 @@ class SchemaSourceFile(BaseSourceFile):
sources: List[str] = field(default_factory=list)
exposures: List[str] = field(default_factory=list)
metrics: List[str] = field(default_factory=list)
# metrics generated from semantic_model measures
generated_metrics: List[str] = field(default_factory=list)
groups: List[str] = field(default_factory=list)
# node patches contain models, seeds, snapshots, analyses
ndp: List[str] = field(default_factory=list)
semantic_models: List[str] = field(default_factory=list)
saved_queries: List[str] = field(default_factory=list)
# any macro patches in this file by macro unique_id.
mcp: Dict[str, str] = field(default_factory=dict)
# any source patches in this file. The entries are package, name pairs

View File

@@ -20,6 +20,7 @@ from typing import (
Generic,
AbstractSet,
ClassVar,
Iterable,
)
from typing_extensions import Protocol
from uuid import UUID
@@ -37,6 +38,7 @@ from dbt.contracts.graph.nodes import (
ModelNode,
DeferRelation,
ResultNode,
SavedQuery,
SemanticModel,
SourceDefinition,
UnpatchedSourceDefinition,
@@ -44,7 +46,13 @@ from dbt.contracts.graph.nodes import (
from dbt.contracts.graph.unparsed import SourcePatch, NodeVersion, UnparsedVersion
from dbt.contracts.graph.manifest_upgrade import upgrade_manifest_json
from dbt.contracts.files import SourceFile, SchemaSourceFile, FileHash, AnySourceFile
from dbt.contracts.util import BaseArtifactMetadata, SourceKey, ArtifactMixin, schema_version
from dbt.contracts.util import (
BaseArtifactMetadata,
SourceKey,
ArtifactMixin,
schema_version,
get_artifact_schema_version,
)
from dbt.dataclass_schema import dbtClassMixin
from dbt.exceptions import (
CompilationError,
@@ -88,7 +96,7 @@ def find_unique_id_for_package(storage, key, package: Optional[PackageName]):
class DocLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest"):
def __init__(self, manifest: "Manifest") -> None:
self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
self.populate(manifest)
@@ -119,7 +127,7 @@ class DocLookup(dbtClassMixin):
class SourceLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest"):
def __init__(self, manifest: "Manifest") -> None:
self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
self.populate(manifest)
@@ -156,7 +164,7 @@ class RefableLookup(dbtClassMixin):
_lookup_types: ClassVar[set] = set(NodeType.refable())
_versioned_types: ClassVar[set] = set(NodeType.versioned())
def __init__(self, manifest: "Manifest"):
def __init__(self, manifest: "Manifest") -> None:
self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
self.populate(manifest)
@@ -267,7 +275,7 @@ class RefableLookup(dbtClassMixin):
class MetricLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest"):
def __init__(self, manifest: "Manifest") -> None:
self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
self.populate(manifest)
@@ -299,6 +307,41 @@ class MetricLookup(dbtClassMixin):
return manifest.metrics[unique_id]
class SavedQueryLookup(dbtClassMixin):
"""Lookup utility for finding SavedQuery nodes"""
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_saved_query(self, saved_query: SavedQuery):
if saved_query.search_name not in self.storage:
self.storage[saved_query.search_name] = {}
self.storage[saved_query.search_name][saved_query.package_name] = saved_query.unique_id
def populate(self, manifest):
for saved_query in manifest.saved_queries.values():
if hasattr(saved_query, "name"):
self.add_saved_query(saved_query)
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SavedQuery:
if unique_id not in manifest.saved_queries:
raise dbt.exceptions.DbtInternalError(
f"SavedQUery {unique_id} found in cache but not found in manifest"
)
return manifest.saved_queries[unique_id]
class SemanticModelByMeasureLookup(dbtClassMixin):
"""Lookup utility for finding SemanticModel by measure
@@ -306,7 +349,7 @@ class SemanticModelByMeasureLookup(dbtClassMixin):
the semantic models in a manifest.
"""
def __init__(self, manifest: "Manifest"):
def __init__(self, manifest: "Manifest") -> None:
self.storage: DefaultDict[str, Dict[PackageName, UniqueID]] = defaultdict(dict)
self.populate(manifest)
@@ -331,20 +374,31 @@ class SemanticModelByMeasureLookup(dbtClassMixin):
"""Populate storage with all the measure + package paths to the Manifest's SemanticModels"""
for semantic_model in manifest.semantic_models.values():
self.add(semantic_model=semantic_model)
for disabled in manifest.disabled.values():
for node in disabled:
if isinstance(node, SemanticModel):
self.add(semantic_model=node)
def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SemanticModel:
"""Tries to get a SemanticModel from the Manifest"""
semantic_model = manifest.semantic_models.get(unique_id)
if semantic_model is None:
enabled_semantic_model: Optional[SemanticModel] = manifest.semantic_models.get(unique_id)
disabled_semantic_model: Optional[List] = manifest.disabled.get(unique_id)
if isinstance(enabled_semantic_model, SemanticModel):
return enabled_semantic_model
elif disabled_semantic_model is not None and isinstance(
disabled_semantic_model[0], SemanticModel
):
return disabled_semantic_model[0]
else:
raise dbt.exceptions.DbtInternalError(
f"Semantic model `{unique_id}` found in cache but not found in manifest"
)
return semantic_model
# This handles both models/seeds/snapshots and sources/metrics/exposures
# This handles both models/seeds/snapshots and sources/metrics/exposures/semantic_models
class DisabledLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest"):
def __init__(self, manifest: "Manifest") -> None:
self.storage: Dict[str, Dict[PackageName, List[Any]]] = {}
self.populate(manifest)
@@ -598,6 +652,9 @@ class Disabled(Generic[D]):
MaybeMetricNode = Optional[Union[Metric, Disabled[Metric]]]
MaybeSavedQueryNode = Optional[Union[SavedQuery, Disabled[SavedQuery]]]
MaybeDocumentation = Optional[Documentation]
@@ -742,6 +799,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
disabled: MutableMapping[str, List[GraphMemberNode]] = field(default_factory=dict)
env_vars: MutableMapping[str, str] = field(default_factory=dict)
semantic_models: MutableMapping[str, SemanticModel] = field(default_factory=dict)
saved_queries: MutableMapping[str, SavedQuery] = field(default_factory=dict)
_doc_lookup: Optional[DocLookup] = field(
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
@@ -755,6 +813,9 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
_metric_lookup: Optional[MetricLookup] = field(
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
)
_saved_query_lookup: Optional[SavedQueryLookup] = field(
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
)
_semantic_model_by_measure_lookup: Optional[SemanticModelByMeasureLookup] = field(
default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
)
@@ -799,6 +860,9 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
"semantic_models": {
k: v.to_dict(omit_none=False) for k, v in self.semantic_models.items()
},
"saved_queries": {
k: v.to_dict(omit_none=False) for k, v in self.saved_queries.items()
},
}
def build_disabled_by_file_id(self):
@@ -860,6 +924,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
self.sources.values(),
self.metrics.values(),
self.semantic_models.values(),
self.saved_queries.values(),
)
for resource in all_resources:
resource_type_plural = resource.resource_type.pluralize()
@@ -895,6 +960,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
files={k: _deepcopy(v) for k, v in self.files.items()},
state_check=_deepcopy(self.state_check),
semantic_models={k: _deepcopy(v) for k, v in self.semantic_models.items()},
saved_queries={k: _deepcopy(v) for k, v in self.saved_queries.items()},
)
copy.build_flat_graph()
return copy
@@ -907,6 +973,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
self.exposures.values(),
self.metrics.values(),
self.semantic_models.values(),
self.saved_queries.values(),
)
)
forward_edges, backward_edges = build_node_edges(edge_members)
@@ -927,13 +994,22 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
groupable_nodes = list(
chain(
self.nodes.values(),
self.saved_queries.values(),
self.semantic_models.values(),
self.metrics.values(),
)
)
group_map = {group.name: [] for group in self.groups.values()}
for node in groupable_nodes:
if node.group is not None:
group_map[node.group].append(node.unique_id)
# group updates are not included with state:modified and
# by ignoring the groups that aren't in the group map we
# can avoid hitting errors for groups that are not getting
# updated. This is a hack but any groups that are not
# valid will be caught in
# parser.manifest.ManifestLoader.check_valid_group_config_node
if node.group in group_map:
group_map[node.group].append(node.unique_id)
self.group_map = group_map
def writable_manifest(self) -> "WritableManifest":
@@ -954,6 +1030,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
parent_map=self.parent_map,
group_map=self.group_map,
semantic_models=self.semantic_models,
saved_queries=self.saved_queries,
)
def write(self, path):
@@ -972,6 +1049,8 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
return self.metrics[unique_id]
elif unique_id in self.semantic_models:
return self.semantic_models[unique_id]
elif unique_id in self.saved_queries:
return self.saved_queries[unique_id]
else:
# something terrible has happened
raise dbt.exceptions.DbtInternalError(
@@ -1008,6 +1087,13 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
self._metric_lookup = MetricLookup(self)
return self._metric_lookup
@property
def saved_query_lookup(self) -> SavedQueryLookup:
"""Retuns a SavedQueryLookup, instantiating it first if necessary."""
if self._saved_query_lookup is None:
self._saved_query_lookup = SavedQueryLookup(self)
return self._saved_query_lookup
@property
def semantic_model_by_measure_lookup(self) -> SemanticModelByMeasureLookup:
"""Gets (and creates if necessary) the lookup utility for getting SemanticModels by measures"""
@@ -1056,8 +1142,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
return resolved_refs
# Called by dbt.parser.manifest._process_refs_for_exposure, _process_refs_for_metric,
# and dbt.parser.manifest._process_refs_for_node
# Called by dbt.parser.manifest._process_refs & ManifestLoader.check_for_model_deprecations
def resolve_ref(
self,
source_node: GraphMemberNode,
@@ -1142,6 +1227,35 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
return Disabled(disabled[0])
return None
def resolve_saved_query(
self,
target_saved_query_name: str,
target_saved_query_package: Optional[str],
current_project: str,
node_package: str,
) -> MaybeSavedQueryNode:
"""Tries to find the SavedQuery by name within the available project and packages.
Will return the first enabled SavedQuery matching the name found while iterating over
the scoped packages. If no enabled SavedQuery node match is found, returns the last
disabled SavedQuery node. Otherwise it returns None.
"""
disabled: Optional[List[SavedQuery]] = None
candidates = _packages_to_search(current_project, node_package, target_saved_query_package)
for pkg in candidates:
saved_query = self.saved_query_lookup.find(target_saved_query_name, pkg, self)
if saved_query is not None and saved_query.config.enabled:
return saved_query
# it's possible that the node is disabled
if disabled is None:
disabled = self.disabled_lookup.find(f"{target_saved_query_name}", pkg)
if disabled:
return Disabled(disabled[0])
return None
def resolve_semantic_model_for_measure(
self,
target_measure_name: str,
@@ -1156,6 +1270,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
semantic_model = self.semantic_model_by_measure_lookup.find(
target_measure_name, pkg, self
)
# need to return it even if it's disabled so know it's not fully missing
if semantic_model is not None:
return semantic_model
@@ -1331,10 +1446,13 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
self.exposures[exposure.unique_id] = exposure
source_file.exposures.append(exposure.unique_id)
def add_metric(self, source_file: SchemaSourceFile, metric: Metric):
def add_metric(self, source_file: SchemaSourceFile, metric: Metric, generated: bool = False):
_check_duplicates(metric, self.metrics)
self.metrics[metric.unique_id] = metric
source_file.metrics.append(metric.unique_id)
if not generated:
source_file.metrics.append(metric.unique_id)
else:
source_file.generated_metrics.append(metric.unique_id)
def add_group(self, source_file: SchemaSourceFile, group: Group):
_check_duplicates(group, self.groups)
@@ -1356,6 +1474,10 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
source_file.add_test(node.unique_id, test_from)
if isinstance(node, Metric):
source_file.metrics.append(node.unique_id)
if isinstance(node, SavedQuery):
source_file.saved_queries.append(node.unique_id)
if isinstance(node, SemanticModel):
source_file.semantic_models.append(node.unique_id)
if isinstance(node, Exposure):
source_file.exposures.append(node.unique_id)
else:
@@ -1371,6 +1493,11 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
self.semantic_models[semantic_model.unique_id] = semantic_model
source_file.semantic_models.append(semantic_model.unique_id)
def add_saved_query(self, source_file: SchemaSourceFile, saved_query: SavedQuery) -> None:
_check_duplicates(saved_query, self.saved_queries)
self.saved_queries[saved_query.unique_id] = saved_query
source_file.saved_queries.append(saved_query.unique_id)
# end of methods formerly in ParseResult
# Provide support for copy.deepcopy() - we just need to avoid the lock!
@@ -1398,6 +1525,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
self.disabled,
self.env_vars,
self.semantic_models,
self.saved_queries,
self._doc_lookup,
self._source_lookup,
self._ref_lookup,
@@ -1410,19 +1538,19 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
class MacroManifest(MacroMethods):
def __init__(self, macros):
def __init__(self, macros) -> None:
self.macros = macros
self.metadata = ManifestMetadata()
# This is returned by the 'graph' context property
# in the ProviderContext class.
self.flat_graph = {}
self.flat_graph: Dict[str, Any] = {}
AnyManifest = Union[Manifest, MacroManifest]
@dataclass
@schema_version("manifest", 10)
@schema_version("manifest", 11)
class WritableManifest(ArtifactMixin):
nodes: Mapping[UniqueID, ManifestNode] = field(
metadata=dict(description=("The nodes defined in the dbt project and its dependencies"))
@@ -1468,6 +1596,9 @@ class WritableManifest(ArtifactMixin):
description="A mapping from group names to their nodes",
)
)
saved_queries: Mapping[UniqueID, SavedQuery] = field(
metadata=dict(description=("The saved queries defined in the dbt project"))
)
semantic_models: Mapping[UniqueID, SemanticModel] = field(
metadata=dict(description=("The semantic models defined in the dbt project"))
)
@@ -1478,7 +1609,7 @@ class WritableManifest(ArtifactMixin):
)
@classmethod
def compatible_previous_versions(self):
def compatible_previous_versions(cls) -> Iterable[Tuple[str, int]]:
return [
("manifest", 4),
("manifest", 5),
@@ -1486,14 +1617,15 @@ class WritableManifest(ArtifactMixin):
("manifest", 7),
("manifest", 8),
("manifest", 9),
("manifest", 10),
]
@classmethod
def upgrade_schema_version(cls, data):
"""This overrides the "upgrade_schema_version" call in VersionedSchema (via
ArtifactMixin) to modify the dictionary passed in from earlier versions of the manifest."""
manifest_schema_version = get_manifest_schema_version(data)
if manifest_schema_version <= 9:
manifest_schema_version = get_artifact_schema_version(data)
if manifest_schema_version <= 10:
data = upgrade_manifest_json(data, manifest_schema_version)
return cls.from_dict(data)
@@ -1506,13 +1638,6 @@ class WritableManifest(ArtifactMixin):
return dct
def get_manifest_schema_version(dct: dict) -> int:
schema_version = dct.get("metadata", {}).get("dbt_schema_version", None)
if not schema_version:
raise ValueError("Manifest doesn't have schema version")
return int(schema_version.split(".")[-2][-1])
def _check_duplicates(value: BaseNode, src: Mapping[str, BaseNode]):
if value.unique_id in src:
raise DuplicateResourceNameError(value, src[value.unique_id])

View File

@@ -62,10 +62,72 @@ def drop_v9_and_prior_metrics(manifest: dict) -> None:
manifest["disabled"] = filtered_disabled_entries
def _convert_dct_with_filter(v10_dct_with_opt_filter):
"""Upgrage the filter object from v10 to v11.
v10 filters from a serialized manifest looked like:
{..., 'filter': {'where_sql_template': '<filter_value>'}}
whereas v11 filters look like:
{..., 'filter': {'where_filters': [{'where_sql_template': '<filter_value>'}, ...]}}
"""
if v10_dct_with_opt_filter is not None and v10_dct_with_opt_filter.get("filter") is not None:
v10_dct_with_opt_filter["filter"] = {"where_filters": [v10_dct_with_opt_filter["filter"]]}
def _convert_metric(v10_metric_dict):
"""Upgrades a v10 metric object to a v11 metric object.
Specifcally the following properties change
1. metric.filter
2. metric.type_params.measure.filter
3. metric.type_params.input_measures[x].filter
4. metric.type_params.numerator.filter
5. metric.type_params.denominator.filter
6. metric.type_params.metrics[x].filter"
"""
# handles top level metric filter
_convert_dct_with_filter(v10_metric_dict)
type_params = v10_metric_dict.get("type_params")
if type_params is not None:
_convert_dct_with_filter(type_params.get("measure"))
_convert_dct_with_filter(type_params.get("numerator"))
_convert_dct_with_filter(type_params.get("denominator"))
# handles metric.type_params.input_measures[x].filter
input_measures = type_params.get("input_measures")
if input_measures is not None:
for input_measure in input_measures:
_convert_dct_with_filter(input_measure)
# handles metric.type_params.metrics[x].filter
metrics = type_params.get("metrics")
if metrics is not None:
for metric in metrics:
_convert_dct_with_filter(metric)
def upgrade_v10_metric_filters(manifest: dict):
"""Handles metric filters changes from v10 to v11."""
metrics = manifest.get("metrics", {})
for metric in metrics.values():
_convert_metric(metric)
disabled_nodes = manifest.get("disabled", {})
for unique_id, nodes in disabled_nodes.items():
if unique_id.split(".")[0] == "metric":
for node in nodes:
_convert_metric(node)
def upgrade_manifest_json(manifest: dict, manifest_schema_version: int) -> dict:
# this should remain 9 while the check in `upgrade_schema_version` may change
if manifest_schema_version <= 9:
drop_v9_and_prior_metrics(manifest=manifest)
elif manifest_schema_version == 10:
upgrade_v10_metric_filters(manifest=manifest)
for node_content in manifest.get("nodes", {}).values():
upgrade_node_content(node_content)
@@ -104,4 +166,6 @@ def upgrade_manifest_json(manifest: dict, manifest_schema_version: int) -> dict:
doc_content["resource_type"] = "doc"
if "semantic_models" not in manifest:
manifest["semantic_models"] = {}
if "saved_queries" not in manifest:
manifest["saved_queries"] = {}
return manifest

View File

@@ -1,8 +1,15 @@
from dbt.node_types import NodeType
from dbt.contracts.graph.manifest import Manifest, Metric
from dbt_semantic_interfaces.type_enums import MetricType
from typing import Any, Dict, Iterator, List
DERIVED_METRICS = [MetricType.DERIVED, MetricType.RATIO]
BASE_METRICS = [MetricType.SIMPLE, MetricType.CUMULATIVE, MetricType.CONVERSION]
class MetricReference(object):
def __init__(self, metric_name, package_name=None):
def __init__(self, metric_name, package_name=None) -> None:
self.metric_name = metric_name
self.package_name = package_name
@@ -17,76 +24,74 @@ class ResolvedMetricReference(MetricReference):
for working with metrics (ie. __str__ and templating functions)
"""
def __init__(self, node, manifest, Relation):
def __init__(self, node: Metric, manifest: Manifest) -> None:
super().__init__(node.name, node.package_name)
self.node = node
self.manifest = manifest
self.Relation = Relation
def __getattr__(self, key):
def __getattr__(self, key) -> Any:
return getattr(self.node, key)
def __str__(self):
def __str__(self) -> str:
return f"{self.node.name}"
@classmethod
def parent_metrics(cls, metric_node, manifest):
def parent_metrics(cls, metric_node: Metric, manifest: Manifest) -> Iterator[Metric]:
"""For a given metric, yeilds all upstream metrics."""
yield metric_node
for parent_unique_id in metric_node.depends_on.nodes:
node = manifest.metrics.get(parent_unique_id)
if node and node.resource_type == NodeType.Metric:
node = manifest.expect(parent_unique_id)
if isinstance(node, Metric):
yield from cls.parent_metrics(node, manifest)
@classmethod
def parent_metrics_names(cls, metric_node, manifest):
yield metric_node.name
for parent_unique_id in metric_node.depends_on.nodes:
node = manifest.metrics.get(parent_unique_id)
if node and node.resource_type == NodeType.Metric:
yield from cls.parent_metrics_names(node, manifest)
def parent_metrics_names(cls, metric_node: Metric, manifest: Manifest) -> Iterator[str]:
"""For a given metric, yeilds all upstream metric names"""
for metric in cls.parent_metrics(metric_node, manifest):
yield metric.name
@classmethod
def reverse_dag_parsing(cls, metric_node, manifest, metric_depth_count):
if metric_node.calculation_method == "derived":
yield {metric_node.name: metric_depth_count}
metric_depth_count = metric_depth_count + 1
def reverse_dag_parsing(
cls, metric_node: Metric, manifest: Manifest, metric_depth_count: int
) -> Iterator[Dict[str, int]]:
"""For the given metric, yeilds dictionaries having {<metric_name>: <depth_from_initial_metric} of upstream derived metrics.
for parent_unique_id in metric_node.depends_on.nodes:
node = manifest.metrics.get(parent_unique_id)
if (
node
and node.resource_type == NodeType.Metric
and node.calculation_method == "derived"
):
yield from cls.reverse_dag_parsing(node, manifest, metric_depth_count)
This function is intended as a helper function for other metric helper functions.
"""
if metric_node.type in DERIVED_METRICS:
yield {metric_node.name: metric_depth_count}
for parent_unique_id in metric_node.depends_on.nodes:
node = manifest.expect(parent_unique_id)
if isinstance(node, Metric):
yield from cls.reverse_dag_parsing(node, manifest, metric_depth_count + 1)
def full_metric_dependency(self):
"""Returns a unique list of all upstream metric names."""
to_return = list(set(self.parent_metrics_names(self.node, self.manifest)))
return to_return
def base_metric_dependency(self):
def base_metric_dependency(self) -> List[str]:
"""Returns a unique list of names for all upstream non-derived metrics."""
in_scope_metrics = list(self.parent_metrics(self.node, self.manifest))
base_metrics = {
metric.name for metric in in_scope_metrics if metric.type not in DERIVED_METRICS
}
to_return = []
for metric in in_scope_metrics:
if metric.calculation_method != "derived" and metric.name not in to_return:
to_return.append(metric.name)
return list(base_metrics)
return to_return
def derived_metric_dependency(self):
def derived_metric_dependency(self) -> List[str]:
"""Returns a unique list of names for all upstream derived metrics."""
in_scope_metrics = list(self.parent_metrics(self.node, self.manifest))
derived_metrics = {
metric.name for metric in in_scope_metrics if metric.type in DERIVED_METRICS
}
to_return = []
for metric in in_scope_metrics:
if metric.calculation_method == "derived" and metric.name not in to_return:
to_return.append(metric.name)
return list(derived_metrics)
return to_return
def derived_metric_dependency_depth(self):
def derived_metric_dependency_depth(self) -> List[Dict[str, int]]:
"""Returns a list of {<metric_name>: <depth_from_initial_metric>} for all upstream metrics."""
metric_depth_count = 1
to_return = list(self.reverse_dag_parsing(self.node, self.manifest, metric_depth_count))

View File

@@ -2,11 +2,11 @@ from dataclasses import field, Field, dataclass
from enum import Enum
from itertools import chain
from typing import Any, List, Optional, Dict, Union, Type, TypeVar, Callable
from typing_extensions import Annotated
from dbt.dataclass_schema import (
dbtClassMixin,
ValidationError,
register_pattern,
StrEnum,
)
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed, Docs
@@ -14,7 +14,9 @@ from dbt.contracts.graph.utils import validate_color
from dbt.contracts.util import Replaceable, list_str
from dbt.exceptions import DbtInternalError, CompilationError
from dbt import hooks
from dbt.node_types import NodeType
from dbt.node_types import NodeType, AccessType
from dbt_semantic_interfaces.type_enums.export_destination_type import ExportDestinationType
from mashumaro.jsonschema.annotations import Pattern
M = TypeVar("M", bound="Metadata")
@@ -188,9 +190,6 @@ class Severity(str):
pass
register_pattern(Severity, insensitive_patterns("warn", "error"))
class OnConfigurationChangeOption(StrEnum):
Apply = "apply"
Continue = "continue"
@@ -204,6 +203,7 @@ class OnConfigurationChangeOption(StrEnum):
@dataclass
class ContractConfig(dbtClassMixin, Replaceable):
enforced: bool = False
alias_types: bool = True
@dataclass
@@ -218,7 +218,6 @@ T = TypeVar("T", bound="BaseConfig")
@dataclass
class BaseConfig(AdditionalPropertiesAllowed, Replaceable):
# enable syntax like: config['key']
def __getitem__(self, key):
return self.get(key)
@@ -376,25 +375,50 @@ class BaseConfig(AdditionalPropertiesAllowed, Replaceable):
self.validate(dct)
return self.from_dict(dct)
def replace(self, **kwargs):
dct = self.to_dict(omit_none=True)
mapping = self.field_mapping()
for key, value in kwargs.items():
new_key = mapping.get(key, key)
dct[new_key] = value
return self.from_dict(dct)
@dataclass
class SemanticModelConfig(BaseConfig):
enabled: bool = True
group: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)
meta: Dict[str, Any] = field(
default_factory=dict,
metadata=MergeBehavior.Update.meta(),
)
@dataclass
class SavedQueryConfig(BaseConfig):
"""Where config options for SavedQueries are stored.
This class is much like many other node config classes. It's likely that
this class will expand in the direction of what's in the `NodeAndTestConfig`
class. It might make sense to clean the various *Config classes into one at
some point.
"""
enabled: bool = True
group: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)
meta: Dict[str, Any] = field(
default_factory=dict,
metadata=MergeBehavior.Update.meta(),
)
export_as: Optional[ExportDestinationType] = None
schema: Optional[str] = None
@dataclass
class MetricConfig(BaseConfig):
enabled: bool = True
group: Optional[str] = None
group: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)
@dataclass
@@ -447,11 +471,11 @@ class NodeConfig(NodeAndTestConfig):
persist_docs: Dict[str, Any] = field(default_factory=dict)
post_hook: List[Hook] = field(
default_factory=list,
metadata=MergeBehavior.Append.meta(),
metadata={"merge": MergeBehavior.Append, "alias": "post-hook"},
)
pre_hook: List[Hook] = field(
default_factory=list,
metadata=MergeBehavior.Append.meta(),
metadata={"merge": MergeBehavior.Append, "alias": "pre-hook"},
)
quoting: Dict[str, Any] = field(
default_factory=dict,
@@ -511,39 +535,29 @@ class NodeConfig(NodeAndTestConfig):
@classmethod
def __pre_deserialize__(cls, data):
data = super().__pre_deserialize__(data)
field_map = {"post-hook": "post_hook", "pre-hook": "pre_hook"}
# create a new dict because otherwise it gets overwritten in
# tests
new_dict = {}
for key in data:
new_dict[key] = data[key]
data = new_dict
for key in hooks.ModelHookType:
if key in data:
data[key] = [hooks.get_hook_dict(h) for h in data[key]]
for field_name in field_map:
if field_name in data:
new_name = field_map[field_name]
data[new_name] = data.pop(field_name)
return data
def __post_serialize__(self, dct):
dct = super().__post_serialize__(dct)
field_map = {"post_hook": "post-hook", "pre_hook": "pre-hook"}
for field_name in field_map:
if field_name in dct:
dct[field_map[field_name]] = dct.pop(field_name)
return dct
# this is still used by jsonschema validation
@classmethod
def field_mapping(cls):
return {"post_hook": "post-hook", "pre_hook": "pre-hook"}
@dataclass
class ModelConfig(NodeConfig):
access: AccessType = field(
default=AccessType.Protected,
metadata=MergeBehavior.Update.meta(),
)
@dataclass
class SeedConfig(NodeConfig):
materialized: str = "seed"
delimiter: str = ","
quote_columns: Optional[bool] = None
@classmethod
@@ -553,6 +567,9 @@ class SeedConfig(NodeConfig):
raise ValidationError("A seed must have a materialized value of 'seed'")
SEVERITY_PATTERN = r"^([Ww][Aa][Rr][Nn]|[Ee][Rr][Rr][Oo][Rr])$"
@dataclass
class TestConfig(NodeAndTestConfig):
__test__ = False
@@ -563,14 +580,64 @@ class TestConfig(NodeAndTestConfig):
metadata=CompareBehavior.Exclude.meta(),
)
materialized: str = "test"
severity: Severity = Severity("ERROR")
# Annotated is used by mashumaro for jsonschema generation
severity: Annotated[Severity, Pattern(SEVERITY_PATTERN)] = Severity("ERROR")
store_failures: Optional[bool] = None
store_failures_as: Optional[str] = None
where: Optional[str] = None
limit: Optional[int] = None
fail_calc: str = "count(*)"
warn_if: str = "!= 0"
error_if: str = "!= 0"
def __post_init__(self):
"""
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.
At the time of implementation, `store_failures = True` would always create a table; the user could not
configure this. Hence, if `store_failures = True` and `store_failures_as` is not specified, then it
should be set to "table" to mimic the existing functionality.
A side effect of this overriding functionality is that `store_failures_as="view"` at the project
level cannot be turned off at the model level without setting both `store_failures_as` and
`store_failures`. The former would cascade down and override `store_failures=False`. The proposal
is to include "ephemeral" as a value for `store_failures_as`, which effectively sets
`store_failures=False`.
The exception handling for this is tricky. If we raise an exception here, the entire run fails at
parse time. We would rather well-formed models run successfully, leaving only exceptions to be rerun
if necessary. Hence, the exception needs to be raised in the test materialization. In order to do so,
we need to make sure that we go down the `store_failures = True` route with the invalid setting for
`store_failures_as`. This results in the `.get()` defaulted to `True` below, instead of a normal
dictionary lookup as is done in the `if` block. Refer to the test materialization for the
exception that is raise as a result of an invalid value.
The intention of this block is to behave as if `store_failures_as` is the only setting,
but still allow for backwards compatibility for `store_failures`.
See https://github.com/dbt-labs/dbt-core/issues/6914 for more information.
"""
# if `store_failures_as` is not set, it gets set by `store_failures`
# the settings below mimic existing behavior prior to `store_failures_as`
get_store_failures_as_map = {
True: "table",
False: "ephemeral",
None: None,
}
# if `store_failures_as` is set, it dictates what `store_failures` gets set to
# the settings below overrides whatever `store_failures` is set to by the user
get_store_failures_map = {
"ephemeral": False,
"table": True,
"view": True,
}
if self.store_failures_as is None:
self.store_failures_as = get_store_failures_as_map[self.store_failures]
else:
self.store_failures = get_store_failures_map.get(self.store_failures_as, True)
@classmethod
def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> bool:
"""This is like __eq__, except it explicitly checks certain fields."""
@@ -582,6 +649,7 @@ class TestConfig(NodeAndTestConfig):
"warn_if",
"error_if",
"store_failures",
"store_failures_as",
]
seen = set()
@@ -619,6 +687,8 @@ class SnapshotConfig(EmptySnapshotConfig):
@classmethod
def validate(cls, data):
super().validate(data)
# Note: currently you can't just set these keys in schema.yml because this validation
# will fail when parsing the snapshot node.
if not data.get("strategy") or not data.get("unique_key") or not data.get("target_schema"):
raise ValidationError(
"Snapshots must be configured with a 'strategy', 'unique_key', "
@@ -649,6 +719,7 @@ class SnapshotConfig(EmptySnapshotConfig):
if data.get("materialized") and data.get("materialized") != "snapshot":
raise ValidationError("A snapshot must have a materialized value of 'snapshot'")
# Called by "calculate_node_config_dict" in ContextConfigGenerator
def finalize_and_validate(self):
data = self.to_dict(omit_none=True)
self.validate(data)
@@ -657,6 +728,8 @@ class SnapshotConfig(EmptySnapshotConfig):
RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
NodeType.Metric: MetricConfig,
NodeType.SemanticModel: SemanticModelConfig,
NodeType.SavedQuery: SavedQueryConfig,
NodeType.Exposure: ExposureConfig,
NodeType.Source: SourceConfig,
NodeType.Seed: SeedConfig,

View File

@@ -29,3 +29,12 @@ class ModelNodeArgs:
unique_id = f"{unique_id}.v{self.version}"
return unique_id
@property
def fqn(self) -> List[str]:
fqn = [self.package_name, self.name]
# Test for None explicitly because version can be 0
if self.version is not None:
fqn.append(f"v{self.version}")
return fqn

View File

@@ -6,12 +6,13 @@ from enum import Enum
import hashlib
from mashumaro.types import SerializableType
from typing import Optional, Union, List, Dict, Any, Sequence, Tuple, Iterator
from typing import Optional, Union, List, Dict, Any, Sequence, Tuple, Iterator, Literal
from dbt.dataclass_schema import dbtClassMixin, ExtensibleDbtClassMixin
from dbt.clients.system import write_file
from dbt.contracts.files import FileHash
from dbt.contracts.graph.saved_queries import Export, QueryParams
from dbt.contracts.graph.semantic_models import (
Defaults,
Dimension,
@@ -20,6 +21,7 @@ from dbt.contracts.graph.semantic_models import (
SourceFileMetadata,
)
from dbt.contracts.graph.unparsed import (
ConstantPropertyInput,
Docs,
ExposureType,
ExternalTable,
@@ -36,6 +38,7 @@ from dbt.contracts.graph.unparsed import (
UnparsedColumn,
)
from dbt.contracts.graph.node_args import ModelNodeArgs
from dbt.contracts.graph.semantic_layer_common import WhereFilterIntersection
from dbt.contracts.util import Replaceable, AdditionalPropertiesMixin
from dbt.events.functions import warn_or_error
from dbt.exceptions import ParsingError, ContractBreakingChangeError
@@ -44,23 +47,28 @@ from dbt.events.types import (
SeedExceedsLimitSamePath,
SeedExceedsLimitAndPathChanged,
SeedExceedsLimitChecksumChanged,
UnversionedBreakingChange,
)
from dbt.events.contextvars import set_log_contextvars
from dbt.flags import get_flags
from dbt.node_types import ModelLanguage, NodeType, AccessType
from dbt_semantic_interfaces.call_parameter_sets import FilterCallParameterSets
from dbt_semantic_interfaces.references import (
EntityReference,
MeasureReference,
LinkableElementReference,
SemanticModelReference,
TimeDimensionReference,
)
from dbt_semantic_interfaces.references import MetricReference as DSIMetricReference
from dbt_semantic_interfaces.type_enums import MetricType, TimeGranularity
from dbt_semantic_interfaces.parsing.where_filter_parser import WhereFilterParser
from dbt_semantic_interfaces.type_enums import (
ConversionCalculationType,
MetricType,
TimeGranularity,
)
from .model_config import (
NodeConfig,
ModelConfig,
SeedConfig,
TestConfig,
SourceConfig,
@@ -69,6 +77,7 @@ from .model_config import (
EmptySnapshotConfig,
SnapshotConfig,
SemanticModelConfig,
SavedQueryConfig,
)
@@ -229,6 +238,7 @@ class ColumnInfo(AdditionalPropertiesMixin, ExtensibleDbtClassMixin, Replaceable
@dataclass
class Contract(dbtClassMixin, Replaceable):
enforced: bool = False
alias_types: bool = True
checksum: Optional[str] = None
@@ -554,19 +564,20 @@ class CompiledNode(ParsedNode):
@dataclass
class AnalysisNode(CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Analysis]})
resource_type: Literal[NodeType.Analysis]
@dataclass
class HookNode(CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Operation]})
resource_type: Literal[NodeType.Operation]
index: Optional[int] = None
@dataclass
class ModelNode(CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Model]})
resource_type: Literal[NodeType.Model]
access: AccessType = AccessType.Protected
config: ModelConfig = field(default_factory=ModelConfig)
constraints: List[ModelLevelConstraint] = field(default_factory=list)
version: Optional[NodeVersion] = None
latest_version: Optional[NodeVersion] = None
@@ -589,7 +600,7 @@ class ModelNode(CompiledNode):
name=args.name,
package_name=args.package_name,
unique_id=unique_id,
fqn=[args.package_name, args.name],
fqn=args.fqn,
version=args.version,
latest_version=args.latest_version,
relation_name=args.relation_name,
@@ -603,7 +614,7 @@ class ModelNode(CompiledNode):
path="",
unrendered_config=unrendered_config,
depends_on=DependsOn(nodes=args.depends_on_nodes),
config=NodeConfig(enabled=args.enabled),
config=ModelConfig(enabled=args.enabled),
)
@property
@@ -625,6 +636,18 @@ class ModelNode(CompiledNode):
def materialization_enforces_constraints(self) -> bool:
return self.config.materialized in ["table", "incremental"]
def same_contents(self, old, adapter_type) -> bool:
return super().same_contents(old, adapter_type) and self.same_ref_representation(old)
def same_ref_representation(self, old) -> bool:
return (
# Changing the latest_version may break downstream unpinned refs
self.latest_version == old.latest_version
# Changes to access or deprecation_date may lead to ref-related parsing errors
and self.access == old.access
and self.deprecation_date == old.deprecation_date
)
def build_contract_checksum(self):
# We don't need to construct the checksum if the model does not
# have contract enforced, because it won't be used.
@@ -669,11 +692,11 @@ class ModelNode(CompiledNode):
# These are the categories of breaking changes:
contract_enforced_disabled: bool = False
columns_removed: List[str] = []
column_type_changes: List[Tuple[str, str, str]] = []
enforced_column_constraint_removed: List[Tuple[str, str]] = [] # column, constraint_type
enforced_model_constraint_removed: List[
Tuple[str, List[str]]
] = [] # constraint_type, columns
column_type_changes: List[Dict[str, str]] = []
enforced_column_constraint_removed: List[
Dict[str, str]
] = [] # column_name, constraint_type
enforced_model_constraint_removed: List[Dict[str, Any]] = [] # constraint_type, columns
materialization_changed: List[str] = []
if old.contract.enforced is True and self.contract.enforced is False:
@@ -695,11 +718,11 @@ class ModelNode(CompiledNode):
# Has this column's data type changed?
elif old_value.data_type != self.columns[old_key].data_type:
column_type_changes.append(
(
str(old_value.name),
str(old_value.data_type),
str(self.columns[old_key].data_type),
)
{
"column_name": str(old_value.name),
"previous_column_type": str(old_value.data_type),
"current_column_type": str(self.columns[old_key].data_type),
}
)
# track if there are any column level constraints for the materialization check late
@@ -720,7 +743,11 @@ class ModelNode(CompiledNode):
and constraint_support[old_constraint.type] == ConstraintSupport.ENFORCED
):
enforced_column_constraint_removed.append(
(old_key, str(old_constraint.type))
{
"column_name": old_key,
"constraint_name": old_constraint.name,
"constraint_type": ConstraintType(old_constraint.type),
}
)
# Now compare the model level constraints
@@ -731,7 +758,11 @@ class ModelNode(CompiledNode):
and constraint_support[old_constraint.type] == ConstraintSupport.ENFORCED
):
enforced_model_constraint_removed.append(
(str(old_constraint.type), old_constraint.columns)
{
"constraint_name": old_constraint.name,
"constraint_type": ConstraintType(old_constraint.type),
"columns": old_constraint.columns,
}
)
# Check for relevant materialization changes.
@@ -745,7 +776,8 @@ class ModelNode(CompiledNode):
# If a column has been added, it will be missing in the old.columns, and present in self.columns
# That's a change (caught by the different checksums), but not a breaking change
# Did we find any changes that we consider breaking? If so, that's an error
# Did we find any changes that we consider breaking? If there's an enforced contract, that's
# a warning unless the model is versioned, then it's an error.
if (
contract_enforced_disabled
or columns_removed
@@ -754,32 +786,89 @@ class ModelNode(CompiledNode):
or enforced_column_constraint_removed
or materialization_changed
):
raise (
ContractBreakingChangeError(
contract_enforced_disabled=contract_enforced_disabled,
columns_removed=columns_removed,
column_type_changes=column_type_changes,
enforced_column_constraint_removed=enforced_column_constraint_removed,
enforced_model_constraint_removed=enforced_model_constraint_removed,
materialization_changed=materialization_changed,
breaking_changes = []
if contract_enforced_disabled:
breaking_changes.append(
"Contract enforcement was removed: Previously, this model had an enforced contract. It is no longer configured to enforce its contract, and this is a breaking change."
)
if columns_removed:
columns_removed_str = "\n - ".join(columns_removed)
breaking_changes.append(f"Columns were removed: \n - {columns_removed_str}")
if column_type_changes:
column_type_changes_str = "\n - ".join(
[
f"{c['column_name']} ({c['previous_column_type']} -> {c['current_column_type']})"
for c in column_type_changes
]
)
breaking_changes.append(
f"Columns with data_type changes: \n - {column_type_changes_str}"
)
if enforced_column_constraint_removed:
column_constraint_changes_str = "\n - ".join(
[
f"'{c['constraint_name'] if c['constraint_name'] is not None else c['constraint_type']}' constraint on column {c['column_name']}"
for c in enforced_column_constraint_removed
]
)
breaking_changes.append(
f"Enforced column level constraints were removed: \n - {column_constraint_changes_str}"
)
if enforced_model_constraint_removed:
model_constraint_changes_str = "\n - ".join(
[
f"'{c['constraint_name'] if c['constraint_name'] is not None else c['constraint_type']}' constraint on columns {c['columns']}"
for c in enforced_model_constraint_removed
]
)
breaking_changes.append(
f"Enforced model level constraints were removed: \n - {model_constraint_changes_str}"
)
if materialization_changed:
materialization_changes_str = (
f"{materialization_changed[0]} -> {materialization_changed[1]}"
)
breaking_changes.append(
f"Materialization changed with enforced constraints: \n - {materialization_changes_str}"
)
if self.version is None:
warn_or_error(
UnversionedBreakingChange(
contract_enforced_disabled=contract_enforced_disabled,
columns_removed=columns_removed,
column_type_changes=column_type_changes,
enforced_column_constraint_removed=enforced_column_constraint_removed,
enforced_model_constraint_removed=enforced_model_constraint_removed,
breaking_changes=breaking_changes,
model_name=self.name,
model_file_path=self.original_file_path,
),
node=self,
)
)
else:
raise (
ContractBreakingChangeError(
breaking_changes=breaking_changes,
node=self,
)
)
# Otherwise, though we didn't find any *breaking* changes, the contract has still changed -- same_contract: False
else:
return False
# Otherwise, the contract has changed -- same_contract: False
return False
# TODO: rm?
@dataclass
class RPCNode(CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.RPCCall]})
resource_type: Literal[NodeType.RPCCall]
@dataclass
class SqlNode(CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.SqlOperation]})
resource_type: Literal[NodeType.SqlOperation]
# ====================================
@@ -789,7 +878,7 @@ class SqlNode(CompiledNode):
@dataclass
class SeedNode(ParsedNode): # No SQLDefaults!
resource_type: NodeType = field(metadata={"restrict": [NodeType.Seed]})
resource_type: Literal[NodeType.Seed]
config: SeedConfig = field(default_factory=SeedConfig)
# seeds need the root_path because the contents are not loaded initially
# and we need the root_path to load the seed later
@@ -915,7 +1004,7 @@ class TestShouldStoreFailures:
@dataclass
class SingularTestNode(TestShouldStoreFailures, CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Test]})
resource_type: Literal[NodeType.Test]
# Was not able to make mypy happy and keep the code working. We need to
# refactor the various configs.
config: TestConfig = field(default_factory=TestConfig) # type: ignore
@@ -951,7 +1040,7 @@ class HasTestMetadata(dbtClassMixin):
@dataclass
class GenericTestNode(TestShouldStoreFailures, CompiledNode, HasTestMetadata):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Test]})
resource_type: Literal[NodeType.Test]
column_name: Optional[str] = None
file_key_name: Optional[str] = None
# Was not able to make mypy happy and keep the code working. We need to
@@ -984,13 +1073,13 @@ class IntermediateSnapshotNode(CompiledNode):
# uses a regular node config, which the snapshot parser will then convert
# into a full ParsedSnapshotNode after rendering. Note: it currently does
# not work to set snapshot config in schema files because of the validation.
resource_type: NodeType = field(metadata={"restrict": [NodeType.Snapshot]})
resource_type: Literal[NodeType.Snapshot]
config: EmptySnapshotConfig = field(default_factory=EmptySnapshotConfig)
@dataclass
class SnapshotNode(CompiledNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Snapshot]})
resource_type: Literal[NodeType.Snapshot]
config: SnapshotConfig
defer_relation: Optional[DeferRelation] = None
@@ -1003,7 +1092,7 @@ class SnapshotNode(CompiledNode):
@dataclass
class Macro(BaseNode):
macro_sql: str
resource_type: NodeType = field(metadata={"restrict": [NodeType.Macro]})
resource_type: Literal[NodeType.Macro]
depends_on: MacroDependsOn = field(default_factory=MacroDependsOn)
description: str = ""
meta: Dict[str, Any] = field(default_factory=dict)
@@ -1033,7 +1122,7 @@ class Macro(BaseNode):
@dataclass
class Documentation(BaseNode):
block_contents: str
resource_type: NodeType = field(metadata={"restrict": [NodeType.Documentation]})
resource_type: Literal[NodeType.Documentation]
@property
def search_name(self):
@@ -1064,7 +1153,7 @@ class UnpatchedSourceDefinition(BaseNode):
source: UnparsedSourceDefinition
table: UnparsedSourceTableDefinition
fqn: List[str]
resource_type: NodeType = field(metadata={"restrict": [NodeType.Source]})
resource_type: Literal[NodeType.Source]
patch_path: Optional[str] = None
def get_full_source_name(self):
@@ -1109,7 +1198,7 @@ class ParsedSourceMandatory(GraphNode, HasRelationMetadata):
source_description: str
loader: str
identifier: str
resource_type: NodeType = field(metadata={"restrict": [NodeType.Source]})
resource_type: Literal[NodeType.Source]
@dataclass
@@ -1219,8 +1308,8 @@ class SourceDefinition(NodeInfoMixin, ParsedSourceMandatory):
return []
@property
def has_freshness(self):
return bool(self.freshness) and self.loaded_at_field is not None
def has_freshness(self) -> bool:
return bool(self.freshness)
@property
def search_name(self):
@@ -1236,7 +1325,7 @@ class SourceDefinition(NodeInfoMixin, ParsedSourceMandatory):
class Exposure(GraphNode):
type: ExposureType
owner: Owner
resource_type: NodeType = field(metadata={"restrict": [NodeType.Exposure]})
resource_type: Literal[NodeType.Exposure]
description: str = ""
label: Optional[str] = None
maturity: Optional[MaturityType] = None
@@ -1315,20 +1404,13 @@ class Exposure(GraphNode):
# ====================================
@dataclass
class WhereFilter(dbtClassMixin):
where_sql_template: str
@property
def call_parameter_sets(self) -> FilterCallParameterSets:
return WhereFilterParser.parse_call_parameter_sets(self.where_sql_template)
@dataclass
class MetricInputMeasure(dbtClassMixin):
name: str
filter: Optional[WhereFilter] = None
filter: Optional[WhereFilterIntersection] = None
alias: Optional[str] = None
join_to_timespine: bool = False
fill_nulls_with: Optional[int] = None
def measure_reference(self) -> MeasureReference:
return MeasureReference(element_name=self.name)
@@ -1346,7 +1428,7 @@ class MetricTimeWindow(dbtClassMixin):
@dataclass
class MetricInput(dbtClassMixin):
name: str
filter: Optional[WhereFilter] = None
filter: Optional[WhereFilterIntersection] = None
alias: Optional[str] = None
offset_window: Optional[MetricTimeWindow] = None
offset_to_grain: Optional[TimeGranularity] = None
@@ -1358,6 +1440,16 @@ class MetricInput(dbtClassMixin):
return DSIMetricReference(element_name=self.alias or self.name)
@dataclass
class ConversionTypeParams(dbtClassMixin):
base_measure: MetricInputMeasure
conversion_measure: MetricInputMeasure
entity: str
calculation: ConversionCalculationType = ConversionCalculationType.CONVERSION_RATE
window: Optional[MetricTimeWindow] = None
constant_properties: Optional[List[ConstantPropertyInput]] = None
@dataclass
class MetricTypeParams(dbtClassMixin):
measure: Optional[MetricInputMeasure] = None
@@ -1368,6 +1460,7 @@ class MetricTypeParams(dbtClassMixin):
window: Optional[MetricTimeWindow] = None
grain_to_date: Optional[TimeGranularity] = None
metrics: Optional[List[MetricInput]] = None
conversion_type_params: Optional[ConversionTypeParams] = None
@dataclass
@@ -1383,9 +1476,9 @@ class Metric(GraphNode):
label: str
type: MetricType
type_params: MetricTypeParams
filter: Optional[WhereFilter] = None
filter: Optional[WhereFilterIntersection] = None
metadata: Optional[SourceFileMetadata] = None
resource_type: NodeType = field(metadata={"restrict": [NodeType.Metric]})
resource_type: Literal[NodeType.Metric]
meta: Dict[str, Any] = field(default_factory=dict)
tags: List[str] = field(default_factory=list)
config: MetricConfig = field(default_factory=MetricConfig)
@@ -1468,7 +1561,7 @@ class Metric(GraphNode):
class Group(BaseNode):
name: str
owner: Owner
resource_type: NodeType = field(metadata={"restrict": [NodeType.Group]})
resource_type: Literal[NodeType.Group]
# ====================================
@@ -1489,6 +1582,7 @@ class SemanticModel(GraphNode):
model: str
node_relation: Optional[NodeRelation]
description: Optional[str] = None
label: Optional[str] = None
defaults: Optional[Defaults] = None
entities: Sequence[Entity] = field(default_factory=list)
measures: Sequence[Measure] = field(default_factory=list)
@@ -1498,6 +1592,9 @@ class SemanticModel(GraphNode):
refs: List[RefArgs] = field(default_factory=list)
created_at: float = field(default_factory=lambda: time.time())
config: SemanticModelConfig = field(default_factory=SemanticModelConfig)
unrendered_config: Dict[str, Any] = field(default_factory=dict)
primary_entity: Optional[str] = None
group: Optional[str] = None
@property
def entity_references(self) -> List[LinkableElementReference]:
@@ -1568,17 +1665,157 @@ class SemanticModel(GraphNode):
measure is not None
), f"No measure with name ({measure_reference.element_name}) in semantic_model with name ({self.name})"
if self.defaults is not None:
default_agg_time_dimesion = self.defaults.agg_time_dimension
default_agg_time_dimension = (
self.defaults.agg_time_dimension if self.defaults is not None else None
)
agg_time_dimension_name = measure.agg_time_dimension or default_agg_time_dimesion
agg_time_dimension_name = measure.agg_time_dimension or default_agg_time_dimension
assert agg_time_dimension_name is not None, (
f"Aggregation time dimension for measure {measure.name} is not set! This should either be set directly on "
f"the measure specification in the model, or else defaulted to the primary time dimension in the data "
f"source containing the measure."
f"Aggregation time dimension for measure {measure.name} on semantic model {self.name} is not set! "
"To fix this either specify a default `agg_time_dimension` for the semantic model or define an "
"`agg_time_dimension` on the measure directly."
)
return TimeDimensionReference(element_name=agg_time_dimension_name)
@property
def primary_entity_reference(self) -> Optional[EntityReference]:
return (
EntityReference(element_name=self.primary_entity)
if self.primary_entity is not None
else None
)
def same_model(self, old: "SemanticModel") -> bool:
return self.model == old.same_model
def same_node_relation(self, old: "SemanticModel") -> bool:
return self.node_relation == old.node_relation
def same_description(self, old: "SemanticModel") -> bool:
return self.description == old.description
def same_defaults(self, old: "SemanticModel") -> bool:
return self.defaults == old.defaults
def same_entities(self, old: "SemanticModel") -> bool:
return self.entities == old.entities
def same_dimensions(self, old: "SemanticModel") -> bool:
return self.dimensions == old.dimensions
def same_measures(self, old: "SemanticModel") -> bool:
return self.measures == old.measures
def same_config(self, old: "SemanticModel") -> bool:
return self.config == old.config
def same_primary_entity(self, old: "SemanticModel") -> bool:
return self.primary_entity == old.primary_entity
def same_group(self, old: "SemanticModel") -> bool:
return self.group == old.group
def same_contents(self, old: Optional["SemanticModel"]) -> bool:
# existing when it didn't before is a change!
# metadata/tags changes are not "changes"
if old is None:
return True
return (
self.same_model(old)
and self.same_node_relation(old)
and self.same_description(old)
and self.same_defaults(old)
and self.same_entities(old)
and self.same_dimensions(old)
and self.same_measures(old)
and self.same_config(old)
and self.same_primary_entity(old)
and self.same_group(old)
and True
)
# ====================================
# SavedQuery and related classes
# ====================================
@dataclass
class SavedQueryMandatory(GraphNode):
query_params: QueryParams
exports: List[Export]
@dataclass
class SavedQuery(NodeInfoMixin, SavedQueryMandatory):
description: Optional[str] = None
label: Optional[str] = None
metadata: Optional[SourceFileMetadata] = None
config: SavedQueryConfig = field(default_factory=SavedQueryConfig)
unrendered_config: Dict[str, Any] = field(default_factory=dict)
group: Optional[str] = None
depends_on: DependsOn = field(default_factory=DependsOn)
created_at: float = field(default_factory=lambda: time.time())
refs: List[RefArgs] = field(default_factory=list)
@property
def metrics(self) -> List[str]:
return self.query_params.metrics
@property
def depends_on_nodes(self):
return self.depends_on.nodes
def same_metrics(self, old: "SavedQuery") -> bool:
return self.query_params.metrics == old.query_params.metrics
def same_group_by(self, old: "SavedQuery") -> bool:
return self.query_params.group_by == old.query_params.group_by
def same_description(self, old: "SavedQuery") -> bool:
return self.description == old.description
def same_where(self, old: "SavedQuery") -> bool:
return self.query_params.where == old.query_params.where
def same_label(self, old: "SavedQuery") -> bool:
return self.label == old.label
def same_config(self, old: "SavedQuery") -> bool:
return self.config == old.config
def same_group(self, old: "SavedQuery") -> bool:
return self.group == old.group
def same_exports(self, old: "SavedQuery") -> bool:
if len(self.exports) != len(old.exports):
return False
# exports should be in the same order, so we zip them for easy iteration
for (old_export, new_export) in zip(old.exports, self.exports):
if not new_export.same_contents(old_export):
return False
return True
def same_contents(self, old: Optional["SavedQuery"]) -> bool:
# existing when it didn't before is a change!
# metadata/tags changes are not "changes"
if old is None:
return True
return (
self.same_metrics(old)
and self.same_group_by(old)
and self.same_description(old)
and self.same_where(old)
and self.same_label(old)
and self.same_config(old)
and self.same_group(old)
and True
)
# ====================================
# Patches
@@ -1646,6 +1883,7 @@ GraphMemberNode = Union[
ResultNode,
Exposure,
Metric,
SavedQuery,
SemanticModel,
]

View File

@@ -0,0 +1,53 @@
from __future__ import annotations
from dataclasses import dataclass
from dbt.contracts.graph.semantic_layer_common import WhereFilterIntersection
from dbt.dataclass_schema import dbtClassMixin
from dbt_semantic_interfaces.type_enums.export_destination_type import ExportDestinationType
from typing import List, Optional
@dataclass
class ExportConfig(dbtClassMixin):
"""Nested configuration attributes for exports."""
export_as: ExportDestinationType
schema_name: Optional[str] = None
alias: Optional[str] = None
@dataclass
class Export(dbtClassMixin):
"""Configuration for writing query results to a table."""
name: str
config: ExportConfig
def same_name(self, old: Export) -> bool:
return self.name == old.name
def same_export_as(self, old: Export) -> bool:
return self.config.export_as == old.config.export_as
def same_schema_name(self, old: Export) -> bool:
return self.config.schema_name == old.config.schema_name
def same_alias(self, old: Export) -> bool:
return self.config.alias == old.config.alias
def same_contents(self, old: Export) -> bool:
return (
self.same_name(old)
and self.same_export_as(old)
and self.same_schema_name(old)
and self.same_alias(old)
)
@dataclass
class QueryParams(dbtClassMixin):
"""The query parameters for the saved query"""
metrics: List[str]
group_by: List[str]
where: Optional[WhereFilterIntersection]

View File

@@ -0,0 +1,23 @@
from dataclasses import dataclass
from dbt.dataclass_schema import dbtClassMixin
from dbt_semantic_interfaces.call_parameter_sets import FilterCallParameterSets
from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import WhereFilterParser
from typing import List, Sequence, Tuple
@dataclass
class WhereFilter(dbtClassMixin):
where_sql_template: str
@property
def call_parameter_sets(self) -> FilterCallParameterSets:
return WhereFilterParser.parse_call_parameter_sets(self.where_sql_template)
@dataclass
class WhereFilterIntersection(dbtClassMixin):
where_filters: List[WhereFilter]
@property
def filter_expression_parameter_sets(self) -> Sequence[Tuple[str, FilterCallParameterSets]]:
raise NotImplementedError

View File

@@ -2,6 +2,7 @@ from dbt_semantic_interfaces.implementations.metric import PydanticMetric
from dbt_semantic_interfaces.implementations.project_configuration import (
PydanticProjectConfiguration,
)
from dbt_semantic_interfaces.implementations.saved_query import PydanticSavedQuery
from dbt_semantic_interfaces.implementations.semantic_manifest import PydanticSemanticManifest
from dbt_semantic_interfaces.implementations.semantic_model import PydanticSemanticModel
from dbt_semantic_interfaces.implementations.time_spine_table_configuration import (
@@ -20,7 +21,7 @@ from dbt.exceptions import ParsingError
class SemanticManifest:
def __init__(self, manifest):
def __init__(self, manifest) -> None:
self.manifest = manifest
def validate(self) -> bool:
@@ -71,6 +72,11 @@ class SemanticManifest:
for metric in self.manifest.metrics.values():
pydantic_semantic_manifest.metrics.append(PydanticMetric.parse_obj(metric.to_dict()))
for saved_query in self.manifest.saved_queries.values():
pydantic_semantic_manifest.saved_queries.append(
PydanticSavedQuery.parse_obj(saved_query.to_dict())
)
# Look for time-spine table model and create time spine table configuration
if self.manifest.semantic_models:
# Get model for time_spine_table

View File

@@ -66,6 +66,7 @@ class Dimension(dbtClassMixin):
name: str
type: DimensionType
description: Optional[str] = None
label: Optional[str] = None
is_partition: bool = False
type_params: Optional[DimensionTypeParams] = None
expr: Optional[str] = None
@@ -100,6 +101,7 @@ class Entity(dbtClassMixin):
name: str
type: EntityType
description: Optional[str] = None
label: Optional[str] = None
role: Optional[str] = None
expr: Optional[str] = None
@@ -136,6 +138,7 @@ class Measure(dbtClassMixin):
name: str
agg: AggregationType
description: Optional[str] = None
label: Optional[str] = None
create_metric: bool = False
expr: Optional[str] = None
agg_params: Optional[MeasureAggregationParameters] = None

View File

@@ -19,11 +19,12 @@ import dbt.helper_types # noqa:F401
from dbt.exceptions import CompilationError, ParsingError, DbtInternalError
from dbt.dataclass_schema import dbtClassMixin, StrEnum, ExtensibleDbtClassMixin, ValidationError
from dbt_semantic_interfaces.type_enums import ConversionCalculationType
from dataclasses import dataclass, field
from datetime import timedelta
from pathlib import Path
from typing import Optional, List, Union, Dict, Any, Sequence
from typing import Optional, List, Union, Dict, Any, Sequence, Literal
@dataclass
@@ -49,31 +50,18 @@ class HasCode(dbtClassMixin):
@dataclass
class UnparsedMacro(UnparsedBaseNode, HasCode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Macro]})
resource_type: Literal[NodeType.Macro]
@dataclass
class UnparsedGenericTest(UnparsedBaseNode, HasCode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Macro]})
resource_type: Literal[NodeType.Macro]
@dataclass
class UnparsedNode(UnparsedBaseNode, HasCode):
name: str
resource_type: NodeType = field(
metadata={
"restrict": [
NodeType.Model,
NodeType.Analysis,
NodeType.Test,
NodeType.Snapshot,
NodeType.Operation,
NodeType.Seed,
NodeType.RPCCall,
NodeType.SqlOperation,
]
}
)
resource_type: NodeType
@property
def search_name(self):
@@ -82,7 +70,7 @@ class UnparsedNode(UnparsedBaseNode, HasCode):
@dataclass
class UnparsedRunHook(UnparsedNode):
resource_type: NodeType = field(metadata={"restrict": [NodeType.Operation]})
resource_type: Literal[NodeType.Operation]
index: Optional[int] = None
@@ -163,14 +151,9 @@ class UnparsedVersion(dbtClassMixin):
def __lt__(self, other):
try:
v = type(other.v)(self.v)
return v < other.v
return float(self.v) < float(other.v)
except ValueError:
try:
other_v = type(self.v)(other.v)
return self.v < other_v
except ValueError:
return str(self.v) < str(other.v)
return str(self.v) < str(other.v)
@property
def include_exclude(self) -> dbt.helper_types.IncludeExclude:
@@ -220,7 +203,7 @@ class UnparsedModelUpdate(UnparsedNodeUpdate):
versions: Sequence[UnparsedVersion] = field(default_factory=list)
deprecation_date: Optional[datetime.datetime] = None
def __post_init__(self):
def __post_init__(self) -> None:
if self.latest_version:
version_values = [version.v for version in self.versions]
if self.latest_version not in version_values:
@@ -228,7 +211,7 @@ class UnparsedModelUpdate(UnparsedNodeUpdate):
f"latest_version: {self.latest_version} is not one of model '{self.name}' versions: {version_values} "
)
seen_versions: set[str] = set()
seen_versions = set()
for version in self.versions:
if str(version.v) in seen_versions:
raise ParsingError(
@@ -600,19 +583,39 @@ class MetricTime(dbtClassMixin, Mergeable):
@dataclass
class UnparsedMetricInputMeasure(dbtClassMixin):
name: str
filter: Optional[str] = None
filter: Optional[Union[str, List[str]]] = None
alias: Optional[str] = None
join_to_timespine: bool = False
fill_nulls_with: Optional[int] = None
@dataclass
class UnparsedMetricInput(dbtClassMixin):
name: str
filter: Optional[str] = None
filter: Optional[Union[str, List[str]]] = None
alias: Optional[str] = None
offset_window: Optional[str] = None
offset_to_grain: Optional[str] = None # str is really a TimeGranularity Enum
@dataclass
class ConstantPropertyInput(dbtClassMixin):
base_property: str
conversion_property: str
@dataclass
class UnparsedConversionTypeParams(dbtClassMixin):
base_measure: Union[UnparsedMetricInputMeasure, str]
conversion_measure: Union[UnparsedMetricInputMeasure, str]
entity: str
calculation: str = (
ConversionCalculationType.CONVERSION_RATE.value
) # ConversionCalculationType Enum
window: Optional[str] = None
constant_properties: Optional[List[ConstantPropertyInput]] = None
@dataclass
class UnparsedMetricTypeParams(dbtClassMixin):
measure: Optional[Union[UnparsedMetricInputMeasure, str]] = None
@@ -622,6 +625,7 @@ class UnparsedMetricTypeParams(dbtClassMixin):
window: Optional[str] = None
grain_to_date: Optional[str] = None # str is really a TimeGranularity Enum
metrics: Optional[List[Union[UnparsedMetricInput, str]]] = None
conversion_type_params: Optional[UnparsedConversionTypeParams] = None
@dataclass
@@ -631,7 +635,7 @@ class UnparsedMetric(dbtClassMixin):
type: str
type_params: UnparsedMetricTypeParams
description: str = ""
filter: Optional[str] = None
filter: Optional[Union[str, List[str]]] = None
# metadata: Optional[Unparsedetadata] = None # TODO
meta: Dict[str, Any] = field(default_factory=dict)
tags: List[str] = field(default_factory=list)
@@ -681,6 +685,7 @@ class UnparsedEntity(dbtClassMixin):
name: str
type: str # EntityType enum
description: Optional[str] = None
label: Optional[str] = None
role: Optional[str] = None
expr: Optional[str] = None
@@ -689,7 +694,7 @@ class UnparsedEntity(dbtClassMixin):
class UnparsedNonAdditiveDimension(dbtClassMixin):
name: str
window_choice: str # AggregationType enum
window_groupings: List[str]
window_groupings: List[str] = field(default_factory=list)
@dataclass
@@ -697,10 +702,12 @@ class UnparsedMeasure(dbtClassMixin):
name: str
agg: str # actually an enum
description: Optional[str] = None
label: Optional[str] = None
expr: Optional[Union[str, bool, int]] = None
agg_params: Optional[MeasureAggregationParameters] = None
non_additive_dimension: Optional[UnparsedNonAdditiveDimension] = None
agg_time_dimension: Optional[str] = None
create_metric: bool = False
@dataclass
@@ -714,6 +721,7 @@ class UnparsedDimension(dbtClassMixin):
name: str
type: str # actually an enum
description: Optional[str] = None
label: Optional[str] = None
is_partition: bool = False
type_params: Optional[UnparsedDimensionTypeParams] = None
expr: Optional[str] = None
@@ -723,11 +731,39 @@ class UnparsedDimension(dbtClassMixin):
class UnparsedSemanticModel(dbtClassMixin):
name: str
model: str # looks like "ref(...)"
config: Dict[str, Any] = field(default_factory=dict)
description: Optional[str] = None
label: Optional[str] = None
defaults: Optional[Defaults] = None
entities: List[UnparsedEntity] = field(default_factory=list)
measures: List[UnparsedMeasure] = field(default_factory=list)
dimensions: List[UnparsedDimension] = field(default_factory=list)
primary_entity: Optional[str] = None
@dataclass
class UnparsedQueryParams(dbtClassMixin):
metrics: List[str] = field(default_factory=list)
group_by: List[str] = field(default_factory=list)
where: Optional[Union[str, List[str]]] = None
@dataclass
class UnparsedExport(dbtClassMixin):
"""Configuration for writing query results to a table."""
name: str
config: Dict[str, Any] = field(default_factory=dict)
@dataclass
class UnparsedSavedQuery(dbtClassMixin):
name: str
query_params: UnparsedQueryParams
description: Optional[str] = None
label: Optional[str] = None
exports: List[UnparsedExport] = field(default_factory=list)
config: Dict[str, Any] = field(default_factory=dict)
def normalize_date(d: Optional[datetime.date]) -> Optional[datetime.datetime]:

View File

@@ -4,13 +4,14 @@ from dbt.helper_types import NoValue
from dbt.dataclass_schema import (
dbtClassMixin,
ValidationError,
HyphenatedDbtClassMixin,
ExtensibleDbtClassMixin,
register_pattern,
dbtMashConfig,
)
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Union, Any
from typing import Optional, List, Dict, Union, Any, ClassVar
from typing_extensions import Annotated
from mashumaro.types import SerializableType
from mashumaro.jsonschema.annotations import Pattern
DEFAULT_SEND_ANONYMOUS_USAGE_STATS = True
@@ -25,12 +26,8 @@ class SemverString(str, SerializableType):
return SemverString(value)
# this supports full semver,
# but also allows for 2 group version numbers, (allows '1.0').
register_pattern(
SemverString,
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)?$", # noqa
)
# This supports full semver, but also allows for 2 group version numbers, (allows '1.0').
sem_ver_pattern = r"^(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)?$"
@dataclass
@@ -42,13 +39,14 @@ class Quoting(dbtClassMixin, Mergeable):
@dataclass
class Package(Replaceable, HyphenatedDbtClassMixin):
class Package(dbtClassMixin, Replaceable):
pass
@dataclass
class LocalPackage(Package):
local: str
unrendered: Dict[str, Any] = field(default_factory=dict)
# `float` also allows `int`, according to PEP484 (and jsonschema!)
@@ -59,14 +57,16 @@ RawVersion = Union[str, float]
class TarballPackage(Package):
tarball: str
name: str
unrendered: Dict[str, Any] = field(default_factory=dict)
@dataclass
class GitPackage(Package):
git: str
revision: Optional[RawVersion] = None
warn_unpinned: Optional[bool] = None
warn_unpinned: Optional[bool] = field(default=None, metadata={"alias": "warn-unpinned"})
subdirectory: Optional[str] = None
unrendered: Dict[str, Any] = field(default_factory=dict)
def get_revisions(self) -> List[str]:
if self.revision is None:
@@ -80,6 +80,7 @@ class RegistryPackage(Package):
package: str
version: Union[RawVersion, List[RawVersion]]
install_prerelease: Optional[bool] = False
unrendered: Dict[str, Any] = field(default_factory=dict)
def get_versions(self) -> List[str]:
if isinstance(self.version, list):
@@ -182,10 +183,13 @@ BANNED_PROJECT_NAMES = {
@dataclass
class Project(HyphenatedDbtClassMixin, Replaceable):
name: Identifier
class Project(dbtClassMixin, Replaceable):
_hyphenated: ClassVar[bool] = True
# Annotated is used by mashumaro for jsonschema generation
name: Annotated[Identifier, Pattern(r"^[^\d\W]\w*$")]
config_version: Optional[int] = 2
version: Optional[Union[SemverString, float]] = None
# Annotated is used by mashumaro for jsonschema generation
version: Optional[Union[Annotated[SemverString, Pattern(sem_ver_pattern)], float]] = None
project_root: Optional[str] = None
source_paths: Optional[List[str]] = None
model_paths: Optional[List[str]] = None
@@ -214,6 +218,8 @@ class Project(HyphenatedDbtClassMixin, Replaceable):
sources: Dict[str, Any] = field(default_factory=dict)
tests: Dict[str, Any] = field(default_factory=dict)
metrics: Dict[str, Any] = field(default_factory=dict)
semantic_models: Dict[str, Any] = field(default_factory=dict)
saved_queries: Dict[str, Any] = field(default_factory=dict)
exposures: Dict[str, Any] = field(default_factory=dict)
vars: Optional[Dict[str, Any]] = field(
default=None,
@@ -224,6 +230,36 @@ class Project(HyphenatedDbtClassMixin, Replaceable):
packages: List[PackageSpec] = field(default_factory=list)
query_comment: Optional[Union[QueryComment, NoValue, str]] = field(default_factory=NoValue)
restrict_access: bool = False
dbt_cloud: Optional[Dict[str, Any]] = None
class Config(dbtMashConfig):
# These tell mashumaro to use aliases for jsonschema and for "from_dict"
aliases = {
"config_version": "config-version",
"project_root": "project-root",
"source_paths": "source-paths",
"model_paths": "model-paths",
"macro_paths": "macro-paths",
"data_paths": "data-paths",
"seed_paths": "seed-paths",
"test_paths": "test-paths",
"analysis_paths": "analysis-paths",
"docs_paths": "docs-paths",
"asset_paths": "asset-paths",
"target_path": "target-path",
"snapshot_paths": "snapshot-paths",
"clean_targets": "clean-targets",
"log_path": "log-path",
"packages_install_path": "packages-install-path",
"on_run_start": "on-run-start",
"on_run_end": "on-run-end",
"require_dbt_version": "require-dbt-version",
"query_comment": "query-comment",
"restrict_access": "restrict-access",
"semantic_models": "semantic-models",
"saved_queries": "saved-queries",
"dbt_cloud": "dbt-cloud",
}
@classmethod
def validate(cls, data):
@@ -240,6 +276,10 @@ class Project(HyphenatedDbtClassMixin, Replaceable):
or not isinstance(entry["search_order"], list)
):
raise ValidationError(f"Invalid project dispatch config: {entry}")
if "dbt_cloud" in data and not isinstance(data["dbt_cloud"], dict):
raise ValidationError(
f"Invalid dbt_cloud config. Expected a 'dict' but got '{type(data['dbt_cloud'])}'"
)
@dataclass
@@ -267,10 +307,10 @@ class UserConfig(ExtensibleDbtClassMixin, Replaceable, UserConfigContract):
@dataclass
class ProfileConfig(HyphenatedDbtClassMixin, Replaceable):
profile_name: str = field(metadata={"preserve_underscore": True})
target_name: str = field(metadata={"preserve_underscore": True})
user_config: UserConfig = field(metadata={"preserve_underscore": True})
class ProfileConfig(dbtClassMixin, Replaceable):
profile_name: str
target_name: str
user_config: UserConfig
threads: int
# TODO: make this a dynamic union of some kind?
credentials: Optional[Dict[str, Any]]

View File

@@ -19,6 +19,7 @@ class RelationType(StrEnum):
CTE = "cte"
MaterializedView = "materialized_view"
External = "external"
Ephemeral = "ephemeral"
class ComponentName(StrEnum):

View File

@@ -1,13 +1,14 @@
import threading
from dbt.contracts.graph.unparsed import FreshnessThreshold
from dbt.contracts.graph.nodes import SourceDefinition, ResultNode
from dbt.contracts.graph.nodes import CompiledNode, SourceDefinition, ResultNode
from dbt.contracts.util import (
BaseArtifactMetadata,
ArtifactMixin,
VersionedSchema,
Replaceable,
schema_version,
get_artifact_schema_version,
)
from dbt.exceptions import DbtInternalError
from dbt.events.functions import fire_event
@@ -31,6 +32,8 @@ from typing import (
Optional,
Sequence,
Union,
Iterable,
Tuple,
)
from dbt.clients.system import write_json
@@ -59,7 +62,7 @@ class TimingInfo(dbtClassMixin):
# This is a context manager
class collect_timing_info:
def __init__(self, name: str, callback: Callable[[TimingInfo], None]):
def __init__(self, name: str, callback: Callable[[TimingInfo], None]) -> None:
self.timing_info = TimingInfo(name=name)
self.callback = callback
@@ -203,9 +206,15 @@ class RunResultsMetadata(BaseArtifactMetadata):
@dataclass
class RunResultOutput(BaseResult):
unique_id: str
compiled: Optional[bool]
compiled_code: Optional[str]
relation_name: Optional[str]
def process_run_result(result: RunResult) -> RunResultOutput:
compiled = isinstance(result.node, CompiledNode)
return RunResultOutput(
unique_id=result.node.unique_id,
status=result.status,
@@ -215,6 +224,9 @@ def process_run_result(result: RunResult) -> RunResultOutput:
message=result.message,
adapter_response=result.adapter_response,
failures=result.failures,
compiled=result.node.compiled if compiled else None, # type:ignore
compiled_code=result.node.compiled_code if compiled else None, # type:ignore
relation_name=result.node.relation_name if compiled else None, # type:ignore
)
@@ -237,7 +249,7 @@ class RunExecutionResult(
@dataclass
@schema_version("run-results", 4)
@schema_version("run-results", 5)
class RunResultsArtifact(ExecutionResult, ArtifactMixin):
results: Sequence[RunResultOutput]
args: Dict[str, Any] = field(default_factory=dict)
@@ -259,6 +271,27 @@ class RunResultsArtifact(ExecutionResult, ArtifactMixin):
)
return cls(metadata=meta, results=processed_results, elapsed_time=elapsed_time, args=args)
@classmethod
def compatible_previous_versions(cls) -> Iterable[Tuple[str, int]]:
return [
("run-results", 4),
]
@classmethod
def upgrade_schema_version(cls, data):
"""This overrides the "upgrade_schema_version" call in VersionedSchema (via
ArtifactMixin) to modify the dictionary passed in from earlier versions of the run_results."""
run_results_schema_version = get_artifact_schema_version(data)
# If less than the current version (v5), preprocess contents to match latest schema version
if run_results_schema_version <= 5:
# In v5, we added 'compiled' attributes to each result entry
# Going forward, dbt expects these to be populated
for result in data["results"]:
result["compiled"] = False
result["compiled_code"] = ""
result["relation_name"] = ""
return cls.from_dict(data)
def write(self, path: str):
write_json(path, self.to_dict(omit_none=False))

View File

@@ -1,13 +1,26 @@
from pathlib import Path
from .graph.manifest import WritableManifest
from .results import RunResultsArtifact
from .results import FreshnessExecutionResultArtifact
from typing import Optional
from dbt.contracts.graph.manifest import WritableManifest
from dbt.contracts.results import FreshnessExecutionResultArtifact
from dbt.contracts.results import RunResultsArtifact
from dbt.events.functions import fire_event
from dbt.events.types import WarnStateTargetEqual
from dbt.exceptions import IncompatibleSchemaError
def load_result_state(results_path) -> Optional[RunResultsArtifact]:
if results_path.exists() and results_path.is_file():
try:
return RunResultsArtifact.read_and_check_versions(str(results_path))
except IncompatibleSchemaError as exc:
exc.add_filename(str(results_path))
raise
return None
class PreviousState:
def __init__(self, state_path: Path, target_path: Path, project_root: Path):
def __init__(self, state_path: Path, target_path: Path, project_root: Path) -> None:
self.state_path: Path = state_path
self.target_path: Path = target_path
self.project_root: Path = project_root
@@ -16,6 +29,9 @@ class PreviousState:
self.sources: Optional[FreshnessExecutionResultArtifact] = None
self.sources_current: Optional[FreshnessExecutionResultArtifact] = None
if self.state_path == self.target_path:
fire_event(WarnStateTargetEqual(state_path=str(self.state_path)))
# Note: if state_path is absolute, project_root will be ignored.
manifest_path = self.project_root / self.state_path / "manifest.json"
if manifest_path.exists() and manifest_path.is_file():
@@ -26,12 +42,7 @@ class PreviousState:
raise
results_path = self.project_root / self.state_path / "run_results.json"
if results_path.exists() and results_path.is_file():
try:
self.results = RunResultsArtifact.read_and_check_versions(str(results_path))
except IncompatibleSchemaError as exc:
exc.add_filename(str(results_path))
raise
self.results = load_result_state(results_path)
sources_path = self.project_root / self.state_path / "sources.json"
if sources_path.exists() and sources_path.is_file():

View File

@@ -16,8 +16,10 @@ from dbt.dataclass_schema import dbtClassMixin
from dbt.dataclass_schema import (
ValidatedStringMixin,
ValidationError,
register_pattern,
)
from mashumaro.jsonschema import build_json_schema
from mashumaro.jsonschema.dialects import DRAFT_2020_12
import functools
SourceKey = Tuple[str, str]
@@ -90,7 +92,9 @@ class AdditionalPropertiesMixin:
cls_keys = cls._get_field_names()
new_dict = {}
for key, value in data.items():
if key not in cls_keys and key != "_extra":
# The pre-hook/post-hook mess hasn't been converted yet... That happens in
# the super().__pre_deserialize__ below...
if key not in cls_keys and key not in ["_extra", "pre-hook", "post-hook"]:
if "_extra" not in new_dict:
new_dict["_extra"] = {}
new_dict["_extra"][key] = value
@@ -192,11 +196,12 @@ class VersionedSchema(dbtClassMixin):
dbt_schema_version: ClassVar[SchemaVersion]
@classmethod
def json_schema(cls, embeddable: bool = False) -> Dict[str, Any]:
result = super().json_schema(embeddable=embeddable)
if not embeddable:
result["$id"] = str(cls.dbt_schema_version)
return result
@functools.lru_cache
def json_schema(cls) -> Dict[str, Any]:
json_schema_obj = build_json_schema(cls, dialect=DRAFT_2020_12, with_dialect_uri=True)
json_schema = json_schema_obj.to_dict()
json_schema["$id"] = str(cls.dbt_schema_version)
return json_schema
@classmethod
def is_compatible_version(cls, schema_version):
@@ -257,7 +262,29 @@ class ArtifactMixin(VersionedSchema, Writable, Readable):
raise DbtInternalError("Cannot call from_dict with no schema version!")
def get_artifact_schema_version(dct: dict) -> int:
schema_version = dct.get("metadata", {}).get("dbt_schema_version", None)
if not schema_version:
raise ValueError("Artifact is missing schema version")
# schema_version is in this format: https://schemas.getdbt.com/dbt/manifest/v10.json
# What the code below is doing:
# 1. Split on "/" v10.json
# 2. Split on "." v10
# 3. Skip first character 10
# 4. Convert to int
# TODO: If this gets more complicated, turn into a regex
return int(schema_version.split("/")[-1].split(".")[0][1:])
class Identifier(ValidatedStringMixin):
"""Our definition of a valid Identifier is the same as what's valid for an unquoted database table name.
That is:
1. It can contain a-z, A-Z, 0-9, and _
1. It cannot start with a number
"""
ValidationRegex = r"^[^\d\W]\w*$"
@classmethod
@@ -271,6 +298,3 @@ class Identifier(ValidatedStringMixin):
return False
return True
register_pattern(Identifier, r"^[^\d\W]\w*$")

View File

@@ -1,97 +1,123 @@
from typing import (
Type,
ClassVar,
cast,
)
from typing import ClassVar, cast, get_type_hints, List, Tuple, Dict, Any, Optional
import re
from dataclasses import fields
import jsonschema
from dataclasses import fields, Field
from enum import Enum
from datetime import datetime
from dateutil.parser import parse
from hologram import JsonSchemaMixin, FieldEncoder, ValidationError
# type: ignore
from mashumaro import DataClassDictMixin
from mashumaro.config import TO_DICT_ADD_OMIT_NONE_FLAG, BaseConfig as MashBaseConfig
from mashumaro.types import SerializableType, SerializationStrategy
from mashumaro.jsonschema import build_json_schema
import functools
class ValidationError(jsonschema.ValidationError):
pass
class DateTimeSerialization(SerializationStrategy):
def serialize(self, value):
def serialize(self, value) -> str:
out = value.isoformat()
# Assume UTC if timezone is missing
if value.tzinfo is None:
out += "Z"
return out
def deserialize(self, value):
def deserialize(self, value) -> datetime:
return value if isinstance(value, datetime) else parse(cast(str, value))
# This class pulls in both JsonSchemaMixin from Hologram and
# DataClassDictMixin from our fork of Mashumaro. The 'to_dict'
# and 'from_dict' methods come from Mashumaro. Building
# jsonschemas for every class and the 'validate' method
# come from Hologram.
class dbtClassMixin(DataClassDictMixin, JsonSchemaMixin):
class dbtMashConfig(MashBaseConfig):
code_generation_options = [
TO_DICT_ADD_OMIT_NONE_FLAG,
]
serialization_strategy = {
datetime: DateTimeSerialization(),
}
json_schema = {
"additionalProperties": False,
}
serialize_by_alias = True
# This class pulls in DataClassDictMixin from Mashumaro. The 'to_dict'
# and 'from_dict' methods come from Mashumaro.
class dbtClassMixin(DataClassDictMixin):
"""The Mixin adds methods to generate a JSON schema and
convert to and from JSON encodable dicts with validation
against the schema
"""
class Config(MashBaseConfig):
code_generation_options = [
TO_DICT_ADD_OMIT_NONE_FLAG,
]
serialization_strategy = {
datetime: DateTimeSerialization(),
}
_mapped_fields: ClassVar[Optional[Dict[Any, List[Tuple[Field, str]]]]] = None
# Config class used by Mashumaro
class Config(dbtMashConfig):
pass
_hyphenated: ClassVar[bool] = False
ADDITIONAL_PROPERTIES: ClassVar[bool] = False
# This is called by the mashumaro to_dict in order to handle
# nested classes.
# Munges the dict that's returned.
def __post_serialize__(self, dct):
if self._hyphenated:
new_dict = {}
for key in dct:
if "_" in key:
new_key = key.replace("_", "-")
new_dict[new_key] = dct[key]
else:
new_dict[key] = dct[key]
dct = new_dict
return dct
# This is called by the mashumaro _from_dict method, before
# performing the conversion to a dict
# This is called by the mashumaro from_dict in order to handle
# nested classes. We no longer do any munging here, but leaving here
# so that subclasses can leave super() in place for possible future needs.
@classmethod
def __pre_deserialize__(cls, data):
# `data` might not be a dict, e.g. for `query_comment`, which accepts
# a dict or a string; only snake-case for dict values.
if cls._hyphenated and isinstance(data, dict):
new_dict = {}
for key in data:
if "-" in key:
new_key = key.replace("-", "_")
new_dict[new_key] = data[key]
else:
new_dict[key] = data[key]
data = new_dict
return data
# This is used in the hologram._encode_field method, which calls
# a 'to_dict' method which does not have the same parameters in
# hologram and in mashumaro.
def _local_to_dict(self, **kwargs):
args = {}
if "omit_none" in kwargs:
args["omit_none"] = kwargs["omit_none"]
return self.to_dict(**args)
# This is called by the mashumaro to_dict in order to handle
# nested classes. We no longer do any munging here, but leaving here
# so that subclasses can leave super() in place for possible future needs.
def __post_serialize__(self, data):
return data
@classmethod
@functools.lru_cache
def json_schema(cls):
json_schema_obj = build_json_schema(cls)
json_schema = json_schema_obj.to_dict()
return json_schema
@classmethod
def validate(cls, data):
json_schema = cls.json_schema()
validator = jsonschema.Draft7Validator(json_schema)
error = next(iter(validator.iter_errors(data)), None)
if error is not None:
raise ValidationError.create_from(error) from error
# This method was copied from hologram. Used in model_config.py and relation.py
@classmethod
def _get_fields(cls) -> List[Tuple[Field, str]]:
if cls._mapped_fields is None:
cls._mapped_fields = {}
if cls.__name__ not in cls._mapped_fields:
mapped_fields = []
type_hints = get_type_hints(cls)
for f in fields(cls): # type: ignore
# Skip internal fields
if f.name.startswith("_"):
continue
# Note fields() doesn't resolve forward refs
f.type = type_hints[f.name]
# hologram used the "field_mapping" here, but we use the
# the field's metadata "alias". Since this method is mainly
# just used in merging config dicts, it mostly applies to
# pre-hook and post-hook.
field_name = f.metadata.get("alias", f.name)
mapped_fields.append((f, field_name))
cls._mapped_fields[cls.__name__] = mapped_fields
return cls._mapped_fields[cls.__name__]
# copied from hologram. Used in tests
@classmethod
def _get_field_names(cls):
return [element[1] for element in cls._get_fields()]
class ValidatedStringMixin(str, SerializableType):
@@ -130,38 +156,10 @@ class StrEnum(str, SerializableType, Enum):
return cls(value)
class HyphenatedDbtClassMixin(dbtClassMixin):
# used by from_dict/to_dict
_hyphenated: ClassVar[bool] = True
# used by jsonschema validation, _get_fields
@classmethod
def field_mapping(cls):
result = {}
for field in fields(cls):
skip = field.metadata.get("preserve_underscore")
if skip:
continue
if "_" in field.name:
result[field.name] = field.name.replace("_", "-")
return result
class ExtensibleDbtClassMixin(dbtClassMixin):
ADDITIONAL_PROPERTIES = True
# This is used by Hologram in jsonschema validation
def register_pattern(base_type: Type, pattern: str) -> None:
"""base_type should be a typing.NewType that should always have the given
regex pattern. That means that its underlying type ('__supertype__') had
better be a str!
"""
class PatternEncoder(FieldEncoder):
@property
def json_schema(self):
return {"type": "string", "pattern": pattern}
dbtClassMixin.register_field_encoders({base_type: PatternEncoder()})
class Config(dbtMashConfig):
json_schema = {
"additionalProperties": True,
}

View File

@@ -4,7 +4,7 @@ import functools
import tempfile
from contextlib import contextmanager
from pathlib import Path
from typing import List, Optional, Generic, TypeVar
from typing import List, Optional, Generic, TypeVar, Dict
from dbt.clients import system
from dbt.contracts.project import ProjectPackageMetadata
@@ -84,6 +84,10 @@ class PinnedPackage(BasePackage):
def nice_version_name(self):
raise NotImplementedError
@abc.abstractmethod
def to_dict(self) -> Dict[str, str]:
raise NotImplementedError
def fetch_metadata(self, project, renderer):
if not self._cached_metadata:
self._cached_metadata = self._fetch_metadata(project, renderer)

View File

@@ -1,5 +1,5 @@
import os
from typing import List, Optional
from typing import List, Optional, Dict
from dbt.clients import git, system
from dbt.config.project import PartialProject, Project
@@ -9,9 +9,9 @@ from dbt.contracts.project import (
GitPackage,
)
from dbt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path
from dbt.exceptions import ExecutableError, MultipleVersionGitDepsError
from dbt.exceptions import ExecutableError, MultipleVersionGitDepsError, scrub_secrets, env_secrets
from dbt.events.functions import fire_event, warn_or_error
from dbt.events.types import EnsureGitInstalled, DepsUnpinned
from dbt.events.types import EnsureGitInstalled, DepsUnpinned, DepsScrubbedPackageName
from dbt.utils import md5
@@ -20,13 +20,20 @@ def md5sum(s: str):
class GitPackageMixin:
def __init__(self, git: str) -> None:
def __init__(
self,
git: str,
git_unrendered: str,
subdirectory: Optional[str] = None,
) -> None:
super().__init__()
self.git = git
self.git_unrendered = git_unrendered
self.subdirectory = subdirectory
@property
def name(self):
return self.git
return f"{self.git}/{self.subdirectory}" if self.subdirectory else self.git
def source_type(self) -> str:
return "git"
@@ -36,15 +43,28 @@ class GitPinnedPackage(GitPackageMixin, PinnedPackage):
def __init__(
self,
git: str,
git_unrendered: str,
revision: str,
warn_unpinned: bool = True,
subdirectory: Optional[str] = None,
) -> None:
super().__init__(git)
super().__init__(git, git_unrendered, subdirectory)
self.revision = revision
self.warn_unpinned = warn_unpinned
self.subdirectory = subdirectory
self._checkout_name = md5sum(self.git)
self._checkout_name = md5sum(self.name)
def to_dict(self) -> Dict[str, str]:
git_scrubbed = scrub_secrets(self.git_unrendered, env_secrets())
if self.git_unrendered != git_scrubbed:
warn_or_error(DepsScrubbedPackageName(package_name=git_scrubbed))
ret = {
"git": git_scrubbed,
"revision": self.revision,
}
if self.subdirectory:
ret["subdirectory"] = self.subdirectory
return ret
def get_version(self):
return self.revision
@@ -82,8 +102,13 @@ class GitPinnedPackage(GitPackageMixin, PinnedPackage):
) -> ProjectPackageMetadata:
path = self._checkout()
# raise warning (or error) if this package is not pinned
if (self.revision == "HEAD" or self.revision in ("main", "master")) and self.warn_unpinned:
warn_or_error(DepsUnpinned(git=self.git))
warn_or_error(DepsUnpinned(revision=self.revision, git=self.git))
# now overwrite 'revision' with actual commit SHA
self.revision = git.get_current_sha(path)
partial = PartialProject.from_project_root(path)
return partial.render_package_metadata(renderer)
@@ -102,11 +127,12 @@ class GitUnpinnedPackage(GitPackageMixin, UnpinnedPackage[GitPinnedPackage]):
def __init__(
self,
git: str,
git_unrendered: str,
revisions: List[str],
warn_unpinned: bool = True,
subdirectory: Optional[str] = None,
) -> None:
super().__init__(git)
super().__init__(git, git_unrendered, subdirectory)
self.revisions = revisions
self.warn_unpinned = warn_unpinned
self.subdirectory = subdirectory
@@ -119,6 +145,7 @@ class GitUnpinnedPackage(GitPackageMixin, UnpinnedPackage[GitPinnedPackage]):
warn_unpinned = contract.warn_unpinned is not False
return cls(
git=contract.git,
git_unrendered=(contract.unrendered.get("git") or contract.git),
revisions=revisions,
warn_unpinned=warn_unpinned,
subdirectory=contract.subdirectory,
@@ -129,13 +156,21 @@ class GitUnpinnedPackage(GitPackageMixin, UnpinnedPackage[GitPinnedPackage]):
other = self.git[:-4]
else:
other = self.git + ".git"
return [self.git, other]
if self.subdirectory:
git_name = f"{self.git}/{self.subdirectory}"
other = f"{other}/{self.subdirectory}"
else:
git_name = self.git
return [git_name, other]
def incorporate(self, other: "GitUnpinnedPackage") -> "GitUnpinnedPackage":
warn_unpinned = self.warn_unpinned and other.warn_unpinned
return GitUnpinnedPackage(
git=self.git,
git_unrendered=self.git_unrendered,
revisions=self.revisions + other.revisions,
warn_unpinned=warn_unpinned,
subdirectory=self.subdirectory,
@@ -146,10 +181,10 @@ class GitUnpinnedPackage(GitPackageMixin, UnpinnedPackage[GitPinnedPackage]):
if len(requested) == 0:
requested = {"HEAD"}
elif len(requested) > 1:
raise MultipleVersionGitDepsError(self.git, requested)
raise MultipleVersionGitDepsError(self.name, requested)
return GitPinnedPackage(
git=self.git,
git_unrendered=self.git_unrendered,
revision=requested.pop(),
warn_unpinned=self.warn_unpinned,
subdirectory=self.subdirectory,

View File

@@ -1,4 +1,5 @@
import shutil
from typing import Dict
from dbt.clients import system
from dbt.deps.base import PinnedPackage, UnpinnedPackage
@@ -29,6 +30,11 @@ class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
def __init__(self, local: str) -> None:
super().__init__(local)
def to_dict(self) -> Dict[str, str]:
return {
"local": self.local,
}
def get_version(self):
return None
@@ -51,19 +57,15 @@ class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
src_path = self.resolve_path(project)
dest_path = self.get_installation_path(project, renderer)
can_create_symlink = system.supports_symlinks()
if system.path_exists(dest_path):
if not system.path_is_symlink(dest_path):
system.rmdir(dest_path)
else:
system.remove_file(dest_path)
if can_create_symlink:
try:
fire_event(DepsCreatingLocalSymlink())
system.make_symlink(src_path, dest_path)
else:
except OSError:
fire_event(DepsSymlinkNotAvailable())
shutil.copytree(src_path, dest_path)

View File

@@ -1,4 +1,4 @@
from typing import List
from typing import List, Dict
from dbt import semver
from dbt.flags import get_flags
@@ -40,6 +40,12 @@ class RegistryPinnedPackage(RegistryPackageMixin, PinnedPackage):
def name(self):
return self.package
def to_dict(self) -> Dict[str, str]:
return {
"package": self.package,
"version": self.version,
}
def source_type(self):
return "hub"

View File

@@ -135,3 +135,15 @@ def resolve_packages(
resolved = final.resolved()
_check_for_duplicate_project_names(resolved, project, renderer)
return resolved
def resolve_lock_packages(packages: List[PackageContract]) -> List[PinnedPackage]:
lock_packages = PackageListing.from_contracts(packages)
final = PackageListing()
for package in lock_packages:
final.incorporate(package)
resolved = final.resolved()
return resolved

View File

@@ -1,11 +1,17 @@
from typing import Dict
from dbt.contracts.project import RegistryPackageMetadata, TarballPackage
from dbt.deps.base import PinnedPackage, UnpinnedPackage
from dbt.exceptions import scrub_secrets, env_secrets
from dbt.events.functions import warn_or_error
from dbt.events.types import DepsScrubbedPackageName
class TarballPackageMixin:
def __init__(self, tarball: str) -> None:
def __init__(self, tarball: str, tarball_unrendered: str) -> None:
super().__init__()
self.tarball = tarball
self.tarball_unrendered = tarball_unrendered
@property
def name(self):
@@ -16,8 +22,8 @@ class TarballPackageMixin:
class TarballPinnedPackage(TarballPackageMixin, PinnedPackage):
def __init__(self, tarball: str, package: str) -> None:
super().__init__(tarball)
def __init__(self, tarball: str, tarball_unrendered: str, package: str) -> None:
super().__init__(tarball, tarball_unrendered)
# setup to recycle RegistryPinnedPackage fns
self.package = package
self.version = "tarball"
@@ -26,6 +32,15 @@ class TarballPinnedPackage(TarballPackageMixin, PinnedPackage):
def name(self):
return self.package
def to_dict(self) -> Dict[str, str]:
tarball_scrubbed = scrub_secrets(self.tarball_unrendered, env_secrets())
if self.tarball_unrendered != tarball_scrubbed:
warn_or_error(DepsScrubbedPackageName(package_name=tarball_scrubbed))
return {
"tarball": tarball_scrubbed,
"name": self.package,
}
def get_version(self):
return self.version
@@ -56,19 +71,28 @@ class TarballUnpinnedPackage(TarballPackageMixin, UnpinnedPackage[TarballPinnedP
def __init__(
self,
tarball: str,
tarball_unrendered: str,
package: str,
) -> None:
super().__init__(tarball)
super().__init__(tarball, tarball_unrendered)
# setup to recycle RegistryPinnedPackage fns
self.package = package
self.version = "tarball"
@classmethod
def from_contract(cls, contract: TarballPackage) -> "TarballUnpinnedPackage":
return cls(tarball=contract.tarball, package=contract.name)
return cls(
tarball=contract.tarball,
tarball_unrendered=(contract.unrendered.get("tarball") or contract.tarball),
package=contract.name,
)
def incorporate(self, other: "TarballUnpinnedPackage") -> "TarballUnpinnedPackage":
return TarballUnpinnedPackage(tarball=self.tarball, package=self.package)
return TarballUnpinnedPackage(
tarball=self.tarball, tarball_unrendered=self.tarball_unrendered, package=self.package
)
def resolved(self) -> TarballPinnedPackage:
return TarballPinnedPackage(tarball=self.tarball, package=self.package)
return TarballPinnedPackage(
tarball=self.tarball, tarball_unrendered=self.tarball_unrendered, package=self.package
)

View File

@@ -1,7 +1,8 @@
import traceback
from dataclasses import dataclass
from dbt.events.functions import fire_event
from dbt.events.functions import fire_event, EVENT_MANAGER
from dbt.events.contextvars import get_node_info
from dbt.events.event_handler import set_package_logging
from dbt.events.types import (
AdapterEventDebug,
AdapterEventInfo,
@@ -15,32 +16,32 @@ from dbt.events.types import (
class AdapterLogger:
name: str
def debug(self, msg, *args):
def debug(self, msg, *args) -> None:
event = AdapterEventDebug(
name=self.name, base_msg=str(msg), args=list(args), node_info=get_node_info()
)
fire_event(event)
def info(self, msg, *args):
def info(self, msg, *args) -> None:
event = AdapterEventInfo(
name=self.name, base_msg=str(msg), args=list(args), node_info=get_node_info()
)
fire_event(event)
def warning(self, msg, *args):
def warning(self, msg, *args) -> None:
event = AdapterEventWarning(
name=self.name, base_msg=str(msg), args=list(args), node_info=get_node_info()
)
fire_event(event)
def error(self, msg, *args):
def error(self, msg, *args) -> None:
event = AdapterEventError(
name=self.name, base_msg=str(msg), args=list(args), node_info=get_node_info()
)
fire_event(event)
# The default exc_info=True is what makes this method different
def exception(self, msg, *args):
def exception(self, msg, *args) -> None:
exc_info = str(traceback.format_exc())
event = AdapterEventError(
name=self.name,
@@ -51,8 +52,15 @@ class AdapterLogger:
)
fire_event(event)
def critical(self, msg, *args):
def critical(self, msg, *args) -> None:
event = AdapterEventError(
name=self.name, base_msg=str(msg), args=list(args), node_info=get_node_info()
)
fire_event(event)
@staticmethod
def set_adapter_dependency_log_level(package_name, level):
"""By default, dbt suppresses non-dbt package logs. This method allows
you to set the log level for a specific package.
"""
set_package_logging(package_name, level, EVENT_MANAGER)

View File

@@ -37,7 +37,7 @@ def get_pid() -> int:
return os.getpid()
# in theory threads can change so we don't cache them.
# in theory threads can change, so we don't cache them.
def get_thread_name() -> str:
return threading.current_thread().name
@@ -55,7 +55,7 @@ class EventLevel(str, Enum):
class BaseEvent:
"""BaseEvent for proto message generated python events"""
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
class_name = type(self).__name__
msg_cls = getattr(types_pb2, class_name)
if class_name == "Formatting" and len(args) > 0:
@@ -100,9 +100,12 @@ class BaseEvent:
self.pb_msg, preserving_proto_field_name=True, including_default_value_fields=True
)
def to_json(self):
def to_json(self) -> str:
return MessageToJson(
self.pb_msg, preserving_proto_field_name=True, including_default_valud_fields=True
self.pb_msg,
preserving_proto_field_name=True,
including_default_value_fields=True,
indent=None,
)
def level_tag(self) -> EventLevel:

View File

@@ -0,0 +1,40 @@
import logging
from typing import Union
from dbt.events.base_types import EventLevel
from dbt.events.types import Note
from dbt.events.eventmgr import IEventManager
_log_level_to_event_level_map = {
logging.DEBUG: EventLevel.DEBUG,
logging.INFO: EventLevel.INFO,
logging.WARN: EventLevel.WARN,
logging.WARNING: EventLevel.WARN,
logging.ERROR: EventLevel.ERROR,
logging.CRITICAL: EventLevel.ERROR,
}
class DbtEventLoggingHandler(logging.Handler):
"""A logging handler that wraps the EventManager
This allows non-dbt packages to log to the dbt event stream.
All logs are generated as "Note" events.
"""
def __init__(self, event_manager: IEventManager, level):
super().__init__(level)
self.event_manager = event_manager
def emit(self, record: logging.LogRecord):
note = Note(msg=record.getMessage())
level = _log_level_to_event_level_map[record.levelno]
self.event_manager.fire_event(e=note, level=level)
def set_package_logging(package_name: str, log_level: Union[str, int], event_mgr: IEventManager):
"""Attach dbt's custom logging handler to the package's logger."""
log = logging.getLogger(package_name)
log.setLevel(log_level)
event_handler = DbtEventLoggingHandler(event_manager=event_mgr, level=log_level)
log.addHandler(event_handler)

View File

@@ -1,183 +1,10 @@
import os
from colorama import Style
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
import json
import logging
from logging.handlers import RotatingFileHandler
import threading
import traceback
from typing import Any, Callable, List, Optional, TextIO
from typing import Callable, List, Optional, Protocol, Tuple
from uuid import uuid4
from dbt.events.format import timestamp_to_datetime_string
from dbt.events.base_types import BaseEvent, EventLevel, msg_from_base_event, EventMsg
# A Filter is a function which takes a BaseEvent and returns True if the event
# should be logged, False otherwise.
Filter = Callable[[EventMsg], bool]
# Default filter which logs every event
def NoFilter(_: EventMsg) -> bool:
return True
# A Scrubber removes secrets from an input string, returning a sanitized string.
Scrubber = Callable[[str], str]
# Provide a pass-through scrubber implementation, also used as a default
def NoScrubber(s: str) -> str:
return s
class LineFormat(Enum):
PlainText = 1
DebugText = 2
Json = 3
# Map from dbt event levels to python log levels
_log_level_map = {
EventLevel.DEBUG: 10,
EventLevel.TEST: 10,
EventLevel.INFO: 20,
EventLevel.WARN: 30,
EventLevel.ERROR: 40,
}
# We need this function for now because the numeric log severity levels in
# Python do not match those for logbook, so we have to explicitly call the
# correct function by name.
def send_to_logger(l, level: str, log_line: str):
if level == "test":
l.debug(log_line)
elif level == "debug":
l.debug(log_line)
elif level == "info":
l.info(log_line)
elif level == "warn":
l.warning(log_line)
elif level == "error":
l.error(log_line)
else:
raise AssertionError(
f"While attempting to log {log_line}, encountered the unhandled level: {level}"
)
@dataclass
class LoggerConfig:
name: str
filter: Filter = NoFilter
scrubber: Scrubber = NoScrubber
line_format: LineFormat = LineFormat.PlainText
level: EventLevel = EventLevel.WARN
use_colors: bool = False
output_stream: Optional[TextIO] = None
output_file_name: Optional[str] = None
logger: Optional[Any] = None
class _Logger:
def __init__(self, event_manager: "EventManager", config: LoggerConfig) -> None:
self.name: str = config.name
self.filter: Filter = config.filter
self.scrubber: Scrubber = config.scrubber
self.level: EventLevel = config.level
self.event_manager: EventManager = event_manager
self._python_logger: Optional[logging.Logger] = config.logger
if config.output_stream is not None:
stream_handler = logging.StreamHandler(config.output_stream)
self._python_logger = self._get_python_log_for_handler(stream_handler)
if config.output_file_name:
file_handler = RotatingFileHandler(
filename=str(config.output_file_name),
encoding="utf8",
maxBytes=10 * 1024 * 1024, # 10 mb
backupCount=5,
)
self._python_logger = self._get_python_log_for_handler(file_handler)
def _get_python_log_for_handler(self, handler: logging.Handler):
log = logging.getLogger(self.name)
log.setLevel(_log_level_map[self.level])
handler.setFormatter(logging.Formatter(fmt="%(message)s"))
log.handlers.clear()
log.propagate = False
log.addHandler(handler)
return log
def create_line(self, msg: EventMsg) -> str:
raise NotImplementedError()
def write_line(self, msg: EventMsg):
line = self.create_line(msg)
if self._python_logger is not None:
send_to_logger(self._python_logger, msg.info.level, line)
def flush(self):
if self._python_logger is not None:
for handler in self._python_logger.handlers:
handler.flush()
class _TextLogger(_Logger):
def __init__(self, event_manager: "EventManager", config: LoggerConfig) -> None:
super().__init__(event_manager, config)
self.use_colors = config.use_colors
self.use_debug_format = config.line_format == LineFormat.DebugText
def create_line(self, msg: EventMsg) -> str:
return self.create_debug_line(msg) if self.use_debug_format else self.create_info_line(msg)
def create_info_line(self, msg: EventMsg) -> str:
ts: str = datetime.utcnow().strftime("%H:%M:%S")
scrubbed_msg: str = self.scrubber(msg.info.msg) # type: ignore
return f"{self._get_color_tag()}{ts} {scrubbed_msg}"
def create_debug_line(self, msg: EventMsg) -> str:
log_line: str = ""
# Create a separator if this is the beginning of an invocation
# TODO: This is an ugly hack, get rid of it if we can
ts: str = timestamp_to_datetime_string(msg.info.ts)
if msg.info.name == "MainReportVersion":
separator = 30 * "="
log_line = f"\n\n{separator} {ts} | {self.event_manager.invocation_id} {separator}\n"
scrubbed_msg: str = self.scrubber(msg.info.msg) # type: ignore
level = msg.info.level
log_line += (
f"{self._get_color_tag()}{ts} [{level:<5}]{self._get_thread_name()} {scrubbed_msg}"
)
return log_line
def _get_color_tag(self) -> str:
return "" if not self.use_colors else Style.RESET_ALL
def _get_thread_name(self) -> str:
thread_name = ""
if threading.current_thread().name:
thread_name = threading.current_thread().name
thread_name = thread_name[:10]
thread_name = thread_name.ljust(10, " ")
thread_name = f" [{thread_name}]:"
return thread_name
class _JsonLogger(_Logger):
def create_line(self, msg: EventMsg) -> str:
from dbt.events.functions import msg_to_dict
msg_dict = msg_to_dict(msg)
raw_log_line = json.dumps(msg_dict, sort_keys=True)
line = self.scrubber(raw_log_line) # type: ignore
return line
from dbt.events.logger import LoggerConfig, _Logger, _TextLogger, _JsonLogger, LineFormat
class EventManager:
@@ -205,15 +32,36 @@ class EventManager:
for callback in self.callbacks:
callback(msg)
def add_logger(self, config: LoggerConfig):
def add_logger(self, config: LoggerConfig) -> None:
logger = (
_JsonLogger(self, config)
if config.line_format == LineFormat.Json
else _TextLogger(self, config)
_JsonLogger(config) if config.line_format == LineFormat.Json else _TextLogger(config)
)
logger.event_manager = self
self.loggers.append(logger)
def flush(self):
def flush(self) -> None:
for logger in self.loggers:
logger.flush()
class IEventManager(Protocol):
callbacks: List[Callable[[EventMsg], None]]
invocation_id: str
loggers: List[_Logger]
def fire_event(self, e: BaseEvent, level: Optional[EventLevel] = None) -> None:
...
def add_logger(self, config: LoggerConfig) -> None:
...
class TestEventManager(IEventManager):
def __init__(self) -> None:
self.event_history: List[Tuple[BaseEvent, Optional[EventLevel]]] = []
self.loggers = []
def fire_event(self, e: BaseEvent, level: Optional[EventLevel] = None) -> None:
self.event_history.append((e, level))
def add_logger(self, config: LoggerConfig) -> None:
raise NotImplementedError()

View File

@@ -44,13 +44,13 @@ def _pluralize(string: Union[str, NodeType]) -> str:
return convert.pluralize()
def pluralize(count, string: Union[str, NodeType]):
def pluralize(count, string: Union[str, NodeType]) -> str:
pluralized: str = str(string)
if count != 1:
pluralized = _pluralize(string)
return f"{count} {pluralized}"
def timestamp_to_datetime_string(ts):
def timestamp_to_datetime_string(ts) -> str:
timestamp_dt = datetime.fromtimestamp(ts.seconds + ts.nanos / 1e9)
return timestamp_dt.strftime("%H:%M:%S.%f")

View File

@@ -1,8 +1,9 @@
from dbt.constants import METADATA_ENV_PREFIX
from dbt.events.base_types import BaseEvent, EventLevel, EventMsg
from dbt.events.eventmgr import EventManager, LoggerConfig, LineFormat, NoFilter
from dbt.events.helpers import env_secrets, scrub_secrets
from dbt.events.types import Formatting, Note
from dbt.events.eventmgr import EventManager, IEventManager
from dbt.events.logger import LoggerConfig, NoFilter, LineFormat
from dbt.exceptions import scrub_secrets, env_secrets
from dbt.events.types import Note
from dbt.flags import get_flags, ENABLE_LEGACY_LOGGER
from dbt.logger import GLOBAL_LOGGER, make_log_dir_if_missing
from functools import partial
@@ -13,6 +14,7 @@ from typing import Callable, Dict, List, Optional, TextIO
import uuid
from google.protobuf.json_format import MessageToDict
import dbt.utils
LOG_VERSION = 3
metadata_vars: Optional[Dict[str, str]] = None
@@ -67,7 +69,11 @@ def setup_event_logger(flags, callbacks: List[Callable[[EventMsg], None]] = [])
log_level_file = EventLevel.DEBUG if flags.DEBUG else EventLevel(flags.LOG_LEVEL_FILE)
EVENT_MANAGER.add_logger(
_get_logfile_config(
log_file, flags.USE_COLORS_FILE, log_file_format, log_level_file
log_file,
flags.USE_COLORS_FILE,
log_file_format,
log_level_file,
flags.LOG_FILE_MAX_BYTES,
)
)
@@ -89,7 +95,6 @@ def _get_stdout_config(
level: EventLevel,
log_cache_events: bool,
) -> LoggerConfig:
return LoggerConfig(
name="stdout_log",
level=level,
@@ -101,6 +106,7 @@ def _get_stdout_config(
log_cache_events,
line_format,
),
invocation_id=EVENT_MANAGER.invocation_id,
output_stream=sys.stdout,
)
@@ -110,14 +116,17 @@ def _stdout_filter(
line_format: LineFormat,
msg: EventMsg,
) -> bool:
return (msg.info.name not in ["CacheAction", "CacheDumpGraph"] or log_cache_events) and not (
line_format == LineFormat.Json and type(msg.data) == Formatting
)
return msg.info.name not in ["CacheAction", "CacheDumpGraph"] or log_cache_events
def _get_logfile_config(
log_path: str, use_colors: bool, line_format: LineFormat, level: EventLevel
log_path: str,
use_colors: bool,
line_format: LineFormat,
level: EventLevel,
log_file_max_bytes: int,
) -> LoggerConfig:
return LoggerConfig(
name="file_log",
line_format=line_format,
@@ -125,15 +134,15 @@ def _get_logfile_config(
level=level, # File log is *always* debug level
scrubber=env_scrubber,
filter=partial(_logfile_filter, bool(get_flags().LOG_CACHE_EVENTS), line_format),
invocation_id=EVENT_MANAGER.invocation_id,
output_file_name=log_path,
output_file_max_bytes=log_file_max_bytes,
)
def _logfile_filter(log_cache_events: bool, line_format: LineFormat, msg: EventMsg) -> bool:
return (
msg.info.code not in nofile_codes
and not (msg.info.name in ["CacheAction", "CacheDumpGraph"] and not log_cache_events)
and not (line_format == LineFormat.Json and type(msg.data) == Formatting)
return msg.info.code not in nofile_codes and not (
msg.info.name in ["CacheAction", "CacheDumpGraph"] and not log_cache_events
)
@@ -161,7 +170,7 @@ def env_scrubber(msg: str) -> str:
return scrub_secrets(msg, env_secrets())
def cleanup_event_logger():
def cleanup_event_logger() -> None:
# Reset to a no-op manager to release streams associated with logs. This is
# especially important for tests, since pytest replaces the stdout stream
# during test runs, and closes the stream after the test is over.
@@ -172,7 +181,7 @@ def cleanup_event_logger():
# Since dbt-rpc does not do its own log setup, and since some events can
# currently fire before logs can be configured by setup_event_logger(), we
# create a default configuration with default settings and no file output.
EVENT_MANAGER: EventManager = EventManager()
EVENT_MANAGER: IEventManager = EventManager()
EVENT_MANAGER.add_logger(
_get_logbook_log_config(False, True, False, False) # type: ignore
if ENABLE_LEGACY_LOGGER
@@ -186,12 +195,12 @@ _CAPTURE_STREAM: Optional[TextIO] = None
# used for integration tests
def capture_stdout_logs(stream: TextIO):
def capture_stdout_logs(stream: TextIO) -> None:
global _CAPTURE_STREAM
_CAPTURE_STREAM = stream
def stop_capture_stdout_logs():
def stop_capture_stdout_logs() -> None:
global _CAPTURE_STREAM
_CAPTURE_STREAM = None
@@ -200,7 +209,7 @@ def stop_capture_stdout_logs():
# the message may contain secrets which must be scrubbed at the usage site.
def msg_to_json(msg: EventMsg) -> str:
msg_dict = msg_to_dict(msg)
raw_log_line = json.dumps(msg_dict, sort_keys=True)
raw_log_line = json.dumps(msg_dict, sort_keys=True, cls=dbt.utils.ForgivingJSONEncoder)
return raw_log_line
@@ -225,7 +234,7 @@ def msg_to_dict(msg: EventMsg) -> dict:
return msg_dict
def warn_or_error(event, node=None):
def warn_or_error(event, node=None) -> None:
flags = get_flags()
if flags.WARN_ERROR or flags.WARN_ERROR_OPTIONS.includes(type(event).__name__):
@@ -263,7 +272,7 @@ def fire_event(e: BaseEvent, level: Optional[EventLevel] = None) -> None:
def get_metadata_vars() -> Dict[str, str]:
global metadata_vars
if metadata_vars is None:
if not metadata_vars:
metadata_vars = {
k[len(METADATA_ENV_PREFIX) :]: v
for k, v in os.environ.items()
@@ -285,3 +294,8 @@ def set_invocation_id() -> None:
# This is primarily for setting the invocation_id for separate
# commands in the dbt servers. It shouldn't be necessary for the CLI.
EVENT_MANAGER.invocation_id = str(uuid.uuid4())
def ctx_set_event_manager(event_manager: IEventManager) -> None:
global EVENT_MANAGER
EVENT_MANAGER = event_manager

View File

@@ -1,22 +1,6 @@
import os
from typing import List
from dbt.constants import SECRET_ENV_PREFIX
from datetime import datetime
def env_secrets() -> List[str]:
return [v for k, v in os.environ.items() if k.startswith(SECRET_ENV_PREFIX) and v.strip()]
def scrub_secrets(msg: str, secrets: List[str]) -> str:
scrubbed = str(msg)
for secret in secrets:
scrubbed = scrubbed.replace(secret, "*****")
return scrubbed
# This converts a datetime to a json format datetime string which
# is used in constructing protobuf message timestamps.
def datetime_to_json_string(dt: datetime) -> str:

180
core/dbt/events/logger.py Normal file
View File

@@ -0,0 +1,180 @@
import json
import logging
import threading
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from logging.handlers import RotatingFileHandler
from typing import Optional, TextIO, Any, Callable
from colorama import Style
import dbt.utils
from dbt.events.base_types import EventLevel, EventMsg
from dbt.events.format import timestamp_to_datetime_string
# A Filter is a function which takes a BaseEvent and returns True if the event
# should be logged, False otherwise.
Filter = Callable[[EventMsg], bool]
# Default filter which logs every event
def NoFilter(_: EventMsg) -> bool:
return True
# A Scrubber removes secrets from an input string, returning a sanitized string.
Scrubber = Callable[[str], str]
# Provide a pass-through scrubber implementation, also used as a default
def NoScrubber(s: str) -> str:
return s
class LineFormat(Enum):
PlainText = 1
DebugText = 2
Json = 3
# Map from dbt event levels to python log levels
_log_level_map = {
EventLevel.DEBUG: 10,
EventLevel.TEST: 10,
EventLevel.INFO: 20,
EventLevel.WARN: 30,
EventLevel.ERROR: 40,
}
# We need this function for now because the numeric log severity levels in
# Python do not match those for logbook, so we have to explicitly call the
# correct function by name.
def send_to_logger(l, level: str, log_line: str):
if level == "test":
l.debug(log_line)
elif level == "debug":
l.debug(log_line)
elif level == "info":
l.info(log_line)
elif level == "warn":
l.warning(log_line)
elif level == "error":
l.error(log_line)
else:
raise AssertionError(
f"While attempting to log {log_line}, encountered the unhandled level: {level}"
)
@dataclass
class LoggerConfig:
name: str
filter: Filter = NoFilter
scrubber: Scrubber = NoScrubber
line_format: LineFormat = LineFormat.PlainText
level: EventLevel = EventLevel.WARN
invocation_id: Optional[str] = None
use_colors: bool = False
output_stream: Optional[TextIO] = None
output_file_name: Optional[str] = None
output_file_max_bytes: Optional[int] = 10 * 1024 * 1024 # 10 mb
logger: Optional[Any] = None
class _Logger:
def __init__(self, config: LoggerConfig) -> None:
self.name: str = config.name
self.filter: Filter = config.filter
self.scrubber: Scrubber = config.scrubber
self.level: EventLevel = config.level
self.invocation_id: Optional[str] = config.invocation_id
self._python_logger: Optional[logging.Logger] = config.logger
if config.output_stream is not None:
stream_handler = logging.StreamHandler(config.output_stream)
self._python_logger = self._get_python_log_for_handler(stream_handler)
if config.output_file_name:
file_handler = RotatingFileHandler(
filename=str(config.output_file_name),
encoding="utf8",
maxBytes=config.output_file_max_bytes, # type: ignore
backupCount=5,
)
self._python_logger = self._get_python_log_for_handler(file_handler)
def _get_python_log_for_handler(self, handler: logging.Handler):
log = logging.getLogger(self.name)
log.setLevel(_log_level_map[self.level])
handler.setFormatter(logging.Formatter(fmt="%(message)s"))
log.handlers.clear()
log.propagate = False
log.addHandler(handler)
return log
def create_line(self, msg: EventMsg) -> str:
raise NotImplementedError()
def write_line(self, msg: EventMsg):
line = self.create_line(msg)
if self._python_logger is not None:
send_to_logger(self._python_logger, msg.info.level, line)
def flush(self):
if self._python_logger is not None:
for handler in self._python_logger.handlers:
handler.flush()
class _TextLogger(_Logger):
def __init__(self, config: LoggerConfig) -> None:
super().__init__(config)
self.use_colors = config.use_colors
self.use_debug_format = config.line_format == LineFormat.DebugText
def create_line(self, msg: EventMsg) -> str:
return self.create_debug_line(msg) if self.use_debug_format else self.create_info_line(msg)
def create_info_line(self, msg: EventMsg) -> str:
ts: str = datetime.utcnow().strftime("%H:%M:%S")
scrubbed_msg: str = self.scrubber(msg.info.msg) # type: ignore
return f"{self._get_color_tag()}{ts} {scrubbed_msg}"
def create_debug_line(self, msg: EventMsg) -> str:
log_line: str = ""
# Create a separator if this is the beginning of an invocation
# TODO: This is an ugly hack, get rid of it if we can
ts: str = timestamp_to_datetime_string(msg.info.ts)
if msg.info.name == "MainReportVersion":
separator = 30 * "="
log_line = f"\n\n{separator} {ts} | {self.invocation_id} {separator}\n"
scrubbed_msg: str = self.scrubber(msg.info.msg) # type: ignore
level = msg.info.level
log_line += (
f"{self._get_color_tag()}{ts} [{level:<5}]{self._get_thread_name()} {scrubbed_msg}"
)
return log_line
def _get_color_tag(self) -> str:
return "" if not self.use_colors else Style.RESET_ALL
def _get_thread_name(self) -> str:
thread_name = ""
if threading.current_thread().name:
thread_name = threading.current_thread().name
thread_name = thread_name[:10]
thread_name = thread_name.ljust(10, " ")
thread_name = f" [{thread_name}]:"
return thread_name
class _JsonLogger(_Logger):
def create_line(self, msg: EventMsg) -> str:
from dbt.events.functions import msg_to_dict
msg_dict = msg_to_dict(msg)
raw_log_line = json.dumps(msg_dict, sort_keys=True, cls=dbt.utils.ForgivingJSONEncoder)
line = self.scrubber(raw_log_line) # type: ignore
return line

View File

@@ -66,6 +66,27 @@ message ReferenceKeyMsg {
string identifier = 3;
}
//ColumnType
message ColumnType {
string column_name = 1;
string previous_column_type = 2;
string current_column_type = 3;
}
// ColumnConstraint
message ColumnConstraint {
string column_name = 1;
string constraint_name = 2;
string constraint_type = 3;
}
// ModelConstraint
message ModelConstraint {
string constraint_name = 1;
string constraint_type = 2;
repeated string columns = 3;
}
// GenericMessage, used for deserializing only
message GenericMessage {
EventInfo info = 1;
@@ -1248,6 +1269,46 @@ message SemanticValidationFailureMsg {
SemanticValidationFailure data = 2;
}
// I071
message UnversionedBreakingChange {
repeated string breaking_changes = 1;
string model_name = 2;
string model_file_path = 3;
bool contract_enforced_disabled = 4;
repeated string columns_removed = 5;
repeated ColumnType column_type_changes = 6;
repeated ColumnConstraint enforced_column_constraint_removed = 7;
repeated ModelConstraint enforced_model_constraint_removed = 8;
repeated string materialization_changed = 9;
}
message UnversionedBreakingChangeMsg {
EventInfo info = 1;
UnversionedBreakingChange data = 2;
}
// I072
message WarnStateTargetEqual {
string state_path = 1;
}
message WarnStateTargetEqualMsg {
EventInfo info = 1;
WarnStateTargetEqual data = 2;
}
// I073
message FreshnessConfigProblem {
string msg = 1;
}
message FreshnessConfigProblemMsg {
EventInfo info = 1;
FreshnessConfigProblem data = 2;
}
// M - Deps generation
@@ -1538,6 +1599,58 @@ message NoNodesForSelectionCriteriaMsg {
NoNodesForSelectionCriteria data = 2;
}
// M031
message DepsLockUpdating{
string lock_filepath = 1;
}
message DepsLockUpdatingMsg{
EventInfo info = 1;
DepsLockUpdating data = 2;
}
// M032
message DepsAddPackage{
string package_name = 1;
string version = 2;
string packages_filepath = 3;
}
message DepsAddPackageMsg{
EventInfo info = 1;
DepsAddPackage data = 2;
}
//M033
message DepsFoundDuplicatePackage{
map<string, string> removed_package = 1;
}
message DepsFoundDuplicatePackageMsg{
EventInfo info = 1;
DepsFoundDuplicatePackage data = 2;
}
//M034
message DepsVersionMissing{
string source = 1;
}
message DepsVersionMissingMsg{
EventInfo info = 1;
DepsVersionMissing data = 2;
}
//M035
message DepsScrubbedPackageName{
string package_name = 1;
}
message DepsScrubbedPackageNameMsg{
EventInfo info = 1;
DepsScrubbedPackageName data = 2;
}
// Q - Node execution
// Q001
@@ -1584,6 +1697,7 @@ message SeedHeaderMsg {
message SQLRunnerException {
string exc = 1;
string exc_info = 2;
NodeInfo node_info = 3;
}
message SQLRunnerExceptionMsg {
@@ -1650,6 +1764,7 @@ message LogSnapshotResult {
int32 total = 5;
float execution_time = 6;
map<string, string> cfg = 7;
string result_message = 8;
}
message LogSnapshotResultMsg {
@@ -1934,6 +2049,7 @@ message CatchableExceptionOnRunMsg {
message InternalErrorOnRun {
string build_path = 1;
string exc = 2;
NodeInfo node_info = 3;
}
message InternalErrorOnRunMsg {
@@ -1946,6 +2062,7 @@ message GenericExceptionOnRun {
string build_path = 1;
string unique_id = 2;
string exc = 3;
NodeInfo node_info = 4;
}
message GenericExceptionOnRunMsg {
@@ -2245,25 +2362,7 @@ message CheckNodeTestFailureMsg {
CheckNodeTestFailure data = 2;
}
// Z028
message FirstRunResultError {
string msg = 1;
}
message FirstRunResultErrorMsg {
EventInfo info = 1;
FirstRunResultError data = 2;
}
// Z029
message AfterFirstRunResultError {
string msg = 1;
}
message AfterFirstRunResultErrorMsg {
EventInfo info = 1;
AfterFirstRunResultError data = 2;
}
// Skipped Z028, Z029
// Z030
message EndOfRunSummary {
@@ -2426,3 +2525,24 @@ message NoteMsg {
EventInfo info = 1;
Note data = 2;
}
// Z051
message ResourceReport {
string command_name = 2;
bool command_success = 3;
float command_wall_clock_time = 4;
// The process_* metrics reflect the resource consumption of the process as
// a whole when the command completes. When dbt is being used as a library,
// these will reflect the resource consumption of the host process as a whole,
// rather than the resources used exclusively by the command.
float process_user_time = 5;
float process_kernel_time = 6;
int64 process_mem_max_rss = 7;
int64 process_in_blocks = 8;
int64 process_out_blocks = 9;
}
message ResourceReportMsg {
EventInfo info = 1;
ResourceReport data = 2;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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