Compare commits

...

2494 Commits

Author SHA1 Message Date
Github Build Bot
51ccbab1e2 Merge remote-tracking branch 'origin/releases/0.18.1' into dev/0.18.1 2020-10-13 21:42:49 +00:00
Github Build Bot
f6bab4adcf Release dbt v0.18.1 2020-10-13 21:31:54 +00:00
Jeremy Cohen
526ecee3da Merge pull request #2832 from fishtown-analytics/fix/colorama-upper-044
Set colorama upper bound to <0.4.4
2020-10-13 17:20:05 -04:00
Jeremy Cohen
1bc9815d53 Set colorama upper bound to <0.4.4 2020-10-13 16:26:10 -04:00
Github Build Bot
1aac869738 Merge remote-tracking branch 'origin/releases/0.18.1rc1' into dev/0.18.1 2020-10-01 16:52:51 +00:00
Github Build Bot
493554ea30 Release dbt v0.18.1rc1 2020-10-01 16:39:50 +00:00
Jeremy Cohen
139b353a28 Merge pull request #2796 from Foxtel-DnA/feature/6434-bq-retry-rate-limit
UPDATE _is_retryable() to handle BQ rateLimitExceeded
2020-09-30 13:09:11 -04:00
Jared Champion (SYD)
fc474a07d0 REBASED on dev/0.18.1; moved CHANGELOG entries 2020-09-30 10:57:03 +10:00
championj-foxtel
8fd8fa09a5 Merge pull request #1 from fishtown-analytics/dev/0.18.1
Dev/0.18.1
2020-09-30 09:56:27 +10:00
Github Build Bot
b2bd5a5548 Merge remote-tracking branch 'origin/releases/0.18.1b3' into dev/0.18.1 2020-09-25 20:20:21 +00:00
Github Build Bot
aa6b333e79 Release dbt v0.18.1b3 2020-09-25 20:05:31 +00:00
Jeremy Cohen
0cb9740535 Merge pull request #2789 from fishtown-analytics/fix/require-keyring
Fix: require keyring on snowflake
2020-09-25 15:00:12 -04:00
Jeremy Cohen
6b032b49fe Merge branch 'dev/0.18.1' into fix/require-keyring 2020-09-25 14:13:53 -04:00
Jeremy Cohen
35f78ee0f9 Merge pull request #2754 from aiguofer/include_external_tables_in_get_columns_in_relation
Include external tables in get_columns_in_relation redshift adapter
2020-09-25 13:25:06 -04:00
Jeremy Cohen
5ec36df7f0 Merge branch 'dev/0.18.1' into include_external_tables_in_get_columns_in_relation 2020-09-25 12:52:39 -04:00
Jeremy Cohen
f918fd65b6 Merge pull request #2766 from jweibel22/fix/redshift-iam-concurrency-issue
Give each redshift client their own boto session
2020-09-25 12:50:20 -04:00
Jeremy Cohen
d08a39483d PR feedback 2020-09-25 12:11:49 -04:00
Jeremy Cohen
9191f4ff2d Merge branch 'dev/0.18.1' into fix/redshift-iam-concurrency-issue 2020-09-25 12:10:33 -04:00
Jeremy Cohen
b4a83414ac Require optional dep (keyring) on snowflake 2020-09-24 15:49:35 -04:00
Jeremy Cohen
cb0e62576d Merge pull request #2732 from Mr-Nobody99/feature/add-snowflake-last-modified
Added last_altered query to Snowflake catalog macro
2020-09-24 15:29:04 -04:00
Alexander Kutz
e3f557406f Updated test/integration/029_docs_generate_test.py to reflect new stat 2020-09-23 11:08:08 -05:00
Alexander Kutz
a93e288d6a Added missing commaabove addition. 2020-09-22 17:48:48 -05:00
Alexander Kutz
8cf9311ced Changed 'BASE_TABLE' to 'BASE TABLE' 2020-09-22 16:04:30 -05:00
Alexander Kutz
713e781473 Reset branch against dev/0.8.1 and re-added changes.
udpated changelog.md
2020-09-22 14:54:56 -05:00
Github Build Bot
e265ab67c7 Merge remote-tracking branch 'origin/releases/0.18.1b2' into dev/0.18.1 2020-09-22 14:23:45 +00:00
Github Build Bot
fde1f13b4e Release dbt v0.18.1b2 2020-09-22 14:09:51 +00:00
Jeremy Cohen
9c3839c7e2 Merge pull request #2782 from fishtown-analytics/docs/0.18.1-exposures
[revised] dbt-docs changes for v0.18.1
2020-09-22 09:51:04 -04:00
Jeremy Cohen
c0fd702cc7 Rename reports --> exposures 2020-09-22 08:44:58 -04:00
Jacob Beck
429419c4af Merge pull request #2780 from fishtown-analytics/feature/rename-results-to-exposures
reports -> exposures
2020-09-21 15:15:11 -06:00
Jacob Beck
56ae20602d reports -> exposures 2020-09-21 14:46:48 -06:00
jweibel22
40c6499d3a Update CHANGELOG.md
Co-authored-by: Jacob Beck <beckjake@users.noreply.github.com>
2020-09-20 13:31:43 +02:00
Jimmy Rasmussen
3a78efd83c Add test cases to ensure default boto session is not used 2020-09-20 13:31:15 +02:00
Jimmy Rasmussen
eb33cf75e3 Add entry to CHANGELOG 2020-09-18 10:44:00 +02:00
Jimmy Rasmussen
863d8e6405 Give each redshift client their own boto session
since the boto session is not thread-safe, using the default session in a multi-threaded scenario will result in concurrency errors
2020-09-18 10:29:26 +02:00
Jacob Beck
1fc5a45b9e Merge pull request #2768 from fishtown-analytics/fix/makefile-on-macos
fix the new makefile on macos
2020-09-17 13:42:32 -06:00
Github Build Bot
7751fece35 Merge remote-tracking branch 'origin/releases/0.18.1b1' into dev/0.18.1 2020-09-17 19:09:23 +00:00
Github Build Bot
7670c42462 Release dbt v0.18.1b1 2020-09-17 18:54:44 +00:00
Jacob Beck
b72fc3cd25 fix the new makefile on macos 2020-09-17 11:47:06 -06:00
Jacob Beck
22c4d8fabe Merge pull request #2741 from heisencoder/fix/docker-testing-on-linux
Fix docker-based testing for Linux users
2020-09-16 13:39:02 -06:00
Jeremy Cohen
3485482460 Merge pull request #2760 from fishtown-analytics/docs/0.18.1-reports
dbt-docs changes for v0.18.1
2020-09-16 15:21:54 -04:00
Jeremy Cohen
ea5e5df5a3 Support reports in dbt-docs 2020-09-16 13:44:17 -04:00
Jacob Beck
f2caf2f1ff Merge pull request #2752 from fishtown-analytics/feature/reports
Feature: reports
2020-09-16 10:41:43 -06:00
Jacob Beck
2142e529ff PR feedback: make report selector more like source selector, remove reports from fqn slector
Make some corresponding fqn adjustments
Add dbt ls report output
Fix dbt ls source output
The default selector now also returns reports
Update tests
2020-09-16 07:23:36 -06:00
Diego Fernandez
9c24fc25f5 Add entry to CHANGELOG 2020-09-15 15:11:05 -06:00
Diego Fernandez
4f1a6d56c1 Include external tables in get_columns_in_relation redshift adapter 2020-09-15 15:09:55 -06:00
Jacob Beck
e96f4a5be6 got redshifted 2020-09-14 14:35:00 -06:00
Jacob Beck
4768ac5fda Fix and add new tests, update changelog 2020-09-14 14:35:00 -06:00
Jacob Beck
c91fcc527a add comparison/selector logic for reports 2020-09-14 12:24:00 -06:00
Jacob Beck
8520ff35b3 Add reports feature
Add ParsedReport/UnparsedReport
add report parser and report node logic to manifest/results/dbt ls/selectors
NonSourceNode -> ManifestNode
add GraphMemberNode type that includes reports
2020-09-14 11:36:31 -06:00
Jacob Beck
9b8a98f4ec Test quality of life/cleanup
remove unused test folders
move rpc tests from 048 to 100 for convenience
 - Migrating these to the test/rpc model is going to take work. In the
   interim, developers can now use `tests/integeration/0*` to run all non-rpc
   tests.
2020-09-14 10:26:02 -06:00
Jacob Beck
4bd4afaec7 bumpversion for 0.18.1 2020-09-11 14:21:03 -06:00
Matt Ball
69352d8414 Fix docker-based testing for Linux users
See https://github.com/fishtown-analytics/dbt/issues/2739

This change enables Linux users to run the dbt tests via the docker
image. It follows the recommendations from this article:
https://jtreminio.com/blog/running-docker-containers-as-current-host-user/

Notable changes:
*  Added new Makefile rule to generate a .env file that contains USER_ID
and GROUP_ID environment variables to the ID of the current user. This
is in turn used by docker-compose and the Dockerfile to make the Docker
image run as the current user. (Note that on Windows or Mac, this
behavior happens by default).
*  Reordered Dockerfile to allow for better caching of intermediate
images (i.e., put things that don't depend on ARGS earlier).
*  Bumped CircleCI's Dockerfile from version 7 to 9.  Jake rebuilt
9 off of this PR.
2020-09-11 11:15:18 -06:00
Jeremy Cohen
4a21ea6575 Merge pull request #2723 from tpilewicz/fix/freshness-logs
Feat(result logs): Use three logging levels
2020-09-09 15:03:06 -04:00
Thomas Pilewicz
86bbb9fe38 Add contributors section to 0.18.1 2020-09-09 18:50:39 +02:00
Thomas Pilewicz
4030d4fc20 Move logging levels changelog entry to 0.18.1 2020-09-09 18:50:27 +02:00
tpilewicz
182f69a9ec Merge pull request #2 from fishtown-analytics/dev/0.18.1 2020-09-09 18:43:45 +02:00
Gerda Shank
fb40efe4b7 Merge pull request #2733 from fishtown-analytics/fix/2539-comment-quoting
When column config says quote, use quotes in SQL to add comments
2020-09-08 14:40:27 -04:00
Gerda Shank
51b8e64972 When column config says quote, use quotes in SQL to add comments
Add separate test for column comments. Fix Snowflake catalog comments.
2020-09-04 16:44:19 -04:00
Github Build Bot
fa8a4f2020 Merge remote-tracking branch 'origin/releases/0.18.0' into dev/marian-anderson 2020-09-03 16:45:49 +00:00
Github Build Bot
481bdd56d3 Release dbt v0.18.0 2020-09-03 16:02:36 +00:00
Github Build Bot
1a9083ddb7 Merge remote-tracking branch 'origin/releases/0.18.0rc2' into dev/marian-anderson 2020-09-03 15:52:16 +00:00
Github Build Bot
9779f43620 Release dbt v0.18.0rc2 2020-09-03 15:49:09 +00:00
Jeremy Cohen
981535a1c3 Merge pull request #2734 from fishtown-analytics/docs/0.18.0-followup
dbt-docs changes for v0.18.0 (final)
2020-09-01 17:14:16 -04:00
Jeremy Cohen
e2fe6a8249 Add project-level overviews 2020-09-01 14:58:15 -04:00
Jeremy Cohen
a8347b7ada Add missing changelog entry 2020-09-01 14:57:47 -04:00
Jeremy Cohen
f55b257609 Merge pull request #2722 from genos/genos/fix-for-2347
fix for 2347
2020-09-01 08:50:04 -04:00
genos
81bf3dae5c add contributors to changelog 2020-08-31 23:48:49 -04:00
Graham
d0074f3411 Merge branch 'dev/marian-anderson' into genos/fix-for-2347 2020-08-31 09:30:52 -04:00
Gerda Shank
2cc2d971c6 Merge pull request #2727 from fishtown-analytics/fix/2197-long-table-names
Check Postgres relation name lengths and throw error when over 63
2020-08-28 09:31:23 -04:00
Gerda Shank
5830f5590e Tweak error message, reformat for flake8 2020-08-27 16:35:35 -04:00
Jacob Beck
75facebe80 Merge pull request #2726 from fishtown-analytics/fix/require-version-validation
Validate require-dbt-version before validating dbt_project.ymls chema
2020-08-27 10:01:13 -06:00
Jacob Beck
0130398e9f Update core/dbt/config/project.py
Co-authored-by: Kyle Wigley <kyle@fishtownanalytics.com>
2020-08-27 08:16:33 -06:00
Gerda Shank
22d9b86e9f update changelog for #2197 2020-08-26 14:15:01 -04:00
Gerda Shank
c87b671275 Use csv for data in test 063; tweak several lines 2020-08-26 13:57:03 -04:00
Jacob Beck
1eb5857811 add missing unit tests 2020-08-26 08:40:53 -06:00
Gerda Shank
5fc1cb39a6 Check Postgres relation name lengths and throw error when over 63 2020-08-26 10:28:37 -04:00
Jacob Beck
1f8e29276e move the require-dbt-version check to before parsing
update changelog
2020-08-25 13:17:00 -06:00
Thomas Pilewicz
cf02c7fd02 Fix(get_printable_result): return type hints 2020-08-25 16:52:21 +02:00
Thomas Pilewicz
5d93c64c0e Add logging levels to features of 0.18.0 2020-08-24 15:59:31 +02:00
Thomas Pilewicz
c738928ea3 Feat(result logs): Use three logging levels 2020-08-24 14:04:05 +02:00
genos
707310db64 update changelog 2020-08-23 17:40:36 -04:00
genos
59bf43dc1f Fix for #2347
**Introduction**

This PR attempts to fix #2347, wherein we wish `dbt` to complain about trying to install with a Python version < 3.6.

**Changes**

Per [the issue's suggestion](https://github.com/fishtown-analytics/dbt/issues/2347), I found every `setup.py` file I could:

```
-# If you have the fantastic `fd` utility installed:
fd setup.py
-# This also works
find . -name setup.py -print
```

Then to each of these, I added the following after the `import sys`:

```
if sys.version_info < (3, 6):
    print('Error: dbt does not support this version of Python.')
    print('Please upgrade to Python 3.6 or higher.')
    sys.exit(1)
```

**Testing**

I used the [`nix` package manager](https://nixos.org) to attempt installing this branch with both Python 2.7 and Python 3.8.

_Python 2.7_ fails as expected:

```
~/github/test2 ∃ cat default.nix
let
  pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/20.03.tar.gz") { };
  py = pkgs.python27Full.withPackages (p: [ p.setuptools ]);
in pkgs.mkShell {
  name = "python-2-env";
  buildInputs = [ py ];
}
~/github/test2 ∃ nix-shell --pure

[nix-shell:~/github/test2]$ python ../dbt/setup.py build
Error: dbt does not support this version of Python.
Please upgrade to Python 3.6 or higher.

[nix-shell:~/github/test2]$ echo $?
1
```

_Python 3.8_ still works:

```
~/github/test3 ∃ cat default.nix
let
  pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/20.03.tar.gz") { };
  py = pkgs.python38Full.withPackages (p: [ p.setuptools ]);
in pkgs.mkShell {
  name = "python-3-env";
  buildInputs = [ py ];
}
~/github/test3 ∃ nix-shell --pure

[nix-shell:~/github/test3]$ python ../dbt/setup.py build
running build

[nix-shell:~/github/test3]$ echo $?
0
```
2020-08-23 17:13:36 -04:00
Jacob Beck
fe461381a2 Merge pull request #2721 from fishtown-analytics/feature/more-test-only-adapter-methods
add more test helper methods
2020-08-21 13:13:28 -06:00
Jacob Beck
685873ab42 its ok to hit a deprecation with tracking disabled 2020-08-21 12:31:35 -06:00
Jacob Beck
b6a951903e add more test helper methods 2020-08-21 08:02:45 -06:00
Github Build Bot
acfa84918e Release dbt v0.18.0rc1 2020-08-19 20:10:33 +00:00
Jacob Beck
75304eb3be Merge pull request #2718 from fishtown-analytics/fix/only-jinja-is-comments
When deciding if we should bypass rendering, also check for comments
2020-08-19 13:39:03 -06:00
Jacob Beck
1d7eb59ff2 Merge pull request #2712 from fishtown-analytics/feature/adapter-cte-generation
Feature: adapter cte generation
2020-08-19 12:40:04 -06:00
Jacob Beck
4273cc9e29 Merge pull request #2709 from kconvey/kconvey-copy-job
Add a BigQuery adapter macro to enable usage of CopyJobs
2020-08-19 12:39:49 -06:00
Jacob Beck
29be2de5cb When deciding if we should bypass rendering, also check for comments 2020-08-19 12:27:40 -06:00
Jacob Beck
91b0496c89 Merge branch 'dev/marian-anderson' into kconvey-copy-job 2020-08-19 12:12:14 -06:00
Jacob Beck
f043f948de Merge pull request #2711 from kconvey/kconvey-model-ttl
Support TTL for BigQuery tables
2020-08-19 12:10:14 -06:00
Jeremy Cohen
7ef7a8f306 Merge pull request #2708 from rsenseman/enhancement/color_output_command_line_flag_second_attempt
Add --use-colors cli option (second attempt)
2020-08-19 13:42:24 -04:00
Robert
db8eea2468 Merge branch 'dev/marian-anderson' into enhancement/color_output_command_line_flag_second_attempt 2020-08-19 10:01:12 -07:00
Kurt Convey
55813d9209 hours_to_expiration 2020-08-19 10:08:52 -06:00
Kurt Convey
4c05daae1b Merge with origin 2020-08-19 09:41:37 -06:00
Kurt Convey
16eb7232c3 Check the copy model for failure 2020-08-19 09:18:33 -06:00
Kurt Convey
c28ffcdd9f merge with origin 2020-08-19 09:08:07 -06:00
Kurt Convey
4b8652f1c4 Should be two results for original table and (failing) copy 2020-08-19 08:58:39 -06:00
Jeremy Cohen
674bd8f264 Merge pull request #2715 from fishtown-analytics/docs/0.18.0-search-selectors
Docs site updates for 0.18.0
2020-08-19 10:41:48 -04:00
Jeremy Cohen
1ba832dbfe Docs site updates for 0.18.0 2020-08-18 18:42:32 -04:00
Jacob Beck
d3e4d3fbcb pr feedback: remove commented out code 2020-08-18 14:34:12 -06:00
Jacob Beck
58a3cb4fbd changelog update 2020-08-18 14:34:11 -06:00
Jacob Beck
8ad1551b15 when you think about it, data tests are really just ctes 2020-08-18 14:33:56 -06:00
Jacob Beck
123771163a hide more things from the context 2020-08-18 14:33:56 -06:00
Jacob Beck
f80a759488 Have the adapter be responsible for producing the compiler
The adapter's Relation is consulted for adding the ephemeral model prefix

Also hide some things from Jinja

Have the adapter be responsible for producing the compiler, move CTE generation into the Relation object
2020-08-18 14:33:56 -06:00
Kurt Convey
42f8a4715e Assert that single result has error 2020-08-18 14:11:28 -06:00
Jeremy Cohen
c29892e340 Merge pull request #2710 from fishtown-analytics/feature/add-deprecation-tracking
Track deprecation warnings
2020-08-18 16:11:02 -04:00
Kurt Convey
b4a2ed6bb5 Look for proper string 2020-08-18 14:06:53 -06:00
Kurt Convey
67e8caf045 No need to assert model success 2020-08-18 14:05:44 -06:00
Kurt Convey
2562debe31 Put --debug before run 2020-08-18 13:42:58 -06:00
Kurt Convey
87e2fd610c Raise error for bad materializations and set a default 2020-08-18 13:41:00 -06:00
Kurt Convey
af118bcc53 Check stdout with --debug for actual ddl 2020-08-18 13:23:28 -06:00
Kurt Convey
7e01172b4c Set status 2020-08-18 13:08:58 -06:00
Kurt Convey
f56ae93772 Return from copy_bq_table and fix test 2020-08-18 10:46:04 -06:00
Kurt Convey
3834805929 Use injected sql from results 2020-08-18 10:25:11 -06:00
Kurt Convey
50b6057bbf Make copy a proper materialization 2020-08-18 10:17:15 -06:00
Kurt Convey
c0199abacf Split failing and succeeding models 2020-08-18 08:34:41 -06:00
Kurt Convey
77688c74f3 Remove test from wrong PR 2020-08-17 17:20:24 -06:00
Kurt Convey
47ab7419ac Embed profile name 2020-08-17 17:08:38 -06:00
Kurt Convey
dc209f77ec Fix class name 2020-08-17 16:35:29 -06:00
Kurt Convey
76aa8c7df5 Fix class name 2020-08-17 16:32:22 -06:00
Kurt Convey
671a29ff34 plugins 2020-08-17 16:16:46 -06:00
Kurt Convey
eb35794aca Fix table options string 2020-08-17 16:05:44 -06:00
Kurt Convey
a8d6691dee Mock config better 2020-08-17 15:59:04 -06:00
Kurt Convey
955f4ae977 Add entry to CHANGELOG 2020-08-17 15:26:33 -06:00
Kurt Convey
25a869a686 Fix unit test 2020-08-17 15:23:25 -06:00
Kurt Convey
7aa8030b76 Fix test name 2020-08-17 15:12:02 -06:00
Kurt Convey
108d843bba Add newlines 2020-08-17 15:10:51 -06:00
Kurt Convey
099fea8565 Add integration test 2020-08-17 15:04:59 -06:00
Jeremy Cohen
a573a2ada1 Explicitly import dbt.tracking module 2020-08-17 17:00:07 -04:00
Kurt Convey
1468ca8ebc Expect to fail 2020-08-17 14:54:11 -06:00
Jeremy Cohen
274aea9f8f Track deprecation warnings 2020-08-17 16:36:04 -04:00
Kurt Convey
38a99a75ed Use correct models 2020-08-17 14:29:49 -06:00
Kurt Convey
6e06bd0cb4 Add unit test 2020-08-17 14:29:02 -06:00
Kurt Convey
02a793998a Fix macro compilation 2020-08-17 14:10:04 -06:00
Kurt Convey
81ab9469b7 Add time_to_expiration 2020-08-17 14:00:10 -06:00
Kurt Convey
bcc928495d Fix if statement 2020-08-17 13:13:48 -06:00
Kurt Convey
621ae7dbc9 make flake8 happy 2020-08-17 13:03:33 -06:00
Kurt Convey
85f2c03903 Tweak integration test 2020-08-17 12:26:25 -06:00
Kurt Convey
f7fd741d43 Attempt at integration tests 2020-08-17 12:16:20 -06:00
Kurt Convey
fad0d81837 Reference consts through the right module 2020-08-17 11:56:14 -06:00
Kurt Convey
09687409dc move bq context to impl.py 2020-08-17 11:44:51 -06:00
Kurt Convey
091bcd107c Fix test write dispositions 2020-08-17 09:20:22 -06:00
Kurt Convey
f9b300d63a Add changelog 2020-08-17 09:12:52 -06:00
Jacob Beck
4f2acc2c96 Merge pull request #2703 from vogt4nick/2702-patch-redshift-table-size-estimation
patch redshift table size estimation
2020-08-17 09:04:03 -06:00
Kurt Convey
6aa4a60d5c Add copy_job macro 2020-08-17 09:01:17 -06:00
rsenseman
b075bf51b0 one more changelog update 2020-08-16 14:48:08 -07:00
rsenseman
de2341ece0 update changelog 2020-08-16 14:44:55 -07:00
rsenseman
ff67e7d47c add missing code; finalize pr 2020-08-16 14:34:46 -07:00
rsenseman
31644ed39d initial commit 2020-08-16 13:48:55 -07:00
Jacob Beck
acb235ef4f Merge pull request #2698 from fishtown-analytics/fix/snowflake-connector-python-upgrade
bump requirements, enable the token cache
2020-08-14 13:47:32 -06:00
Jacob Beck
d554835b50 Merge pull request #2695 from fishtown-analytics/feature/state-modified-selector
Add state:modified and state:new selectors
2020-08-14 13:44:48 -06:00
Jacob Beck
c8453d80fc fix ls tests 2020-08-14 11:36:19 -06:00
Jacob Beck
f3f713ae65 fix macro change check to account for new list return value when macros are added/removed 2020-08-14 09:05:55 -06:00
Nick Vogt
cfa741e597 calculate Redshift table size in bytes, not megabytes 2020-08-14 11:02:10 -04:00
Jacob Beck
13fb2351ed PR feedback 2020-08-14 08:48:59 -06:00
Jacob Beck
3555ba518d fix unit tests 2020-08-12 14:07:20 -06:00
Jacob Beck
c5a19ca42e bump requirements, enable the token cache 2020-08-12 14:00:37 -06:00
Jacob Beck
153eb7e9d3 flake8 update found more things to complain about 2020-08-12 07:44:52 -06:00
Jacob Beck
7ba52d4931 mypy is rightfully mad about our funky inheritance, just copy+paste things
We can fix this when we drop python 3.6 and unions stop collapsing types
2020-08-12 07:44:52 -06:00
Jacob Beck
ebe5b46653 Add state:modified and state:new selectors 2020-08-12 07:44:51 -06:00
Jacob Beck
1bd82d4914 Merge pull request #2694 from kconvey/kconvey-retry-upstream
Add retry of additional errors
2020-08-12 07:43:50 -06:00
Jeremy Cohen
89775fa94f Merge pull request #2594 from brunomurino/issue-2533
added option --adapter to dbt init, to create sample profiles.yml bas…
2020-08-11 18:27:02 -04:00
Kurt Convey
4456872635 Didn't forget to add myself to the contributors 2020-08-11 15:23:02 -06:00
Kurt Convey
ee9ae22651 Fix variable name 2020-08-11 15:16:15 -06:00
Kurt Convey
afe0f46768 use isinstance with tuple 2020-08-11 15:15:04 -06:00
Kurt Convey
203d8c3481 Update CHANGELOG 2020-08-11 12:42:31 -06:00
Kurt Convey
c9ae49255d Add test from dev 2020-08-11 12:29:52 -06:00
Bruno Murino
51f17d3358 Update plugins/redshift/dbt/include/redshift/sample_profiles.yml
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-08-10 13:38:08 +01:00
Bruno Murino
cacdd58b41 Update plugins/redshift/dbt/include/redshift/sample_profiles.yml
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-08-10 13:38:01 +01:00
Bruno Murino
8b7bcbbc47 Update plugins/postgres/dbt/include/postgres/sample_profiles.yml
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-08-10 13:37:49 +01:00
Bruno Murino
bdf9482e75 Update plugins/postgres/dbt/include/postgres/sample_profiles.yml
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-08-10 13:37:42 +01:00
Bruno Murino
b94d0b66e6 Apply suggestions from code review
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-08-06 19:11:12 +01:00
Bruno Murino
1dff122a94 Update plugins/postgres/dbt/include/postgres/sample_profiles.yml
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-08-06 19:10:34 +01:00
Jacob Beck
fb8065df27 Merge pull request #2686 from fishtown-analytics/feature/override-core-macros
include project macros in the manifest the adapter stores locally
2020-08-06 11:35:25 -06:00
Jacob Beck
4274139210 Fix the test
Make sure "dbt deps" reloads the full manifest
Make sure commands that reload the dbt_project.yml properly reset the config (including adapters)
2020-08-05 14:50:59 -06:00
brunomurino
b422a44c03 update redshift and snowflake sample profiles 2020-08-05 19:56:29 +01:00
Jacob Beck
285479c0bc include project macros in the manifest the adapter stores locally 2020-08-05 09:35:00 -06:00
Jacob Beck
e641ec12fa Merge pull request #2684 from fishtown-analytics/feature/dispatch-schema-tests
convert tests
2020-08-05 09:27:55 -06:00
Jeremy Cohen
36fda28a92 Merge pull request #2677 from bbhoss/bq_impersonate
Add support for impersonating a service account with BigQuery
2020-08-04 14:37:53 -04:00
Jacob Beck
1ece515074 Merge pull request #2679 from fishtown-analytics/feature/adapter-dispatch
Create adapter.dispatch
2020-08-04 12:01:00 -06:00
Jacob Beck
04f840d907 convert tests 2020-08-04 09:47:48 -06:00
Preston Marshall
df8ccc04eb add contributors section 2020-08-04 10:49:35 -04:00
Jacob Beck
dd764b93e0 check if packages is a string and error out 2020-08-04 07:46:50 -06:00
Jacob Beck
335497f688 fixed error message, fixed the changelog 2020-08-03 15:48:04 -06:00
Jacob Beck
41a9251982 pr feedback: better errors for dotted macro names
add more tests
2020-08-03 12:31:29 -06:00
Preston Marshall
84bf03580d Merge remote-tracking branch 'upstream/dev/marian-anderson' into bq_impersonate 2020-08-03 13:00:13 -04:00
Preston Marshall
43d5dfcb71 move changelog additions to next 2020-08-03 12:57:30 -04:00
Preston Marshall
7a9fc7ef12 update changelog 2020-08-03 12:55:42 -04:00
Jacob Beck
ecf24cd4d9 adapter_macro -> adapter.dispatch
Added tests
Added deprecation warning + tests
2020-08-03 10:49:28 -06:00
Jacob Beck
ba828e59de Merge pull request #2675 from fishtown-analytics/feature/python-adapter-macro
Port the adapter macro to python
2020-08-03 09:50:13 -06:00
Jacob Beck
34719f94b4 PR feedback 2020-08-03 09:15:28 -06:00
Preston Marshall
c95a6792e5 add a test 2020-08-01 15:40:48 -04:00
Preston Marshall
88529d5c25 first try 2020-08-01 15:12:00 -04:00
Jacob Beck
3489878395 changelog 2020-07-31 12:19:51 -06:00
Jacob Beck
d938013733 remove unused to_dict method 2020-07-31 12:19:51 -06:00
Jacob Beck
dfe2ea4d0a adapter macro -> python
Updated tests
2020-07-31 12:19:51 -06:00
Jacob Beck
7a3e345cf7 upgrade mypy, now it might not crash on failures 2020-07-31 12:19:51 -06:00
Jacob Beck
1fb8c9c896 Merge pull request #2656 from fishtown-analytics/feature/defer-to-prod
Feature/defer to prod
2020-07-31 12:09:25 -06:00
Jacob Beck
2ed97640d5 changelog updates 2020-07-31 09:24:51 -06:00
Jacob Beck
6d31aa6f67 PR feedback
- Actually set deferred=True
- add a test for that
- combine/unify environment vars vs cli vars behavior between defer/state better
  - make the state path start with DBT_
2020-07-31 09:24:31 -06:00
Jacob Beck
a76ffbd194 add logging, add tests, update changelog 2020-07-31 09:24:31 -06:00
Jacob Beck
0d7e0ba087 read json and merge things 2020-07-31 09:24:31 -06:00
Github Build Bot
618d491f92 Release dbt v0.18.0b2 2020-07-30 17:11:32 +00:00
Jacob Beck
21a3462798 Merge pull request #2589 from azhard/dbt-2586-bigquery-policy-tags
Added support for setting policy tags for columns in BigQuery
2020-07-30 11:07:16 -06:00
Azhar Dewji
d2c5783e5a Addressed PR commments 2020-07-30 11:02:23 -04:00
Azhar Dewji
007cb4516a Addressed PR comments 2020-07-30 10:55:52 -04:00
Azhar Dewji
8ec93d9973 bigquery integration test fix 2020-07-30 10:55:52 -04:00
Azhar Dewji
0a64708302 Linting fix 2020-07-30 10:55:52 -04:00
Azhar Dewji
b964f6dd22 Rebased 2020-07-30 10:55:52 -04:00
Azhar Dewji
14233188be Changed update_column to update_columns 2020-07-30 10:55:51 -04:00
Azhar Dewji
e3193c6243 Updated pull request # 2020-07-30 10:55:51 -04:00
Azhar Dewji
8782714ec3 Added support for setting policy tags for columns in BigQuery 2020-07-30 10:55:51 -04:00
Jacob Beck
03010ecde7 Merge branch 'dev/0.17.2' into dev/marian-anderson 2020-07-29 13:54:10 -06:00
Github Build Bot
97d63d3da4 Release dbt v0.17.2 2020-07-29 19:09:25 +00:00
Jacob Beck
a212f4eb5e Merge pull request #2663 from fishtown-analytics/fix/changelog
Fix the changelog
2020-07-29 12:47:26 -06:00
Jacob Beck
5c993fd568 fix the changelog 2020-07-29 12:10:35 -06:00
Jeremy Cohen
df60189266 Merge pull request #2658 from fishtown-analytics/docs/0.17.2-code-block-background
Docs site updates for 0.17.2: fix code block background
2020-07-29 10:50:36 -04:00
Jacob Beck
97f61a9ecc Merge pull request #2654 from fishtown-analytics/fix/redshift-schema-override-catalog
fix redshift catalog with custom schemas
2020-07-29 08:39:16 -06:00
Jeremy Cohen
8740e6ca66 Docs site updates for 0.17.2: fix code block background 2020-07-28 18:43:09 -04:00
Claire Carroll
c9eb3c2b54 Merge pull request #2623 from fishtown-analytics/asset-paths
Compile assets as part of docs generate
2020-07-28 17:15:21 -04:00
Github Build Bot
d9411bc9bb Release dbt v0.17.2rc1 2020-07-28 20:20:16 +00:00
Claire Carroll
a3a1b143cf Compile assets as part of docs generate 2020-07-28 15:49:15 -04:00
Jacob Beck
5c18c78b50 Merge pull request #2650 from fishtown-analytics/fix/close-connections
Fix: close connections after use
2020-07-28 13:25:24 -06:00
Jacob Beck
3bcb5f147d fix a test with too-strict bounds, any <=2 value is ok here 2020-07-28 13:02:34 -06:00
Jacob Beck
1bfe43ff76 PR feedback:
- fix conftest adainivalue_line calls
 - deprecate the release argument to execute_macro
2020-07-28 12:07:08 -06:00
Jacob Beck
2d2019c059 add a test for cross-schema catalog generation, fix redshift to work for schema overrides 2020-07-28 11:08:24 -06:00
Jacob Beck
4613619841 missed the snowflake rpc tests 2020-07-28 07:55:24 -06:00
Jacob Beck
44e3c7eb6d only try to cancel open connections 2020-07-27 16:08:43 -06:00
Jacob Beck
fd0b460391 alter the tests so we can run rpc tests on snowflake 2020-07-27 15:16:14 -06:00
Jacob Beck
3b917b9d79 Handle the fallout of closing connections in release
- close() implies rollback, so do not call it
- make sure to not open new connections for executors in single-threaded mode
- logging cleanups
- fix a test case that never acquired connections
- to cancel other connections, one must first acquire a connection for the master thread
- change a number of release() calls to rollback

release vs rollback
2020-07-27 15:16:14 -06:00
Jacob Beck
970049fde8 Always close connections in release() 2020-07-27 15:16:13 -06:00
Jacob Beck
5b40cc4a01 Merge pull request #2649 from fishtown-analytics/fix/dbt-clean-noprofile
Fix: dbt clean without profile
2020-07-27 15:15:13 -06:00
Jacob Beck
0919c5642d Update CHANGELOG.md
Co-authored-by: Jeremy Cohen <jeremy@fishtownanalytics.com>
2020-07-27 13:42:21 -06:00
Jacob Beck
de4f90b3f4 clean without a profile 2020-07-27 11:11:38 -06:00
Jacob Beck
a1c2270956 azure pipelines silently messing with $PATH again 2020-07-27 11:04:50 -06:00
Jacob Beck
b9cf0cfc44 Merge pull request #2646 from joshpeng-quibi/fix/fail-fast-output
Fix fast-fail not logging original error message
2020-07-27 11:02:26 -06:00
joshpeng-quibi
d2869e4749 Apply suggestions from code review
Co-authored-by: Jacob Beck <beckjake@users.noreply.github.com>
2020-07-24 13:49:18 -07:00
Jacob Beck
3ec911b62c Merge pull request #2640 from fishtown-analytics/feature/yaml-selections
Feature/yaml selections
2020-07-24 14:30:32 -06:00
Josh Peng
4ff66c1cb6 Fix fast-fail not logging original error message 2020-07-24 12:06:57 -07:00
Jacob Beck
184146b825 add changelog 2020-07-22 09:57:27 -06:00
Jacob Beck
1e8543efb1 Create selector config file, handle selector argument
Added tests
Added RPC and CLI support
2020-07-22 09:31:55 -06:00
Jacob Beck
a976e54c19 Parse selectors
This doesn't use hologram, as it needs more permissive schemas than what hologram provides.
Added some unit tests
2020-07-22 07:58:10 -06:00
Jacob Beck
88e26da026 Merge pull request #2629 from fishtown-analytics/feature/new-node-selectors
new node selectors (#2425)
2020-07-21 20:33:48 -06:00
Jacob Beck
58522b8f67 PR feedback: catch non-declared config items 2020-07-21 15:43:52 -06:00
Github Build Bot
f09bb17da4 Release dbt v0.17.2b1 2020-07-21 13:28:43 +00:00
Jacob Beck
353ae0f090 changelog fix 2020-07-21 07:25:42 -06:00
Jacob Beck
0404881d85 Merge pull request #2635 from fishtown-analytics/feature/debug-logging-env-vars
add environment variables for logging
2020-07-20 13:48:18 -06:00
Jacob Beck
fb88d54c31 Merge pull request #2628 from fishtown-analytics/feature/advanced-node-selection
split up node selection
2020-07-20 13:48:04 -06:00
Jacob Beck
a523356504 new node selectors
- added a bunch of unit tests
- added some new integration tests
- cleaned up some selection logic
2020-07-20 11:16:40 -06:00
Jacob Beck
91ff7f1c80 PR feedback: only log about the innermost failed expectation 2020-07-20 11:16:17 -06:00
Jacob Beck
51f6c268d3 add environment variables for logging 2020-07-20 09:55:19 -06:00
Jacob Beck
5e1b284ad0 Bump version: 0.17.1 → 0.17.2a1 2020-07-20 09:39:04 -06:00
Github Build Bot
a962d28674 Release dbt v0.17.1 2020-07-20 12:48:01 +00:00
Jacob Beck
7f0c1f823a Merge pull request #2627 from kning/ISSUE-2569_bad_target
Add more helpful error message for misconfiguration in profiles.yml
2020-07-17 11:48:08 -06:00
Kenny Ning
d08a2b8c02 Add PR link to changelog 2020-07-17 11:49:38 -04:00
Kenny Ning
d7f3518a9a Add test 2020-07-17 11:37:02 -04:00
Jacob Beck
d34d2a023c pr feedback: Make UniqueId a NewType 2020-07-17 09:03:39 -06:00
Kenny Ning
1ecd954199 Address review 2020-07-16 16:08:02 -04:00
Jacob Beck
6e161ab84e azure pipelines silently messing with $PATH again 2020-07-15 17:25:14 -06:00
Jacob Beck
97c59f36a2 fix some bugs
rpc, ls, some import tweaks
2020-07-15 16:30:38 -06:00
Jacob Beck
89443eac0e Refactor for node selection
- moved what remains of the linker into compilation, migrate things to the graph
 - compile uncompiled ephemeral models when prepending ctes
  - no more including them all in the selection
 - per-node task selectors
 - added the idea of a selection spec, graph selection uses that
   - right now we parse include + exclude strings
2020-07-15 16:30:38 -06:00
Jacob Beck
3e8414e259 merge dev/0.17.1 2020-07-15 15:49:40 -06:00
Kenny Ning
06a30f0447 Add more helpful error message for misconfiguration in profiles.yml 2020-07-15 17:26:12 -04:00
Jacob Beck
4e636aa53a Merge pull request #2610 from fishtown-analytics/feature/adapter-test-suite-changes
move the sql for getting different rows into dbt proper
2020-07-15 08:43:59 -06:00
Claire Carroll
7bd18ef331 Merge pull request #2576 from fishtown-analytics/update-logo
Update logo
2020-07-10 10:24:52 -04:00
Github Build Bot
181fcc3965 Release dbt v0.17.1rc4 2020-07-08 16:12:55 +00:00
Jacob Beck
3a1f745e65 Merge pull request #2618 from fishtown-analytics/fix/native-env-opt-in
make native env rendering opt-in
2020-07-08 09:44:57 -06:00
Jacob Beck
2dc3ea3f12 fix tests 2020-07-08 08:41:58 -06:00
Jacob Beck
1e8ed49661 Apply suggestions from code review
Co-authored-by: Drew Banin <drew@fishtownanalytics.com>
2020-07-08 08:34:39 -06:00
Jacob Beck
84bf169458 make native env rendering opt-in 2020-07-07 14:50:40 -06:00
brunomurino
3dabe62254 updated redshift and snowflake sample_profiles.yml 2020-07-04 14:13:51 +01:00
brunomurino
0d246ac95b updated postgres sample_profiles.yml 2020-07-02 21:16:45 +01:00
brunomurino
2d0612c972 updated bigquery sample_profiles.yml 2020-07-02 21:14:52 +01:00
Jacob Beck
4cf2b78fca move the sql for getting different rows into dbt proper, from the test suite. Bump pytest dependency. 2020-07-02 08:39:23 -06:00
Jacob Beck
3af8a22d13 Merge pull request #2590 from fishtown-analytics/fix/adapter-macro-namespacing
Fix adapter macro namespacing (#2548)
2020-07-01 10:51:29 -06:00
Jacob Beck
b0c7b3a38b remove commented out code 2020-07-01 09:21:01 -06:00
Github Build Bot
3be03b681b Release dbt v0.17.1rc3 2020-07-01 13:19:07 +00:00
Jacob Beck
dd5b7240b6 Merge pull request #2601 from fishtown-analytics/fix/bigquery-seed-descriptions
On bigquery, persist docs for seeds as well as tables/views
2020-06-30 12:26:52 -06:00
Jacob Beck
2b9b82e5c4 Merge branch 'dev/0.17.1' into fix/bigquery-seed-descriptions 2020-06-30 09:38:13 -06:00
Jacob Beck
c5c14bb798 Merge pull request #2603 from fishtown-analytics/fix/windows-is-the-worst-os-ever
remove length check + catch any exceptions on windows
2020-06-30 09:04:26 -06:00
Jacob Beck
a201fc773a Merge pull request #2599 from fishtown-analytics/fix/more-jinja-quoting-sadness
Fix more jinja quoting sadness
2020-06-30 07:53:42 -06:00
Jacob Beck
d897164d5c PR feedback: add exception to the log message 2020-06-30 07:53:23 -06:00
Jacob Beck
e558d3f9ad remove length check + catch any exceptions on windows 2020-06-30 07:42:41 -06:00
Jacob Beck
6a7078f634 flake8 2020-06-29 16:02:49 -06:00
Jacob Beck
b14676f0c5 Merge branch 'dev/0.17.1' into fix/more-jinja-quoting-sadness 2020-06-29 15:57:07 -06:00
Jacob Beck
4cef1af8cb On bigquery, persist docs for seeds as well as tables/views 2020-06-29 15:50:48 -06:00
Jacob Beck
42aa407adc PR feedback: extra test 2020-06-29 15:00:00 -06:00
Claire Carroll
e7298d60d3 Merge pull request #2600 from fishtown-analytics/fix/package-tracking
Hash local package name
2020-06-29 16:38:32 -04:00
Claire Carroll
9c0ff932c8 Update changelog 2020-06-29 15:56:03 -04:00
Claire Carroll
288dd221f8 Hash local package name 2020-06-29 15:39:17 -04:00
Jacob Beck
d374ef9ffe If it comes into literal_eval as a string and it comes out as a string, return the original 2020-06-29 13:18:52 -06:00
Jeremy Cohen
7fa6a363b2 Merge pull request #2577 from fishtown-analytics/refactor/contributing
Revise CONTRIBUTING doc
2020-06-26 14:42:47 -04:00
brunomurino
13da3390e5 updated changelog 2020-06-26 18:58:45 +01:00
brunomurino
4164e6ee8e updated setup.py of adapters to include sample_profiles.yml 2020-06-26 18:12:18 +01:00
brunomurino
2b454f99dd updated 040_init_test for profile postgres to work with new --adapter option of dbt init 2020-06-25 23:54:33 +01:00
brunomurino
2025634417 added option --adapter to dbt init, to create sample profiles.yml based on chosen adapter 2020-06-25 23:27:08 +01:00
Github Build Bot
42e582a6ad Release dbt v0.17.1rc2 2020-06-25 21:05:41 +00:00
Drew Banin
339a7e9409 Merge pull request #2588 from fishtown-analytics/docs/new-logo-jun-2020
Docs/new logo jun 2020
2020-06-25 17:02:52 -04:00
Jacob Beck
f420911e19 Merge pull request #2591 from fishtown-analytics/fix/windows-path-length-nonsense
More windows path lengths
2020-06-25 14:57:09 -06:00
Jacob Beck
2e39a5fff0 handle unmarked path too long errors 2020-06-25 10:32:42 -06:00
Jacob Beck
e0f7deabfd spelling 2020-06-25 10:26:09 -06:00
Jacob Beck
abe345e925 Allow plugin macros to override core
Moved some relation logic out of adapters and into contracts
Fixed a typo in a type name
Fixed an issue where tests could fail based on color settings
Fixed the type analysis for adapter plugins/factory
Swapped more concrete tyeps out for protocols
Finally removed PACKAGES global
Added unit tests
2020-06-25 10:05:41 -06:00
Jacob Beck
4d33554465 protocols 2020-06-25 10:02:03 -06:00
Jacob Beck
32c559838d Try to make imports a little more sane, ordering-wise
consolidate dbt.ui, move non-rpc node_runners into their tasks
move parse_cli_vars into config.utils
get rid of logger/exceptions requirements in dbt.utils
2020-06-25 10:02:02 -06:00
Jacob Beck
62a0bf8732 store package info on the factory instead of globally
adapter_types -> plugins
Use factory.packages instead of global PACKAGES
2020-06-25 09:59:47 -06:00
Jacob Beck
24ae8b4498 Merge pull request #2555 from DrMcTaco/snowflake-query-tag
Snowflake query tag
2020-06-25 08:46:27 -06:00
Jacob Beck
2945a91189 Merge branch 'dev/marian-anderson' into snowflake-query-tag 2020-06-25 07:36:45 -06:00
Drew Banin
26735dac8d update docs logo 2020-06-25 09:35:13 -04:00
Jeremy Cohen
7c0a41c998 Fixup anchor 2020-06-25 00:35:39 -04:00
Jeremy Cohen
778a89ef8a Feedback from Andrew 2020-06-25 00:34:31 -04:00
Jeremy Cohen
83d85dc928 Merge pull request #2581 from brunomurino/issue-2437
added option to specify profile when connecting to redshift via IAM
2020-06-24 10:13:34 -04:00
Dan Rubin
25a06aee01 Merge branch 'dev/marian-anderson' into snowflake-query-tag 2020-06-24 09:16:07 -04:00
Dan Rubin
c89ea1ad56 fix CHANGELOG formatting 2020-06-24 09:15:17 -04:00
Jeremy Cohen
76f9f23b16 Merge pull request #2530 from alepuccetti/bigquery-processed-bytes-log
Add data processed info into dbt run logs for all statement types
2020-06-23 16:39:33 -04:00
Jacob Beck
a45f6bea5b Merge pull request #2584 from fishtown-analytics/fix/plus-prefixed-hooks
Fix prefixing support to not render "+pre-hook"
2020-06-23 14:37:08 -06:00
Dan Rubin
49fec95b33 Merge branch 'dev/marian-anderson' into snowflake-query-tag 2020-06-23 16:20:44 -04:00
Jacob Beck
d38fd80dff PR feedback: double seeds 2020-06-23 13:25:01 -06:00
Jacob Beck
37b03ef5d2 Fix prefixing support to not render "+pre-hook"
Increased the startup timeout in the RPC tests to try to fix some local timeouts
2020-06-23 10:04:23 -06:00
Bruno Murino
0c878a056e Update CHANGELOG.md
Co-authored-by: Jacob Beck <beckjake@users.noreply.github.com>
2020-06-23 16:24:25 +01:00
Alessandro Puccetti
1da4bef068 Update CHANGELOG.md
Co-authored-by: Jeremy Cohen <jtcohen6@gmail.com>
2020-06-23 16:47:37 +02:00
brunomurino
65c156750d updated changelog 2020-06-23 15:09:10 +01:00
brunomurino
bd34dfe1f7 removed hardcoded option in fetch_cluster_credentials 2020-06-23 14:53:53 +01:00
brunomurino
78445f9879 updates to make pep8 compliant 2020-06-22 23:15:40 +01:00
brunomurino
053210330e updated to comply with pep8 guidelines 2020-06-22 23:03:14 +01:00
brunomurino
df894f91f2 updated changelog 2020-06-22 22:27:57 +01:00
Jacob Beck
f758614b0a Merge pull request #2579 from christineberger/feature/change_yml_warning_color
Feature: Change project-level warning color
2020-06-22 14:41:39 -06:00
Bruno Murino
71846c98b3 added option to specify profile when connecting to redshift via IAM 2020-06-22 21:39:03 +01:00
Alessandro Puccetti
272cad7b97 core/dbt/utils: remove extra empty line 2020-06-22 22:21:02 +02:00
Alessandro Puccetti
5eb9523325 plugins/bigquery: log processed bytes value for all statement types
Fixes https://github.com/fishtown-analytics/dbt/issues/2526
2020-06-22 22:21:02 +02:00
Alessandro Puccetti
d2b2f2f6b6 core/dbt/utils: add format_rows_number 2020-06-22 22:21:01 +02:00
Alessandro Puccetti
55f9720a44 core/dbt/utils: add PB to format_bytes and always return the accurate value 2020-06-22 22:17:06 +02:00
Jeremy Cohen
9d52e6acbf Feedback from Sanjana 2020-06-22 14:57:05 -04:00
Jacob Beck
8f649f5ab4 Merge pull request #2547 from azhard/bigquery-alter-column-type
Add support for altering BigQuery column types
2020-06-22 12:50:18 -06:00
Christine
fbfb54e3e2 Fixed flake8 suggestions on inline comment 2020-06-22 12:51:39 -05:00
Christine
e523d908bf Moved dbt.tracking InvocationProcesser in to print_run_end_messages() to avoid import loop 2020-06-22 12:45:24 -05:00
Christine
8b2d416035 Simplified warning_tag() logic 2020-06-22 11:56:51 -05:00
Jeremy Cohen
078356458b Feedback from Drew 2020-06-22 12:38:01 -04:00
Christine
4846246f12 Fixed indenting on end cap of function 2020-06-22 11:30:33 -05:00
Christine
dc10b3d904 Fixed more flake8 whitespace suggestions 2020-06-22 11:27:59 -05:00
Christine
05d4a5e4a3 Updated old warning_tag concatenation to use new warning_tag function 2020-06-22 11:17:46 -05:00
Christine
2883b6dc17 Removed some formatting and whitespace 2020-06-22 11:16:31 -05:00
Christine
5f9e153db8 Fixed whitespace suggestions from flake8 2020-06-22 11:04:09 -05:00
Christine
c05153f00a Added changes to changelog.md 2020-06-22 09:38:05 -05:00
Claire Carroll
3616641e93 Update other logos 2020-06-22 08:48:59 -04:00
Christine
78cbeca606 Added some whitespace under the deprecation warning 2020-06-20 15:09:06 -05:00
Christine
354c7ecddf Changed configuration paths warning to be wrapped with new warning_tag 2020-06-20 15:08:44 -05:00
Christine
4d659318c4 Changed format of project-level warning messages to be wrapped with new warning_tag 2020-06-20 15:07:58 -05:00
Christine
9d4399eb28 Added a warning_tag wrapper fuction to printer class 2020-06-20 15:06:48 -05:00
Github Build Bot
32702559f6 Release dbt v0.17.1rc1 2020-06-19 21:13:16 +00:00
Drew Banin
310a1dfaec Merge pull request #2565 from fishtown-analytics/docs/0.17.1-docs-updates
bump docs site
2020-06-19 17:08:55 -04:00
Drew Banin
6528b5f25b Merge branch 'dev/0.17.1' into docs/0.17.1-docs-updates 2020-06-19 17:08:44 -04:00
Jacob Beck
19378abfae Merge pull request #2575 from fishtown-analytics/fix/backport-aliasing
backport aliases in config fix
2020-06-19 15:06:57 -06:00
Jacob Beck
593e86c15d Merge pull request #2550 from bodschut/bodschut/bq_nested_column_descriptions
Add functionality to persist descriptions to nested columns in bigquery
2020-06-19 13:58:21 -06:00
Jeremy Cohen
6226d60c9a Give CONTRIBUTING.md some love 2020-06-19 15:36:38 -04:00
Claire Carroll
aa35dc9437 Update logo 2020-06-19 15:25:28 -04:00
Jacob Beck
098d05c27f Fix tests 2020-06-19 12:54:10 -06:00
Jacob Beck
05917c6d2f update changelog 2020-06-19 11:29:35 -06:00
Jacob Beck
e300c62d76 add a test 2020-06-19 11:24:47 -06:00
Jacob Beck
3829d1f964 Fix a regression where dbt ignored aliases in config() calls 2020-06-19 11:16:07 -06:00
Bob De Schutter
0d2593e0e3 Fix docs_generate test 2020-06-19 18:19:50 +02:00
Bob De Schutter
e99f0d12b8 Remove commented out code
Co-authored-by: Jacob Beck <beckjake@users.noreply.github.com>
2020-06-19 16:56:17 +02:00
Jacob Beck
d0e736f31b Merge branch 'dev/0.17.1' into bodschut/bq_nested_column_descriptions 2020-06-19 08:41:19 -06:00
Bob De Schutter
3a5db3c16d Updated changelog 2020-06-19 16:37:53 +02:00
Jacob Beck
0cddd9c417 Merge pull request #2566 from fishtown-analytics/fix/windows-long-paths
Fix windows long paths
2020-06-19 08:31:18 -06:00
Bob De Schutter
51ce3e1093 Fix persist docs functionality for bigquery to include descriptions for nested columns 2020-06-19 16:31:14 +02:00
Jacob Beck
db50880399 fix this test for spawn() vs fork() 2020-06-19 07:26:40 -06:00
Jacob Beck
8e63da2f04 Be pickier about when to mangle paths, handle the case where we still hit path length limits 2020-06-18 17:11:15 -06:00
Drew Banin
a0b502dd44 Merge branch 'dev/0.17.1' into docs/0.17.1-docs-updates 2020-06-18 17:34:42 -04:00
Jacob Beck
ff272c797a make sure dbt clean still works... 2020-06-18 15:17:27 -06:00
Jacob Beck
f4b93c8a5a handle long paths in windows, add a test 2020-06-18 15:08:43 -06:00
Jacob Beck
c01056b99a Merge pull request #2559 from fishtown-analytics/fix/alias-bug
Fix a regression where dbt ignored aliases in config() calls
2020-06-18 07:13:36 -06:00
Jeremy Cohen
12ca721e0b Merge pull request #2567 from fishtown-analytics/feature/ci-on-forks
Run all integration tests on PRs from forks
2020-06-17 18:57:07 -04:00
Drew Banin
9c7d519ab2 changelog 2020-06-17 16:32:57 -04:00
Drew Banin
5299079f01 bump docs site 2020-06-17 16:30:30 -04:00
Jeremy Cohen
4c8575dea3 Build PRs on forks 2020-06-17 15:01:50 -04:00
Dan Rubin
f3aaa8bcee Merge branch 'snowflake-query-tag' of https://github.com/DrMcTaco/dbt into snowflake-query-tag 2020-06-17 13:02:18 -04:00
Dan Rubin
2cc53321ae remove incorrect 2nd arg to config.get 2020-06-17 13:01:16 -04:00
Jacob Beck
64e9d704e2 Merge pull request #2554 from fishtown-analytics/fix/parallel-rpc-method-stomping
Fix parallel rpc methods (2484)
2020-06-17 10:54:09 -06:00
Jacob Beck
1c1f10056d Fix a regression where dbt ignored aliases in config() calls 2020-06-17 07:41:19 -06:00
Dan Rubin
ccae4a8888 Merge branch 'dev/marian-anderson' into snowflake-query-tag 2020-06-16 18:49:08 -04:00
Dan Rubin
5c1840914b update CHANGELOG.md 2020-06-16 18:43:58 -04:00
Dan Rubin
be61e6724a add query tag update macros to each snowflake materialization 2020-06-16 18:43:44 -04:00
Dan Rubin
38975ecee0 add snowflake macros to support fetching current session query tag, updating the session query tag, and reverting to the old query tag 2020-06-16 18:43:26 -04:00
Dan Rubin
d8b31c02e2 add query tag to the snowflake adapter config 2020-06-16 18:43:13 -04:00
Dan Rubin
331b961819 add query tag to snoflake connection dataclass and set tag on new connection if query_tag value is present 2020-06-16 18:42:56 -04:00
Azhar Dewji
769e4d5193 Added warning comment to alter macro 2020-06-16 18:11:08 -04:00
Jacob Beck
dbf7e690b8 this test difference appears to be spawn vs fork 2020-06-16 16:00:07 -06:00
Jacob Beck
12a9c2b441 changelog 2020-06-16 14:46:07 -06:00
Jacob Beck
12d3c52de2 add a test
For the test to work, we have to switch to spawn so that we don't deadlock
2020-06-16 14:43:39 -06:00
Jacob Beck
614f0b4350 deepcopy args in method calls 2020-06-16 13:08:37 -06:00
Jacob Beck
74c78ef927 Merge branch 'dev/0.17.1' into dev/marian-anderson 2020-06-15 13:17:43 -06:00
Jacob Beck
765c2de148 Merge pull request #2537 from fishtown-analytics/fix/bump-minimum-python
Update minimum python version
2020-06-15 12:18:33 -06:00
Jacob Beck
644fef1e84 Update minimum python version: 3.6.2 has a typing bug that dbt/hologram handle poorly 2020-06-15 11:30:29 -06:00
Jacob Beck
b9053a24dc Merge pull request #2535 from fishtown-analytics/feature/allow-package-version-floats
allow floats for package versions, add a test with a float-like branch (#2518)
2020-06-15 11:29:55 -06:00
Jacob Beck
0be8326351 Merge pull request #2534 from fishtown-analytics/fix/dbt-modules-in-project-dir
Make deps respect --project-dir
2020-06-15 11:29:21 -06:00
Azhar Dewji
a7a73df696 Add support for altering BigQuery column types 2020-06-15 12:54:27 -04:00
Jacob Beck
e9b4c47887 PR feedback: use the project root instead of cwd 2020-06-15 10:07:58 -06:00
Jacob Beck
7ee6f1de39 allow relative paths in project-dir 2020-06-12 13:00:00 -06:00
Jacob Beck
eea51bee30 Merge pull request #2517 from azhard/bigquery-auth-views
Added support for BigQuery authorized views
2020-06-11 14:17:25 -06:00
Jacob Beck
0beb03fe53 allow floats for package versions, add a test with a float-like branch 2020-06-11 11:43:47 -06:00
Azhar Dewji
4ccfe020a2 Moved bigquery imports into bigquery test 2020-06-11 13:30:36 -04:00
Azhar Dewji
19735d5416 Added noqa flag to GrantTarget import 2020-06-11 13:19:05 -04:00
Azhar Dewji
e53a45b3e0 Added integration test 2020-06-11 13:10:10 -04:00
Jacob Beck
e00d1ed78c make tests drop more things at cleanup
Make the unique ID a bit more collision-resistant to try to avoid collisions
2020-06-11 09:58:06 -06:00
Jacob Beck
83a4562b98 Make deps respect --project-dir 2020-06-11 07:39:12 -06:00
Jacob Beck
27fef50919 Merge pull request #2528 from fishtown-analytics/fix/do-not-create-schemas
Fix/do not create schemas
2020-06-11 07:36:15 -06:00
Jacob Beck
947a00f6c9 Bump version: 0.17.0 → 0.17.1a1 2020-06-10 15:11:31 -06:00
Jacob Beck
2f36d7d259 Update changelog 2020-06-10 15:08:46 -06:00
Jacob Beck
e2d7027074 add a test 2020-06-10 15:07:15 -06:00
Jacob Beck
a2de92d2a3 Fix azure pipelines builds by using the existing postgres setup 2020-06-10 15:07:15 -06:00
Jacob Beck
edef65f333 do not create schemas 2020-06-10 15:07:15 -06:00
Azhar Dewji
e7dfe97ecc Debug message change 2020-06-10 14:33:29 -04:00
Azhar Dewji
cd0fe51109 Re-add dropped changelog lines 2020-06-10 11:55:51 -04:00
Azhar Dewji
6c0a5021cd Merge branch 'dev/marian-anderson' into bigquery-auth-views 2020-06-10 11:54:38 -04:00
Jacob Beck
c9b34681b7 Merge pull request #2521 from azhard/bigquery-rename-relation
Added support for BigQuery relation renaming
2020-06-10 09:48:28 -06:00
Azhar Dewji
8422c3a9d3 grant_access no longer common and takes in grant_target_dict param 2020-06-10 11:35:18 -04:00
Azhar Dewji
daed251d0d Fix seed file name 2020-06-09 17:04:42 -04:00
Azhar Dewji
ddb6d791e8 Fixed CI errors 2020-06-09 16:28:00 -04:00
Azhar Dewji
107170163a Fixed relation creation 2020-06-09 15:00:29 -04:00
Azhar Dewji
7aa09e393c Undo accidental commented out test 2020-06-09 14:50:08 -04:00
Azhar Dewji
75cc3e40a5 Addressed PR comments 2020-06-09 14:43:59 -04:00
Azhar Dewji
78cf84310c Changed dataset_id to relation 2020-06-09 11:58:24 -04:00
Azhar Dewji
0f6622fab6 Added support for BigQuery relation renaming 2020-06-09 11:17:17 -04:00
Azhar Dewji
bb20df0db9 #1718 - Added support for BigQuery authorized views 2020-06-08 17:30:26 -04:00
Github Build Bot
8a2b5f53f6 Release dbt v0.18.0b1 2020-06-08 21:27:13 +00:00
Jacob Beck
e30db9816e Merge branch 'dev/octavius-catto' into dev/marian-anderson 2020-06-08 14:52:29 -06:00
Github Build Bot
5f8efc3aa8 Release dbt v0.17.0 2020-06-08 17:12:47 +00:00
Jacob Beck
f3565f3f70 Merge pull request #2513 from southpolemonkey/dev/marian-anderson
(#2511) Remove log messages about opening connections in set_connecti…
2020-06-08 10:06:34 -06:00
Chenxuan Rong
2be7ee6ee0 update CHANGELOG 2020-06-08 23:42:22 +10:00
Chenxuan Rong
f7a8cd1250 (#2511) Move opening connection message to LazyHandle module 2020-06-08 15:17:07 +10:00
Chenxuan Rong
3f5cc224a6 (#2511) Move opening connection message inside open method 2020-06-05 08:18:36 +10:00
Jacob Beck
26363638bc Merge pull request #2505 from aburgel/move-pytest-logbook
Move pytest-logbook dependency to dev_requirements.txt
2020-06-04 10:37:30 -06:00
Chenxuan Rong
1cd51181eb (#2511) Remove log messages about opening connections in set_connection_name. 2020-06-05 00:53:02 +10:00
Alex Burgel
a1d5c340e8 Update changelog 2020-06-04 10:33:11 -04:00
Alex Burgel
6f1763317a Update changelog 2020-06-04 10:31:24 -04:00
Alex Burgel
8973393333 Move pytest-logbook dependency to dev_requirements.txt 2020-06-04 10:31:24 -04:00
Jacob Beck
a5d53c4453 Add dbt:0.17.0rc4 dockerfiles and requirements 2020-06-02 08:19:47 -06:00
Jacob Beck
85fda89413 Bump version: 0.17.0rc3 → 0.17.0rc4 2020-06-02 07:48:09 -06:00
Jacob Beck
7b6c11adb7 Changelog update for 0.17.0rc4 2020-06-02 07:47:38 -06:00
Jacob Beck
7e14666802 Merge pull request #2502 from fishtown-analytics/fix/homebrew-packaging
Generate the homebrew formula pypi urls in the build script (#2500)
2020-06-02 07:11:07 -06:00
Jacob Beck
6fbf6721b9 Merge pull request #2509 from fishtown-analytics/fix/snowflake-get-columns-missing-relation
fix get_columns_in_relation on snowflake (#2504)
2020-06-02 06:34:52 -06:00
Jacob Beck
ec76498686 PR feedback: remove extra class def 2020-06-02 06:34:40 -06:00
Jacob Beck
05634e61a7 Merge pull request #2508 from scarrucciu/patch-1
Fix --dependency arg bug in create_adapter_plugin
2020-06-01 12:43:29 -06:00
Spencer Carrucciu
2dd7ef5302 Update CHANGELOG.md 2020-06-01 12:47:15 -04:00
Jacob Beck
49a5a556e9 fix get_columns_in_relation on snowflake
Also add a test for adapter methods to catch this if it regresses
2020-06-01 10:28:42 -06:00
Spencer Carrucciu
855dd98c62 Fix --dependency arg bug in create_adapter_plugin
Fix bug in create_adapter_plugin when a dependency is not passed in.
2020-06-01 07:37:33 -04:00
Jacob Beck
2759c9e3bd Generate the homebrew formula pypi urls in the build script, check versions 2020-05-29 14:50:09 -06:00
Jacob Beck
594b8b786a Merge pull request #2501 from fishtown-analytics/fix/better-filename-errors
add project names/filenames/values to rendering errors (#2499)
2020-05-29 11:30:28 -06:00
Jacob Beck
3387d085b4 add project names/filenames/values to rendering errors 2020-05-29 10:21:38 -06:00
Jacob Beck
ee6b842f9a Merge pull request #2491 from fishtown-analytics/fix/pin-protobufs
Put protobuf in version jail
2020-05-27 10:52:07 -06:00
Jacob Beck
fce11d7b46 Pin protobuf 2020-05-27 10:00:16 -06:00
Jacob Beck
48c9b8abdc Merge branch 'fix/catalog-allow-none' into dev/octavius-catto 2020-05-27 08:01:05 -06:00
Jacob Beck
c3c99f317e Merge pull request #2485 from Raalsky/direct-nodes-selector
Direct child model selector
2020-05-26 16:18:55 -06:00
Jacob Beck
9deb50f812 Add dbt:0.17.0rc3 dockerfiles and requirements 2020-05-26 14:55:16 -06:00
Jacob Beck
18c02ea991 Bump version: 0.17.0rc2 → 0.17.0rc3 2020-05-26 14:51:11 -06:00
Jacob Beck
3e23a5ff75 Merge pull request #2489 from fishtown-analytics/fix/catalog-allow-none
Make databases optional
2020-05-26 14:50:11 -06:00
Jacob Beck
076bb9c356 Allow column stats description fields to be empty 2020-05-26 14:06:31 -06:00
Jacob Beck
c21af17a30 Make databases optional 2020-05-26 11:59:20 -06:00
Raalsky
1d298ea5cf Added type annotations and errors types refactored 2020-05-26 19:03:29 +02:00
Raalsky
f214ebf4e9 Graph methods refactor 2020-05-26 17:55:45 +02:00
Raalsky
ce5ca6f845 Updated CHANGELOG 2020-05-24 17:05:06 +02:00
Raalsky
cc2be00abb Integration tests added for direct child model selection 2020-05-24 16:56:39 +02:00
Raalsky
bbfcce1cc6 Unit tests added for direct model selection 2020-05-24 16:42:53 +02:00
Raalsky
d81961ccee flake8 2020-05-24 16:07:49 +02:00
Raalsky
c1da5dd513 Added direct child syntax to node selector 2020-05-24 14:08:22 +02:00
Raalsky
7c201fed81 Added internal methods from networkx library 2020-05-24 12:10:15 +02:00
Raalsky
f3a97b6c08 Initial refactor of ancestors/descendants selector methods 2020-05-24 12:03:53 +02:00
Jacob Beck
6509067856 Add dbt:0.17.0rc2 dockerfiles and requirements 2020-05-22 14:22:13 -06:00
Jacob Beck
6482718dbf Bump version: 0.17.0rc1 → 0.17.0rc2 2020-05-22 14:18:45 -06:00
Jacob Beck
6dac4c76eb Merge pull request #2481 from fishtown-analytics/fix/docs-perf
Improve docs performance (#2480)
2020-05-22 14:16:43 -06:00
Jacob Beck
0d825eb590 Check for instances of "}}" and "%}" as well when deciding whether to render so users get compile errors instead of wrong data in that case 2020-05-22 13:34:32 -06:00
Jacob Beck
6cc0d9103d Add changelog entry, push another entry to its real release + add contributors entry 2020-05-21 16:55:06 -06:00
Jacob Beck
514a1ad7d0 On windows, docs now include their original carriage returns before newlines 2020-05-21 16:53:17 -06:00
Jacob Beck
7f99353ca0 add libyaml-dev so we get that libyaml goodness
And point tests to the new image
2020-05-21 16:53:17 -06:00
Jacob Beck
a58657403e avoid double-building the flat graph - we already built it at parse time 2020-05-21 16:53:17 -06:00
Jacob Beck
b9a86df795 if we have "cyaml" available, use it
This is a significant performance improvement with a lot of yaml files!
2020-05-21 16:53:17 -06:00
Jacob Beck
cf94ce6a54 add caching on the manifest for docs, ref, and source calls
removed the internal `find_*_by_name` calls for things that use the cache, just do them through `resolve_*`
fix unit tests to point at `resolve_*` now
2020-05-21 16:53:17 -06:00
Jacob Beck
e9d112ac39 Improve docs performance, especially on docs blocks that do not use jinja internally
Make docs parsing avoid a bunch of template nonsense and just use get_rendered
Add a check to get_rendered that skips the block if we can prove it should render to its input
  - but not in native mode
2020-05-21 16:53:17 -06:00
Jacob Beck
f4c6bf30b6 Merge pull request #2358 from ChristianKohlberg/feature/snowflake-expose-query-id
Expose Snowflake query id in case of an exception raised by connector
2020-05-21 11:35:42 -06:00
Jacob Beck
626522d1bb Merge pull request #2477 from fishtown-analytics/fix/global-vars
Fix some issues around global-scoped vars
2020-05-21 07:47:14 -06:00
Jacob Beck
58f3905af4 Merge pull request #2478 from fishtown-analytics/fix/source-patching-perf
fix source patching perf with no patches
2020-05-21 07:18:45 -06:00
ChristianKohlberg
89b4984009 Merge branch 'dev/octavius-catto' into feature/snowflake-expose-query-id 2020-05-20 23:15:38 +02:00
Christian Kohlberg
804c779359 expose snowflake query id if available 2020-05-20 23:12:05 +02:00
ChristianKohlberg
d699add216 Update CHANGELOG.md
Co-authored-by: Jacob Beck <beckjake@users.noreply.github.com>
2020-05-20 21:15:18 +02:00
Jacob Beck
e36fb3cc17 clean up replace() calls 2020-05-20 12:55:06 -06:00
Jacob Beck
fcf57a5066 if there is no patch, do not patch it 2020-05-20 12:48:38 -06:00
Jacob Beck
715c886dad appease flake8/mypy
Change variables named `l` to `log`
Make IsFQNResource runtime type-checkable and use that to handle the FQN check
Make the GenerateNameProvider its own top-level Provider type
2020-05-20 11:34:14 -06:00
Jacob Beck
e2cd45b18a Fix some issues around global-scoped vars
Fix global-level var rewriting from v2 -> v1
Add top-level vars to more lookup fields
When a user calls var() in a `generate_*_name` macro, on failure it now raises (like at runtime)
When a node has no FQN, pretend the FQN is just the package name when returning vars
Add tests
2020-05-20 10:59:15 -06:00
Jacob Beck
a8f8708418 Merge pull request #2475 from dmateusp/dmateusp/fix_docker_entrypoints
Fix ENTRYPOINT to use 'dbt'
2020-05-20 10:40:34 -06:00
Daniel Mateus Pires
ca5b02af9a Add Contributors 2020-05-20 16:32:06 +01:00
Daniel Mateus Pires
832d43efdc Add CHANGELOG note 2020-05-20 15:24:32 +01:00
Daniel Mateus Pires
e9daff7616 Fix ENTRYPOINT to use 'dbt' 2020-05-20 15:15:26 +01:00
Jacob Beck
22999a9861 Merge pull request #2467 from fishtown-analytics/fix/macos-tests
Fix tests to run on macos
2020-05-18 17:03:57 -06:00
Jacob Beck
64edf70b93 Fix tests to run on macos 2020-05-18 14:10:09 -06:00
Jacob Beck
75dbb0bc19 Merge pull request #2431 from alf-mindshift/dev/octavius-catto
Make it possible to hide macros in docs
2020-05-18 08:19:23 -06:00
Jacob Beck
e34439dcd7 Merge branch 'dev/marian-anderson' into dev/octavius-catto 2020-05-18 08:19:07 -06:00
Jacob Beck
9c84ce1e6f Merge pull request #2461 from fishtown-analytics/fix/typo-failfast-msg
Fix typo in failfast msg
2020-05-18 08:11:04 -06:00
Jacob Beck
907003881e Merge pull request #2464 from fishtown-analytics/fix/duplicate-sources-crash
Fix duplicate sources crash
2020-05-18 08:10:35 -06:00
Jacob Beck
4e2ec6b7f8 Merge pull request #2417 from Raalsky/feature/model-intersection-syntax
Model intersection syntax
2020-05-18 08:09:14 -06:00
Alf Lervag
4b90ff892f Updated tests and changelog 2020-05-18 15:25:52 +02:00
Alf Lervag
5591a82a15 Make it possible to hide macros in docs 2020-05-18 15:21:25 +02:00
Raalsky
598c06f5b0 CHANGELOG update 2020-05-16 00:12:38 +02:00
Raalsky
bf12001c04 Added concatenation and intersection unittests 2020-05-16 00:06:52 +02:00
Raalsky
e59df0fbb5 Added tests for model intersection syntax 2020-05-16 00:06:52 +02:00
Raalsky
47f5c51b2d Working prototype for model intersection syntax 2020-05-16 00:06:52 +02:00
Raalsky
e9b982b8ea Initial tests for model intersection syntax 2020-05-16 00:06:51 +02:00
Jacob Beck
770cf71c13 Merge branch 'dev/octavius-catto' into dev/marian-anderson 2020-05-15 15:00:55 -06:00
Jacob Beck
5734fb897c Merge pull request #2462 from fishtown-analytics/fix/version-2-test-intermittent-failures
Fix config-version 2 test intermittent failures
2020-05-15 15:00:33 -06:00
Jacob Beck
df8aa642c5 Fix crash on duplicate sources, add a test 2020-05-15 14:04:41 -06:00
Jacob Beck
5c76cfb071 remove the need for strict=False on most tests that have dependencies by upgrading the dependencies to config-version: 2 2020-05-15 11:58:26 -06:00
Jeremy Cohen
75f8bc2679 Fix typo in failfast msg 2020-05-15 13:36:51 -04:00
Jacob Beck
208c1cfb1a reset deprecation warnings after tests, clean up the fallout 2020-05-15 09:36:00 -06:00
Jacob Beck
1d543b3737 Merge pull request #2455 from fishtown-analytics/fix/adapter-plugin-config-version
set config-version: 2 in the generated plugin project
2020-05-14 15:05:39 -06:00
Jacob Beck
b41b72c316 set config-version: 2 in the generated plugin project 2020-05-14 14:07:42 -06:00
Jacob Beck
26703df4d7 Merge pull request #2450 from fishtown-analytics/fix/status-line-relations
In dbt run, log relations for the status line instead of explicit node info
2020-05-14 12:24:16 -06:00
Jacob Beck
f490f6af98 In dbt run, log relations for the status line instead of explicit node parts 2020-05-14 11:09:16 -06:00
Claire Carroll
70ad551cf4 Merge pull request #2446 from fishtown-analytics/slack-references
Replace slack references
2020-05-14 06:36:16 -04:00
Jacob Beck
48705b6d8c Merge pull request #2448 from fishtown-analytics/fix/bigquery-create-schema-macro
Fix create_schema macro on bigquery (#2445)
2020-05-13 14:40:49 -06:00
Jacob Beck
6e5c8750f6 Fix create_schema macro on bigquery 2020-05-13 13:35:11 -06:00
Claire Carroll
1f78f30756 Replace slack references 2020-05-13 13:56:27 -04:00
Drew Banin
df5b97c614 Merge pull request #2427 from RealAshampoo/feature/bigquery-cost-limit
Add BigQuery option to define a maximum query cost limit
2020-05-13 13:05:21 -04:00
Jacob Beck
1de892bf0e Merge pull request #2442 from fishtown-analytics/fix/empty-persist-docs
Backport #2440 to 0.17
2020-05-13 08:47:20 -06:00
Jacob Beck
457cbbd954 Merge pull request #2440 from fishtown-analytics/fix/empty-persist-docs
Fix empty SQL in persist docs (#2439)
2020-05-13 08:39:32 -06:00
haukeduden
842edd62a6 Merge branch 'dev/octavius-catto' into feature/bigquery-cost-limit 2020-05-13 09:55:11 +02:00
Jacob Beck
7b374a4b08 Merge pull request #2438 from fishtown-analytics/feature/full-refresh-config
make full refresh a config item
2020-05-12 15:28:19 -06:00
Jacob Beck
d0f5664358 PR feedback - config is still not a dict 2020-05-12 14:49:29 -06:00
Jacob Beck
053546402d Only run alter_column_comment if there are columns defined
Also remove unused (and confusing/misleading) test data
2020-05-12 14:42:37 -06:00
Jacob Beck
0fa9d0ab12 make full refresh a config item
Added integration tests, fixed existing tests
2020-05-12 12:10:03 -06:00
Jacob Beck
9d0eab6305 Bump version: 0.17.0rc1 → 0.18.0a1 2020-05-12 11:04:43 -06:00
Jacob Beck
79a90d0671 Add dbt:0.17.0rc1 dockerfiles and requirements 2020-05-12 10:51:35 -06:00
Jacob Beck
451e95e48d Bump version: 0.17.0b1 → 0.17.0rc1 2020-05-12 10:45:59 -06:00
Jacob Beck
d3f535a0a7 Changelog update 2020-05-12 10:45:17 -06:00
Jacob Beck
4fc54dae32 Merge pull request #2435 from fishtown-analytics/fix/no-testing-homebrew
Do not test homebrew packages
2020-05-12 10:44:46 -06:00
Jacob Beck
4e70d078ef Do not test homebrew packages 2020-05-12 09:50:37 -06:00
Drew Banin
31673d8acd Merge pull request #2433 from fishtown-analytics/docs/0.17.0-hide-from-search
Docs site updates for 0.17.0: Hide hidden models from search
2020-05-12 11:03:31 -04:00
Drew Banin
d5e127bfea Docs site updates for 0.17.0: Hide hidden models from search 2020-05-12 10:30:41 -04:00
Hauke Duden
f40374deb9 Merge branch 'dev/octavius-catto' into feature/bigquery-cost-limit 2020-05-12 16:15:22 +02:00
Hauke Duden
f8cbea4dbf added whitespace around operator to pass flake8 tests 2020-05-12 15:37:19 +02:00
Hauke Duden
e3bd6cd5ef - added pull request link to changelog
- added entry to contributors list
2020-05-12 15:35:57 +02:00
Drew Banin
71e7b0ab71 Merge pull request #2429 from fishtown-analytics/fix/track-package-deps-for-hub-site
Track project id for deps events
2020-05-12 09:32:51 -04:00
Drew Banin
fd662a1855 Merge pull request #2422 from azhard/fix/bigquery-view-spacing
Fix for extra spacing and parentheses for BigQuery views
2020-05-12 09:30:36 -04:00
Drew Banin
2a8940415d quiet new pep8 check 2020-05-11 20:57:00 -04:00
Drew Banin
b63a271d55 (#23551) track project id for deps events 2020-05-11 20:48:28 -04:00
Hauke Duden
47e70efca6 updated changelog 2020-05-11 20:16:58 +02:00
Hauke Duden
0f3dd88029 - added maximum_bytes_billed option to bigquery profiles
- added basic unit test for maximum_bytes_billed
2020-05-11 20:06:25 +02:00
Azhar Dewji
35a052f4ab Updated CHANGELOG links 2020-05-08 15:39:40 -04:00
Azhar Dewji
a31e78817e Re-add dropped semi-colon 2020-05-08 15:38:45 -04:00
Azhar Dewji
f250d7ab47 Fix for extra spacing and parentheses for BigQuery views 2020-05-08 15:36:29 -04:00
Jacob Beck
8686ab9a9d Merge pull request #2420 from fishtown-analytics/fix/dbt-init-v2-project
Fix dbt init to clone a v2 project (#2404)
2020-05-08 09:18:08 -06:00
Jacob Beck
2cd98c2c60 Fix dbt init to clone a v2 project 2020-05-08 08:41:28 -06:00
Jacob Beck
c9eec4fa06 Merge pull request #2411 from fishtown-analytics/fix/always-preserve-case
Preserve the case of schemas and databases when listing relations (#2403)
2020-05-08 08:15:07 -06:00
Drew Banin
6230c608d0 Merge pull request #2419 from fishtown-analytics/may-2020-update-readme
Update README.md
2020-05-07 23:11:12 -04:00
Jacob Beck
ab8392b856 Update core/dbt/task/runnable.py
Co-authored-by: Drew Banin <drew@fishtownanalytics.com>
2020-05-07 20:02:41 -06:00
Jacob Beck
067f02f9d8 Merge pull request #2418 from fishtown-analytics/fix/plugin-versions-search
fix version-loading logic to respect multiple plugins at the same path (#2410)
2020-05-07 19:10:04 -06:00
Drew Banin
0c1f8550b8 Update README.md 2020-05-07 20:07:02 -04:00
Jacob Beck
7287204279 fix up the unit tests 2020-05-07 14:32:52 -06:00
Jacob Beck
e392212c0e Change list_relations_without_caching macro to take a single argument
The argument is a Relation object with no identifier field, configured with the appropriate quoting information

Unique quoted/unquoted representations will be treated as distinct
The logic for generating what schemas to search for relations is now distinct from the catalog search logic.
Schema creation/dropping takes a similar relation argument
Add tests
2020-05-07 14:13:18 -06:00
Jacob Beck
dacfe38429 fix version-loading logic to respect multiple plugins at the same path 2020-05-07 14:08:43 -06:00
Jacob Beck
1eb75d9be7 Merge pull request #2416 from mikaelene/patch-1
Update CHANGELOG.md
2020-05-07 13:06:23 -06:00
Mikael Ene
a122d35e43 Update CHANGELOG.md
Added myself as contributor
2020-05-07 20:52:33 +02:00
Jacob Beck
445543e256 Merge pull request #2343 from Raalsky/warn-nothing-selected
Added warning to nodes selector if nothing was matched
2020-05-07 11:12:03 -06:00
Rafał Jankowski
cfeffe40f6 Typos corrected 2020-05-07 18:12:17 +02:00
Jacob Beck
dfb72ccb2e Merge pull request #2413 from azhard/fix/bigquery-list-schemas
Fix error when trying to call list_schemas macro for BigQuery adapter
2020-05-07 09:56:56 -06:00
Jacob Beck
0f4a369aeb Merge pull request #2414 from mikaelene/alias_returned_columns
alias for schema tests macros
2020-05-07 09:55:41 -06:00
Rafał Jankowski
385f7f2c24 Merge branch 'dev/octavius-catto' into warn-nothing-selected 2020-05-07 17:44:08 +02:00
Raalsky
aa36ed5aa4 CHANGELOG updated 2020-05-07 17:39:17 +02:00
Rafał Jankowski
c87096993e Nodes selector refactor and added warning for non-existence of models for spec 2020-05-07 17:36:52 +02:00
Drew Banin
784ab79eec Merge pull request #2402 from fishtown-analytics/feature/bigquery-column-comments
Feature/bigquery column comments
2020-05-07 10:55:09 -04:00
Drew Banin
7122a3f90b Add column comments on BigQuery for views, tables, and incrementals 2020-05-07 09:48:18 -04:00
Mikael Ene
868a447c58 alias for schema tests macros 2020-05-07 07:38:21 +02:00
Azhar Dewji
002244e3b3 Fixed CHANGELOG links and added contributors section 2020-05-06 21:41:47 -04:00
Azhar Dewji
5a319d6ee1 Updated CHANGELOG.md 2020-05-06 17:21:51 -04:00
Azhar Dewji
a1ca9025e7 Fix error when trying to call list_schemas macro for BigQuery adapter 2020-05-06 17:10:04 -04:00
Jacob Beck
9cd7cbc9e3 Merge pull request #2409 from fishtown-analytics/fix/plugin-version-files
update __version__.py files, add them to .bumpversion.cfg (#2408)
2020-05-06 10:19:03 -06:00
Jacob Beck
2d3dc1f6cc update __version__.py files, add them to .bumpversion.cfg 2020-05-06 09:41:10 -06:00
Drew Banin
d01c1614a5 Merge pull request #2407 from fishtown-analytics/fix/dupe-resource-type-error
Fix for error in error handler for duped data tests
2020-05-06 11:00:19 -04:00
Drew Banin
fb5320f7bc (#2406) Fix for error in error handler for duped data tests 2020-05-06 10:10:46 -04:00
Jacob Beck
7c916e9bdb Merge pull request #2321 from snowflakeseitz/column_comments
Column comments
2020-05-05 12:23:02 -06:00
Andrew Seitz
b774702ee8 removing duplicates in common 2020-05-05 10:21:36 -07:00
Andrew Seitz
ffaaacc148 adding in changelog 2020-05-05 10:05:14 -07:00
Jacob Beck
9de9335554 Merge pull request #2400 from fishtown-analytics/fix/disabling-tracking-resets-invocation-id
Fix: disabling tracking resets invocation ID (#2398)
2020-05-05 10:25:46 -06:00
Drew Banin
feae21992a Merge pull request #2399 from fishtown-analytics/docs/0.17.0
Update docs site for 0.17.0
2020-05-05 12:23:19 -04:00
Jacob Beck
18cfe81e00 use is not instead of != on time comparison check 2020-05-05 09:41:46 -06:00
Jacob Beck
869bdc454d Disabling tracking no longer resets the invocation ID
- re-use the same active user
- add disable_tracking method that does not reset invocation ID
2020-05-05 09:31:46 -06:00
Drew Banin
6725234466 Fix missing source columns in docs site 2020-05-05 11:08:47 -04:00
Drew Banin
05bf1512f9 (#2284) bump docs build 2020-05-05 09:07:21 -04:00
Andrew Seitz
bd2d1f7722 fixing failing test on incremental 2020-05-04 14:49:05 -07:00
Andrew Seitz
f58bc49176 fixing default 2020-05-04 14:49:05 -07:00
Andrew Seitz
5cabafc257 fixing rebase error 2020-05-04 14:49:05 -07:00
Andrew Seitz
0fdff04fc6 fixing rebase 2020-05-04 14:48:55 -07:00
Andrew Seitz
1448d0b0c2 adding in support for views 2020-05-04 14:48:05 -07:00
Andrew Seitz
f843e658a1 fixing spacing 2020-05-04 14:48:05 -07:00
Andrew Seitz
06ffb6dd53 adding in Drews feedback 2020-05-04 14:48:05 -07:00
Andrew Seitz
78110e17e5 basics to get column comments working 2020-05-04 14:48:05 -07:00
Andrew Seitz
74df1a1c31 adding persist docs tests 2020-05-04 14:48:05 -07:00
Andrew Seitz
d9862af8e1 adding in support for views 2020-05-04 14:47:46 -07:00
Andrew Seitz
ea82d8f8ca fixing spacing 2020-05-04 14:47:46 -07:00
Andrew Seitz
8de3235a63 adding in Drews feedback 2020-05-04 14:47:43 -07:00
Andrew Seitz
eb3ed5744b basics to get column comments working 2020-05-04 14:47:19 -07:00
Andrew Seitz
b98a4d82c3 rebasing 2020-05-04 14:42:47 -07:00
Jacob Beck
ac562d9388 Add dbt:0.17.0b1 dockerfiles and requirements 2020-05-04 15:09:43 -06:00
Jacob Beck
d0cb960455 Bump version: 0.17.0a1 → 0.17.0b1 2020-05-04 14:59:39 -06:00
Jacob Beck
7db16f8521 Merge pull request #2395 from fishtown-analytics/fix/bigquery-numbery-datasets
Add a special as_text filter to the native env (#2384)
2020-05-04 14:50:01 -06:00
Jacob Beck
229d3986c2 PR feedback: text filters for everyone 2020-05-04 14:17:13 -06:00
Jacob Beck
c237c8ee9e Merge pull request #2393 from fishtown-analytics/fix/suppress-bigquery-job-sql
Suppress the bigquery job sql in exception messages (#2383)
2020-05-04 13:49:22 -06:00
Jacob Beck
bd9a1320fa Merge pull request #2349 from franloza/fix/2173
Use original path to mimick resource subdirectory structure
2020-05-04 13:46:11 -06:00
Jacob Beck
9eb4506117 Merge branch 'dev/octavius-catto' into fix/2173 2020-05-04 13:45:25 -06:00
Jacob Beck
58c95bb399 Merge pull request #2389 from sethwoodworth/patch-1
Adds missing comma to `dbt compile` help text
2020-05-04 13:43:57 -06:00
Jacob Beck
af5af829cf add missing as_text to sources tests 2020-05-04 13:41:45 -06:00
Jacob Beck
0961bdb506 Add a special as_text filter to the native env that tells dbt to render the value as a string no matter what.
Use like "{{ 1991 | as_text }}" -> "1991"
Also, moved the weird special quoting handling logic out and instead avoid calling the renderer on column_name arguments
2020-05-04 13:18:49 -06:00
Jacob Beck
7b9111d386 Merge branch 'dev/octavius-catto' into patch-1 2020-05-04 10:37:39 -06:00
Jacob Beck
f8580a23ff Suppress the bigquery job sql in exception messages 2020-05-04 10:35:50 -06:00
Drew Banin
b5f99881fe Merge pull request #2390 from fishtown-analytics/fix/snapshot-staging-is-not-atomic
Fix/snapshot staging is not atomic
2020-05-04 12:24:49 -04:00
Seth Woodworth
6266216fda Merge branch 'dev/octavius-catto' into patch-1 2020-05-04 12:22:43 -04:00
Seth Woodworth
ca39ac5f00 Update changelog with PR #2389 2020-05-04 12:21:25 -04:00
Drew Banin
5d99798c7d Merge branch 'dev/octavius-catto' into fix/snapshot-staging-is-not-atomic 2020-05-04 11:00:29 -04:00
Drew Banin
24ed84dddd Merge pull request #2391 from fishtown-analytics/fix/snapshot-use-updated-at-timestamp-for-comparisons
Fix for changing snapshot strategy types
2020-05-04 10:39:23 -04:00
Drew Banin
e82e68d22a update changelog 2020-05-04 10:39:00 -04:00
Jacob Beck
e6bb06030c Merge pull request #2378 from fishtown-analytics/feature/pg-rs-persist-docs
Column and table comments for postgres/redshift (#2333)
2020-05-04 07:47:23 -06:00
Jacob Beck
595c82c1f1 Merge pull request #2387 from fishtown-analytics/fix/ambiguous-alias-exception
fix the ambiguous alias exception to respect databases
2020-05-04 07:18:55 -06:00
Jacob Beck
a46855821b Merge branch 'dev/octavius-catto' into fix/ambiguous-alias-exception 2020-05-04 07:18:27 -06:00
Drew Banin
89ba3e707a Tweak code comment 2020-05-04 09:00:20 -04:00
Drew Banin
2cd6a2d7a0 (#2350) Fix for changing snapshot strategy types 2020-05-02 12:38:57 -04:00
Drew Banin
1f758f90d0 Merge pull request #2376 from fishtown-analytics/fix/handle-query-array-types
Fix/handle query array types
2020-05-02 10:00:14 -04:00
Drew Banin
8681dd8c93 (#1884) Fix for non-atomic snapshot staging tables 2020-05-02 09:28:31 -04:00
Drew Banin
0781cef8b1 (#2337) Handle array values in agate dataframe building 2020-05-02 09:23:03 -04:00
Seth Woodworth
5edbe5758e Adds missing comma to dbt compile help text 2020-05-01 13:50:07 -04:00
Jacob Beck
20c5c4c3dd fix the ambiguous alias exception to respect databases
The alias name check is now tied to the behavior of adapter.Relation.create_from(...)
Plugins that override the `include` of their relations will use whatever they render to for the check
the actual exception-raising code gets the name that was compared instead of generating its own
Finally, I added a reasonable fallback behavior since this method was exposed to the context
2020-05-01 10:43:38 -06:00
Jacob Beck
e376c14e8a Merge branch 'dev/octavius-catto' into fix/2173 2020-05-01 08:45:08 -06:00
Drew Banin
68babfb4bb Merge pull request #2200 from fishtown-analytics/fix/sql-header-in-incrementals
Fix missing sql_header for incremental models
2020-05-01 10:11:59 -04:00
Drew Banin
f3d4377fdd Merge branch 'dev/octavius-catto' into fix/sql-header-in-incrementals 2020-05-01 10:10:21 -04:00
Fran Lozano
efaeb6786e Merge remote-tracking branch 'upstream/dev/octavius-catto' into fix/2173 2020-05-01 12:32:44 +02:00
Fran Lozano
96d2978a23 Update CHANGELOG.md 2020-05-01 12:29:37 +02:00
Jacob Beck
6e1665d1fb Merge pull request #2382 from fishtown-analytics/fix/remove-bq-location
remove location and information_schema.schematas query from catalog (#2320)
2020-04-30 18:47:08 -06:00
Jacob Beck
c6603be194 Handle funky incremental behavior in case existing materializations use it
- also fixed the default incremental to include that it is creating a table
2020-04-30 14:49:34 -06:00
Jacob Beck
d17e706351 improve parsing errors, include line:char instead of absolute character index 2020-04-30 14:49:34 -06:00
Jacob Beck
acd978e054 Column and table comments
- add some table comment framework stuff
- have redshift/postgres catalogs include table comments
- have redshift/postgres add comments to columns/tables/views
- push some bigquery-specific formatting into bigquery
- add tests for table comments
2020-04-30 14:49:34 -06:00
Jacob Beck
ed6591e450 remove location and information_schema.schematas query from catalog 2020-04-30 13:52:13 -06:00
Fran Lozano
fc61869ea8 Revert "Fix wrong path in test"
This reverts commit a3e6518c41.
2020-04-30 01:24:43 +02:00
Fran Lozano
80501e7f8f Fix wrong path in test 2020-04-30 01:19:09 +02:00
Fran Lozano
a3e6518c41 Fix wrong path in test 2020-04-30 00:58:19 +02:00
Fran Lozano
b71496a25d Fix wrong paths in tests 2020-04-30 00:45:55 +02:00
Fran Lozano
391ef70e67 Change build paths in docs generate tests according to new path format 2020-04-30 00:21:20 +02:00
Drew Banin
29e2bbc0c2 Merge branch 'dev/octavius-catto' into fix/sql-header-in-incrementals 2020-04-29 15:52:44 -04:00
Jacob Beck
5884f7df55 Merge pull request #2357 from fishtown-analytics/feature/schema-yml-rendering-3
Schema yaml rendering and source overrides
2020-04-29 13:52:22 -06:00
Jacob Beck
6708046951 Merge pull request #2341 from dcereijodo/log-depends-on-extra
log 'depends_on_nodes' node attribute in the log record 'extra' field
2020-04-29 07:13:33 -06:00
Jacob Beck
4751a5f3e2 Merge pull request #2361 from Fokko/patch-1
Add python_requires to setup.py
2020-04-28 13:17:30 -06:00
Jacob Beck
0b82def5f5 support windows in source overrides tests 2020-04-28 12:49:49 -06:00
Jacob Beck
ee529a5472 fix catalog unit tests 2020-04-28 11:52:03 -06:00
Jacob Beck
d38344125e move catalog nodes entry into sources
Fix tests accordingly
2020-04-28 11:37:00 -06:00
Jacob Beck
caee9415e4 PR feedback
- add paths to source patches, apply them to parsed source definitions
- warn on unused source patches
- fix error on duplicate source patches
- add sources back into the catalog
- update changelog
2020-04-28 11:37:00 -06:00
Jacob Beck
7e85ad95c7 Add some very special quoting rules for rendering
Handle quoting in render() instead of get_rendered code
 - avoid stripping the quotes from a string that was returned as jinja
2020-04-28 11:29:06 -06:00
Jacob Beck
86a35d6c13 Add file paths to dbt_project.yml when it fails to render 2020-04-28 11:29:06 -06:00
Jacob Beck
ff3360afc0 flip the flag
Add/modify tests to test native rendering/config application
2020-04-28 11:29:06 -06:00
Jacob Beck
eea322e647 update changelog 2020-04-28 11:29:05 -06:00
Jacob Beck
120f0f0959 Tests
add override tests
fix some unit tests
2020-04-28 11:28:48 -06:00
Jacob Beck
38443cf3f5 implement source patching 2020-04-28 11:28:48 -06:00
Jacob Beck
c69f28e0a0 move sources into their own dict 2020-04-28 11:28:48 -06:00
Jacob Beck
704e44e479 add source patches, distinguished by override, store them on run results/the manifest 2020-04-28 11:28:48 -06:00
Jacob Beck
c19a7d7b32 Docs now get the schema.yml parser vars
The SchemaParser now renders the entire schema.yml except tests/descriptions
Snapshot config resolution
2020-04-28 11:28:48 -06:00
Jacob Beck
7323ddbabf Give sources a sane FQN, initial schema var rendering stuff 2020-04-28 11:28:48 -06:00
Jacob Beck
0b7dc326e9 remove docs parse context - not really used 2020-04-28 11:28:48 -06:00
Jacob Beck
afc0341b54 Merge pull request #2363 from fishtown-analytics/fix/vars-invalidation
Vars invalidation (#2265)
2020-04-28 10:06:22 -06:00
Jacob Beck
0df8462562 Merge branch 'dev/octavius-catto' into fix/vars-invalidation 2020-04-28 08:36:47 -06:00
David
305ace528a Merge branch 'dev/octavius-catto' into log-depends-on-extra 2020-04-28 09:28:45 +02:00
Drew Banin
f7b8e5c861 Merge pull request #2348 from fishtown-analytics/fix/decimal-to-json-bq
Fix for non-json-serializable values in BigQuery nested columns
2020-04-27 15:22:16 -04:00
Jacob Beck
8bf5dd0502 Merge pull request #2364 from fishtown-analytics/feature/webbrowser-no-deadlock
add --no-browser argument to docs serve
2020-04-27 12:04:19 -06:00
Fokko Driesprong
8d84da40e1 Add python_requires to setup.py
This will let people know that they are running
a too old version of Python

Signed-off-by: Fokko Driesprong <fokko@apache.org>
2020-04-27 19:35:05 +02:00
Jacob Beck
ab886cde16 added --no-browser argument to docs serve 2020-04-27 11:09:20 -06:00
Jacob Beck
ab5432daa0 add a test for vars that change manifest-compile behavior 2020-04-27 10:31:21 -06:00
Jacob Beck
60d4708f15 refactor test suite 2020-04-27 10:31:21 -06:00
Jacob Beck
7fba68fcbd invalidate the manifest when vars change 2020-04-27 10:31:21 -06:00
Fran Lozano
8f45ecebb4 Consider many-to-one relationships from nodes to files 2020-04-26 21:43:01 +02:00
Christian Kohlberg
c51ba50946 Expose Snowflake query id in case of an exception raised by the connector
* Allows to search for the query id inside the Snowflake query history
* Allows for better communication with snowflake support through query ids
2020-04-26 13:28:08 +02:00
Jacob Beck
616b32eb67 Merge pull request #2312 from fishtown-analytics/feature/dbt-project-v2
dbt project v2
2020-04-22 07:40:57 -06:00
Fran Lozano
278f764d61 Use original path to mimick resource subdirectory structure 2020-04-22 01:18:18 +02:00
Jacob Beck
5ed5693cd5 remove warning for "vars" in config blocks 2020-04-21 16:43:14 -06:00
Jacob Beck
236a087a42 skip deprecation warnings on non-graph runnable tasks so the deprecation warnings on graph runnable tasks are better 2020-04-21 15:10:17 -06:00
Drew Banin
044f8fce4e Update CHANGELOG.md
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2020-04-21 16:19:38 -04:00
Jacob Beck
618adf501d PR feedback: adjust deprecation warning copy 2020-04-21 13:46:39 -06:00
Jacob Beck
0da3b07fb3 add type annotations, add some unit tests for MultiDict 2020-04-21 13:15:59 -06:00
Drew Banin
4bea303c5f (#2336) Fix for non-json-serializable values in BigQuery nested columns 2020-04-21 15:00:04 -04:00
Jacob Beck
c18b72b53d PR feedback
Handle empty vars dict
Check sources for unused configs
  - add a test
Warn when dbt finds "vars" in "models", "seeds", etc blocks
  - add a test
Clean up enabled/disabled code to share between sources and nodes
  - only log downstream tests at debug level when a source is disabled
  - include the offending project name in the v1 deprecation message
  - Fix tests that care about error messages, add new ones
2020-04-21 12:41:19 -06:00
dcereijodo
ac9ea53e8e updated changelog and contributors 2020-04-21 08:24:07 +02:00
Jacob Beck
669a18564f instead of a special "config" key, use a "+" prefix and only require it on dict values
Make the package-duplicates test use a local dependency
2020-04-20 14:30:44 -06:00
Jacob Beck
c6b234673c deprecate v1 configs
Bump included projects to v2
fix tests
2020-04-20 11:56:14 -06:00
Jacob Beck
1adf994740 Convert tests to v2, fix them
Also add some missing agate methods to stubs
fix tests from splitting the config renderer
2020-04-20 11:56:14 -06:00
Jacob Beck
72d82e45ed Fix some mypy issues
Split up hooks to expose the hook dict
Remove some odd imports
2020-04-20 11:56:14 -06:00
Jacob Beck
070e13ded8 Split ConfigRenderer into parts
Docs now get the schema.yml parser vars
The SchemaParser now renders the entire schema.yml except tests/descriptions
2020-04-20 11:56:14 -06:00
Jacob Beck
c7d8031681 Support for v1/v2 config blocks
- Backwards compatiblity shims from v2 to v1 configs
- config merging for v2
- compatibility shim for parsing/contexts
- defer var lookups as late as possible
- fixed ContextConfigType to be a proper Union of the two context config types
- Fix adapter configs to be proper dataclasses
- fix unused config paths error
- make v2 config parsing not alter its source data
2020-04-20 11:31:00 -06:00
Jacob Beck
bf8932efaa Make node configs work like they should, clean up the json schema/parsing for snapshot configs 2020-04-20 11:30:59 -06:00
Jacob Beck
ff9e0cd4cf Add get_config_class_by_name function for adapters 2020-04-20 11:30:59 -06:00
Jacob Beck
1522d4cb2b Move configs into their own graph module
Configs all get defaults, a base class
Added SourceConfig
Introduced the idea of MergeBehavior and ShowBehavior
Implemented updates/merges based on MergeBehavior for configs
Implemented typed adapter-specific configs and behavior
2020-04-20 11:30:59 -06:00
Jacob Beck
fac8a286e8 Split SourceConfig into LegacyContextConfig and ConfigUpdater
Renamed config.config to config.build_config_dict()
mypy annotations
rename a module to avoid duplicating names
2020-04-20 11:18:29 -06:00
Jacob Beck
d37e74c62c Rework vars to support v1 and v2
- Rename project contract to projectv1
- Add load_dependencies and dependencies attrs to RuntimeConfig
  - Moved project loading from parser/manifest.py into methods of RuntimeConfig
- Move methods that check for unused configs from Project to RuntimeConfig
  - they require the adapter type to be correct
- Rework Var in provider vs base context to handle both styles of Var configuration
2020-04-20 11:18:28 -06:00
Jacob Beck
0a1862e92e refactor source config 2020-04-20 11:18:28 -06:00
dcereijodo
9dd16045fb log 'depends_on_nodes' node attribute in the log record 'extra' field 2020-04-18 11:11:49 +02:00
Jacob Beck
c05b45b241 Merge pull request #2339 from nickwu241/fix/0.16.1/UnsetProfileConfig-use-args-project-dir
Update UnsetProfileConfig.from_args to use args.project_dir if passed
2020-04-17 14:59:24 -06:00
Nick Wu
2a0f69ac5f Update test to change out of dir that will be deleted 2020-04-17 10:35:09 -07:00
Nick Wu
d5f50954c0 Add test for running simple dbt commands with --project-dir arg 2020-04-17 09:58:10 -07:00
Nicholas Wu
dda9289d77 Update core/dbt/config/runtime.py
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2020-04-17 07:55:56 -07:00
Nick Wu
66f9ee0b3e Updatet CHANGELOD.md with dbt deps fix 2020-04-17 03:22:28 -07:00
Nick Wu
72d2cab517 Update UnsetProfileConfig.from_args docstring 2020-04-17 02:40:45 -07:00
Nick Wu
a376d656a7 Update UnsetProfileConfig.from_args to use args.project_dir if passed 2020-04-17 02:30:30 -07:00
Jacob Beck
204fc25c21 Merge pull request #2291 from fishtown-analytics/feature/test-source-overrides-deps
Tests for source overriding (#2264)
2020-04-16 10:57:01 -06:00
Jacob Beck
bea8c72a82 Merge branch 'dev/octavius-catto' into feature/test-source-overrides-deps 2020-04-16 10:18:13 -06:00
Jacob Beck
16c1fcf4db Update CHANGELOG.md
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2020-04-16 10:17:12 -06:00
Jacob Beck
566f78a95c Merge pull request #2322 from fishtown-analytics/feature/show-objects-in-schema
use "show objects in schema" (#2174)
2020-04-16 07:26:31 -06:00
Jacob Beck
6ca707822e Merge branch 'dev/octavius-catto' into feature/show-objects-in-schema 2020-04-16 07:21:18 -06:00
Jacob Beck
59477a32bc Merge pull request #2324 from fishtown-analytics/feature/sf-describe-columns-in-table
Use "describe table" to get the columns in a relation on snowflake (#2260)
2020-04-16 07:19:12 -06:00
Jacob Beck
bc38750d47 PR feedback, tests 2020-04-15 10:11:30 -06:00
Jacob Beck
51278c0fbf Merge pull request #2325 from fishtown-analytics/fix/bigquery-include-vs-quote-policy
Fix include_policy vs. quote_policy bug in BigQueryRelation (#2188)
2020-04-15 08:54:09 -06:00
Jacob Beck
644e3e8721 Fix #2188
The BigQuery information schema previously used its quote policy as the basis
for a new include policy, rather than its include policy.
2020-04-14 14:42:29 -06:00
Jacob Beck
107bc5c217 Use "describe table" to get the columns in a relation on snowflake 2020-04-14 14:02:27 -06:00
Jacob Beck
1313445afe change list_relations_without_caching to use "show objects in schema" 2020-04-14 12:33:04 -06:00
Jacob Beck
79db8807a6 Merge pull request #2319 from fishtown-analytics/feature/compile-ls-relation-caches
dbt compile and dbt ls relation caches (#1705)
2020-04-14 12:22:33 -06:00
Drew Banin
4dc12c72c2 Merge pull request #2310 from fishtown-analytics/fix/incorrect-skip-counts
Fix incorrect SKIP count in stdout
2020-04-14 13:51:26 -04:00
Jacob Beck
ac427bdc0c populate_adapter_cache for everyone
Added the populate_adapter_cache to the GraphRunnableTask before_run method
 - Previously only Run used it
2020-04-14 10:44:25 -06:00
Drew Banin
96a3736cd7 (#2095) fix incorrect SKIP count in stdout 2020-04-14 12:30:31 -04:00
Jacob Beck
6424b65097 Merge pull request #2318 from fishtown-analytics/fix/bq-dp-models-failure
Fix the tests (#2317)
2020-04-14 10:21:17 -06:00
Jacob Beck
c6121675bb re-add quoting logic 2020-04-14 08:07:00 -06:00
Jacob Beck
626f835601 Jinja removed a keyword argument
Since they apparently will do this in patch releases, pinnned to precisely 2.11.2
But, it means we don't have to override NativeSandboxTemplate.render
anymore, so that's nice
2020-04-14 07:27:27 -06:00
Jacob Beck
aa38c8101b Fix the tests
This looks like a neat interplay between yaml's anchor/pointer notation (it's
a reference, not a copy!), and dbt inserting 'model' into the keyword args for
tests.

I've added a `copy.deepcopy` call to where dbt collects test arguments from the
underlying file, so the 'model' is inserted into a fresh keyword arguments
dictionary.
2020-04-13 20:18:30 -06:00
Drew Banin
d2760d5a49 Merge pull request #2298 from rodrigodelmonte/fix-diststyle-auto
Change redshift diststyle behavior
2020-04-09 21:15:34 -04:00
Rodrigo Monte
0c3f9e7e2e Merge branch 'dev/octavius-catto' into fix-diststyle-auto 2020-04-09 22:57:34 +02:00
Jacob Beck
fd4a33e450 Fix merge: it doubled up a few lines 2020-04-08 12:03:19 -06:00
Jacob Beck
f07face7c0 Merge branch 'dev/0.16.1' into dev/octavius-catto 2020-04-08 11:47:47 -06:00
Jacob Beck
935f985e25 Merge pull request #2297 from sumanau7/fix/return_proper_msg_for_empty_profile
Return proper error message when empty profiles.yml is used for project
2020-04-08 10:44:49 -06:00
Sumanau Sareen
35b43e9708 Update changelog.md with github issue and PR details 2020-04-08 20:57:42 +05:30
Sumanau Sareen
9991b066c7 Check if profile data with given profile name is empty 2020-04-08 20:57:07 +05:30
Sumanau Sareen
c76d6f34a2 Return proper error message when empty profiles.yml is used for dbt project 2020-04-08 20:57:07 +05:30
Jacob Beck
380f7c2e51 Merge pull request #2263 from sumanau7/fix/read_filenames_using_case_insenstive_extension
Read filenames using case insensitive extension
2020-04-08 08:02:03 -06:00
rodrigodelmonte
ab07b8ca06 Change redshift diststyle behavior (#2246)
* Don't provide a diststyle to redshift tables when configured as "auto"
2020-04-07 22:34:04 +02:00
Sumanau Sareen
6f302e26b3 Update changelog.md with github issue and PR details 2020-04-07 22:49:50 +05:30
Sumanau Sareen
f9343c4683 1. Add integration test for yaml schema extension warning. 2. Add integration test for checking case-insensitive extensions 2020-04-07 22:08:11 +05:30
Sumanau Sareen
466ed1f8e6 Add warning in case core config files are found with yaml extension 2020-04-07 21:49:06 +05:30
Sumanau Sareen
83cdd40191 FilesystemSearcher now searches for file names ignoring the case of the extension 2020-04-07 21:49:06 +05:30
Drew Banin
579a6d6064 Update changelog 2020-04-06 20:35:48 -04:00
Drew Banin
3a5eb4c2ad Fix for bq insert_overwrite incrementals, add additional tests 2020-04-06 17:58:00 -04:00
Drew Banin
40455108bb Merge branch 'dev/octavius-catto' of github.com:fishtown-analytics/dbt into fix/sql-header-in-incrementals 2020-04-06 17:12:28 -04:00
Jacob Beck
2c241025c9 Merge pull request #2279 from sumanau7/add_dbt_plugin_version
Add dbt plugin version
2020-04-06 14:41:00 -06:00
Sumanau Sareen
f5697fd82b Update CHANGELOG.md
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2020-04-07 00:55:54 +05:30
Sumanau Sareen
10d20197ed Update changelog.md with github issue and PR details 2020-04-07 00:38:47 +05:30
Sumanau Sareen
5d5913efeb Add DBT Plugin information to get_version_information 2020-04-05 00:33:16 +05:30
Jacob Beck
8cc0178b64 Add dbt:0.16.1rc1 dockerfiles and requirements 2020-04-03 12:24:18 -06:00
Jacob Beck
6a26a5f47d Merge pull request #2258 from fishtown-analytics/feature/model-select-paths
Add a `path:` selector to the node selector (#454)
2020-04-03 12:23:23 -06:00
Jacob Beck
01f5ce73a3 Bump version: 0.16.1a1 → 0.16.1rc1 2020-04-03 12:13:45 -06:00
Jacob Beck
1e94b1f5bb Merge pull request #2293 from fishtown-analytics/fix/duplicate-plugin-macros
Fix an error in exception handling for duplicate macros (#2288)
2020-04-03 12:12:41 -06:00
Jacob Beck
13e8b0dd0d Merge branch 'dev/0.16.1' into fix/duplicate-plugin-macros 2020-04-03 12:12:02 -06:00
Jacob Beck
f25b830aad Merge pull request #2294 from fishtown-analytics/fix/repair-create-adapter-script.py
Update the create adapter script to dbt 0.16.1
2020-04-03 11:05:46 -06:00
Jacob Beck
be0e12edb0 Fix an error in exception handling
Add a unit test
2020-04-03 10:53:14 -06:00
Jacob Beck
6aafd827f9 Update the create adapter script to dbt 0.16.1
Make the base dbt version in the script managed by bumpversion
2020-04-03 10:29:05 -06:00
Jacob Beck
41746eebbb Tests for source overriding
- Make sure dependency sources can be selected from the root project
- Make sure the root project can override dependency sources
2020-04-02 10:34:11 -06:00
Jacob Beck
fb3019f3f1 Merge pull request #2290 from fishtown-analytics/fix/deps-no-profile-required
Do not require a profile in "dbt deps" (#2231)
2020-04-02 10:16:46 -06:00
Jacob Beck
aa3cb37e05 PR feedback: name change 2020-04-02 09:42:22 -06:00
Jacob Beck
f0a4810b08 Use config.credentials.type instead of get_adapter(config).type()
- remove import cycle
  - works even if adapter class has been loaded but adapter object has not been registered
2020-04-02 09:17:28 -06:00
Jacob Beck
42e8c56b13 in "dbt deps", if a profile cannot be found/loaded, use invalid values instead
Add tests
2020-04-02 08:42:16 -06:00
Jacob Beck
ff0e955ccb Merge pull request #2280 from fishtown-analytics/fix/add-partitions-bq-config
Add "partitions" to the adapter specific config (#2256)
2020-04-01 12:12:49 -06:00
Jacob Beck
f8ee0561cb PR feedback
Also, added more tests
2020-04-01 10:14:24 -06:00
Jacob Beck
6c7d71028b Add a path: selector to the node selector
Instead of always being FQN by default, path-like selectors default to PATH
Paths referring to non-root packages are ignored
Added a test to the dependency tests that checks both of these
2020-04-01 10:14:23 -06:00
Jacob Beck
c63f60ef7c Add "partitions" to the adapter specific config
And tests, of course
2020-04-01 10:06:42 -06:00
Jacob Beck
79e8a86750 Merge pull request #2281 from fishtown-analytics/fix/quoted-information-schema
do not double-quote the database name (#2267)
2020-04-01 10:03:44 -06:00
Jacob Beck
aa0e58ef3b Merge pull request #2262 from kyleabeauchamp/kab_1995
Add db_groups and autocreate flags to Redshift config
2020-04-01 09:53:18 -06:00
Jacob Beck
4c93b51427 Merge pull request #2248 from fishtown-analytics/feature/document-contexts
Add documentation for contexts and manifests, plus extraction scripts (#2202)
2020-04-01 09:30:22 -06:00
Kyle Beauchamp
f4f10f6dd2 Fixes to changelog 2020-04-01 07:58:25 -07:00
Jacob Beck
e9b537a861 Add documentation for contexts and manifests, plus extraction scripts
Contexts are generally documented based on the dbt documentation page
Manifest is documented using the 'description' metadata field that hologram supports
Added two scripts:
 - one to extract context metadata
 - one to extract the manifest/run result/catalog json schemas
 - both scripts run during circleCI and dump the json files to disk
   - which are then uploaded as build artifacts on circle
2020-04-01 08:46:01 -06:00
Jacob Beck
b385d176a7 Merge pull request #2251 from fishtown-analytics/fix/render-profile-name-base-context
Render profile_name in the base context (#2230)
2020-04-01 08:40:23 -06:00
Jacob Beck
df2ae076b9 Merge branch 'dev/0.16.1' into fix/render-profile-name-base-context 2020-04-01 08:39:19 -06:00
Jacob Beck
9e6e381a40 Merge pull request #2282 from fishtown-analytics/feature/append-query-comment-backport
append query comment backport (#2138)
2020-04-01 08:37:55 -06:00
Jacob Beck
a8bdaba497 Merge branch 'dev/0.16.1' into feature/append-query-comment-backport 2020-04-01 08:37:26 -06:00
Jacob Beck
e3ac64d14a PR feedback 2020-04-01 08:37:07 -06:00
Jacob Beck
c146e5076e Merge pull request #2252 from fishtown-analytics/fix/docs-blocks-embedding
Make the contents of the raw blocks regex pattern non-greedy (#2241)
2020-04-01 08:35:24 -06:00
Jacob Beck
374d0b4e91 Merge branch 'dev/0.16.1' into fix/docs-blocks-embedding 2020-04-01 08:34:56 -06:00
Jacob Beck
c415b6b41f Merge pull request #2237 from fishtown-analytics/feature/slim-manifest
slim the manifest
2020-04-01 08:33:48 -06:00
Jacob Beck
3807b0b6cd CHANGELOG update 2020-03-31 15:14:16 -06:00
ilkin Balkanay
8b05113cc8 pass integration test
- to disable query comments use : `'query-comment': ''`
2020-03-31 15:05:10 -06:00
ilkin Balkanay
304b11ee09 NoValue type definition updated.
- hologram version updated to 0.0.6 to be able to use QueryComment, NoValue and str within the same Union type.
2020-03-31 15:05:10 -06:00
ilkin Balkanay
11b7bf77c0 updated changelog
- added ilkinulas to contributers list
2020-03-31 15:05:10 -06:00
ilkin Balkanay
ee2d990ece updated change log.
- replaced pr link with issue link
2020-03-31 15:05:10 -06:00
ilkin Balkanay
031d844d30 refactored _get_comment_macro method
- isinstance check is needed to pass mypy checks
2020-03-31 15:05:10 -06:00
ilkin Balkanay
33d3c9f0bd pass unit test 2020-03-31 15:05:10 -06:00
ilkin Balkanay
a6aa1c36b3 refactored types (pass mypy checks) 2020-03-31 15:05:10 -06:00
ilkin Balkanay
90542fb499 added QueryComment dataclass 2020-03-31 15:05:10 -06:00
ilkin Balkanay
70031c63b6 updated CHANGELOG 2020-03-31 15:05:09 -06:00
ilkin Balkanay
b781cdad6d fix integration test. 2020-03-31 15:05:09 -06:00
ilkin Balkanay
504c163570 fixed unittest: default value for query_comment is None 2020-03-31 15:05:09 -06:00
ilkin Balkanay
5f294bc587 handle unset query comment 2020-03-31 15:05:09 -06:00
ilkin Balkanay
b202da7d84 return default comment if config.query_comment is None 2020-03-31 15:05:09 -06:00
ilkin Balkanay
43525d622a fixed mypy error 2020-03-31 15:05:09 -06:00
ilkin Balkanay
0979c67b63 added unit test for query_headers.py 2020-03-31 15:05:09 -06:00
ilkin Balkanay
90abe7bbe7 pass unit tests 2020-03-31 15:05:09 -06:00
ilkin Balkanay
9c9d6248ae added query comment append feature (wip) 2020-03-31 15:05:09 -06:00
Jacob Beck
8c7fef47a4 do not double-quote the database name 2020-03-31 14:57:31 -06:00
Jacob Beck
78f1afa959 Merge pull request #2259 from jeremyyeo/fix/warn-exception-sql-compilation
Make warn return empty string (#2222)
2020-03-30 08:10:21 -06:00
Jeremy Yeo
f5d358d3cf add to changelog 2020-03-28 10:58:56 +13:00
Jeremy Yeo
55cd72cf62 add test 2020-03-28 10:23:30 +13:00
Jeremy Yeo
11d62f6c13 make warn return empty string 2020-03-28 09:57:37 +13:00
Jacob Beck
4182c03258 Merge pull request #2255 from fishtown-analytics/fix/all-null-seed-column-types
Fix a ValueError on redshift when an all-null column had a text type …
2020-03-27 11:44:44 -06:00
Jacob Beck
2da96e3169 Fix a ValueError on redshift when an all-null column had a text type specified
add unit tests for conversions
2020-03-27 09:40:27 -06:00
Jacob Beck
8e3c95b48c Make the contents of the raw blocks regex pattern non-greedy
This fixes an issue where a file with multiple raw blocks got treated as one _big_ one
 - it extended from the first {% raw %} to the last {% endraw %}
2020-03-26 13:36:45 -06:00
Jacob Beck
3c3f57ece1 fix unit tests 2020-03-26 13:03:43 -06:00
Jacob Beck
69ec5fd82e Render profile_name in the base context 2020-03-26 12:44:06 -06:00
Jacob Beck
386895f800 Merge pull request #2249 from fishtown-analytics/fix/rev-google-lower-bound
rev the bigquery minimum versions to a big number (#2233)
2020-03-26 11:42:38 -06:00
Jacob Beck
6065237de4 fix unit test bug that only appears on final release versions 2020-03-26 10:58:57 -06:00
Jacob Beck
a7617745e2 rev the bigquery minimum versions to a big number 2020-03-26 10:57:05 -06:00
Jacob Beck
923f28225a Bump version: 0.16.0 → 0.16.1a1 2020-03-26 10:57:05 -06:00
Jacob Beck
7dfef27886 Slim the manifest
remove raw_sql from ParsedMacros, remove file_contents from docs
get rid of seed_file_path
Do not write 'manifest.files' to disk
2020-03-26 10:29:56 -06:00
Jacob Beck
5c698908a4 Merge pull request #2244 from fishtown-analytics/feature/source-aliases
Add support for source aliases (#2133)
2020-03-26 08:12:17 -06:00
Jacob Beck
d88089fb24 Merge branch 'dev/octavius-catto' into feature/source-aliases 2020-03-25 14:52:11 -06:00
Jacob Beck
0188d03d1b Merge pull request #2238 from fishtown-analytics/fix/macro-kwarg-error-add-file
fix commit messages by using the stack properly (#2073)
2020-03-25 14:41:10 -06:00
Jacob Beck
7b6ea338ed Merge pull request #2220 from fishtown-analytics/feature/schema-tests-pass-rendered
In schema tests, pass rendered items (#2149)
2020-03-25 14:40:57 -06:00
Jacob Beck
40e88ded25 Merge pull request #2245 from fishtown-analytics/fix/circleci-pipelines
set the circleci config version to 2.1 (#2207)
2020-03-25 14:03:33 -06:00
Jacob Beck
38bcc2b736 override NativeSandboxTemplate.render(), remove the quotes hack 2020-03-25 14:03:17 -06:00
Jacob Beck
73e6941817 circleci 2.1 2020-03-25 13:32:56 -06:00
Jacob Beck
1268c119b5 require jinja 2.11.x 2020-03-25 10:46:45 -06:00
Jacob Beck
434edce8a7 remove extra methods 2020-03-25 10:46:44 -06:00
Jacob Beck
c0ceaee122 Add support for source aliases 2020-03-25 10:43:11 -06:00
Jacob Beck
e9a053c18d Update core/dbt/node_runners.py
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2020-03-25 07:30:02 -06:00
Jacob Beck
427f3bf163 fix commit messages by using the stack properly 2020-03-24 15:12:52 -06:00
Jacob Beck
96cfc49cb9 rev hologram to 0.0.7 2020-03-24 09:13:00 -06:00
Jacob Beck
dc65118f17 Add metadata to tests, add a native env
Render test arguments in the native env and pass them along to the context
added/fixed tests
Update changelog
2020-03-24 09:11:24 -06:00
Jacob Beck
31025a0a7f remove wrapped_sql, move data test wrapping into the node runner 2020-03-24 09:09:49 -06:00
Jacob Beck
d20c301dcd split test types into data/schema tests 2020-03-24 09:09:49 -06:00
Jacob Beck
0a382c411f Merge branch 'dev/barbara-gittings' into dev/octavius-catto 2020-03-24 09:09:13 -06:00
Jacob Beck
367bf6849b Merge pull request #2228 from Raalsky/bug/registry-timeout
(#2195) Added timeout to registry download call
2020-03-24 08:58:11 -06:00
Jacob Beck
848716607c Merge pull request #2224 from Raalsky/fail-fast
Add --fail-fast argument for dbt run and dbt test
2020-03-24 08:36:45 -06:00
Jacob Beck
0a0e71a9ff Merge branch 'dev/octavius-catto' into fail-fast 2020-03-24 08:13:24 -06:00
Jacob Beck
ca232dbdf4 Merge branch 'feature/rpc-get-manifest' into dev/octavius-catto 2020-03-24 07:22:58 -06:00
Jacob Beck
efcf78b348 Merge pull request #2199 from ilkinulas/query_comment_append
Append query comment
2020-03-24 06:51:14 -06:00
Jacob Beck
bbc0d30fbf When a user calls the 'get-manifest' API call, compile all the full project and return the compiled manifest from memory.
'get-manifest' returns an async ID, just like the existing compile/run/etc
2020-03-23 12:59:36 -06:00
Raalsky
1c60882525 (#2195) DBT_HTTP_TIMEOUT moved inside system download call 2020-03-23 17:56:37 +01:00
Raalsky
d6cb415f76 (#2195) DBT_HTTP_TIMEOUT environment variable added 2020-03-23 17:48:54 +01:00
Jacob Beck
f58bf878d5 Add dbt:0.16.0 dockerfiles and requirements 2020-03-23 08:20:20 -06:00
Jacob Beck
2fbc716615 Bump version: 0.16.0rc4 → 0.16.0 2020-03-23 07:59:37 -06:00
Raalsky
cce99b1ff2 (#2195) Added entry to CHANGELOG 2020-03-23 14:14:21 +01:00
Raalsky
f98b1669fa (#2195) Added timeout to registry download call 2020-03-23 13:35:31 +01:00
Raalsky
efebabe44f 058_fail_fast test method renamed 2020-03-22 18:39:43 +01:00
Rafal Jankowski
42a0461f57 Added integration test 058_fail_fast 2020-03-22 18:27:55 +01:00
Rafal Jankowski
a23ad3ad4a Linker some_task_done method lock refactor to be more consistent 2020-03-22 18:27:47 +01:00
Rafal Jankowski
1478be716d FailFastException refactor to RuntimeException 2020-03-22 18:27:32 +01:00
ilkin Balkanay
0eed51dfdd pass integration test
- to disable query comments use : `'query-comment': ''`
2020-03-21 21:36:45 +03:00
İlkin Balkanay
574d65ab4b Merge branch 'dev/octavius-catto' into query_comment_append 2020-03-21 17:31:30 +03:00
ilkin Balkanay
ac336b20ca NoValue type definition updated.
- hologram version updated to 0.0.6 to be able to use QueryComment, NoValue and str within the same Union type.
2020-03-21 17:21:24 +03:00
Rafal Jankowski
c9613016e2 Added --fail-fast argument for dbt run and dbt test 2020-03-20 19:26:17 +00:00
Jacob Beck
f9696a85d3 Merge pull request #2226 from fishtown-analytics/fix/add-docker-stuff-unlink-dbt
Build script updates - add docker stuff, unlink dbt (#2211)
2020-03-20 13:16:26 -06:00
Jacob Beck
deb97546c0 add 0.16.0rc4 dockerfiles and requirements 2020-03-20 12:16:05 -06:00
Jacob Beck
cea964910d Also unlink "dbt" if it is installed when test-installing the new homebrew package 2020-03-20 12:11:54 -06:00
Jacob Beck
f8b99f2e34 Bump version: 0.16.0rc3 → 0.16.0rc4 2020-03-20 11:31:17 -06:00
Jacob Beck
3a7e328c6e Update changelog 2020-03-20 11:31:01 -06:00
Jacob Beck
732792fc80 Merge pull request #2225 from fishtown-analytics/fix/build-script
Do not run brew audit on the default formula, it complains
2020-03-20 11:30:18 -06:00
Jacob Beck
dd842ac3b8 Do not run brew audit on the default formula, it complains about versions 2020-03-20 11:15:11 -06:00
Jacob Beck
bdcf10e209 Merge branch 'dev/barbara-gittings' into dev/octavius-catto 2020-03-20 11:05:55 -06:00
Jacob Beck
a43edd9dd8 Merge pull request #2223 from dmateusp/add_dockerize
Add Dockerize to test image
2020-03-20 09:34:00 -06:00
ilkin Balkanay
65d9c187b8 updated changelog
- added ilkinulas to contributers list
2020-03-20 00:04:14 +03:00
ilkin Balkanay
75e1c1dac8 updated change log.
- replaced pr link with issue link
2020-03-19 23:47:06 +03:00
Daniel Mateus Pires
28ccdcc52d Add Dockerize 2020-03-19 20:36:32 +00:00
ilkin Balkanay
7c34ff166a refactored _get_comment_macro method
- isinstance check is needed to pass mypy checks
2020-03-19 17:48:26 +03:00
ilkin Balkanay
36bf479115 pass unit test 2020-03-19 14:10:19 +03:00
ilkin Balkanay
3b90bfcdc3 refactored types (pass mypy checks) 2020-03-19 11:30:32 +03:00
ilkin Balkanay
ab19135ca6 added QueryComment dataclass 2020-03-19 10:25:29 +03:00
Jacob Beck
41f7b8cb9d Merge pull request #2215 from fishtown-analytics/feature/add-spark-deps
add spark deps to the test container
2020-03-18 18:05:00 -06:00
Jacob Beck
d8b9541b86 Merge pull request #2214 from fishtown-analytics/fix/bigquery-version
increase the google-cloud-bigquery lower bound to ensure we support "range_partitioning"
2020-03-18 15:53:57 -06:00
Jacob Beck
a4620adbce add spark deps 2020-03-18 15:52:23 -06:00
Jacob Beck
02ac87d160 increase the lower bound to ensure we support "range_partitioning" 2020-03-18 15:20:50 -06:00
Jacob Beck
f4c62726ee Merge pull request #2198 from fishtown-analytics/rework/incremental-overwrite
Rework insert_overwrite incremental strategy
2020-03-18 13:11:06 -06:00
Jacob Beck
0844be5a28 Merge pull request #2208 from fishtown-analytics/fix/bigquery-numeric-catalog
Fix issue with table databases/schemas/names becoming numbers (#2206)
2020-03-17 14:41:35 -06:00
Jacob Beck
3a77626cd7 Fix issue with table databases/schemas/names not being interpreted as strings if they look like numbers 2020-03-17 13:07:22 -06:00
Jeremy Cohen
4fee33c020 Unquote partition values in predicate 2020-03-16 13:01:27 -04:00
ilkin Balkanay
e3c53ee874 updated CHANGELOG 2020-03-14 13:55:02 +03:00
Drew Banin
367eff77fc (#2136) Fix missing sql_header for incremental models 2020-03-12 15:56:43 -04:00
Jeremy Cohen
1bed6a1b96 Rename dbt_partitions_for_upsert to dbt_partitions_for_replacement 2020-03-12 10:51:09 -04:00
ilkin Balkanay
1cf9220380 fix integration test. 2020-03-12 17:18:34 +03:00
ilkin Balkanay
fd8629a6cf fixed unittest: default value for query_comment is None 2020-03-12 16:59:30 +03:00
Jeremy Cohen
be1ca971be Rm failing irrelevant tint tests 2020-03-12 09:53:15 -04:00
ilkin Balkanay
5fa59d86e7 handle unset query comment 2020-03-12 16:45:36 +03:00
ilkin Balkanay
be80009aca return default comment if config.query_comment is None 2020-03-12 16:34:30 +03:00
ilkin Balkanay
f18f6af5e0 fixed mypy error 2020-03-12 16:25:58 +03:00
ilkin Balkanay
65f14723ca added unit test for query_headers.py 2020-03-12 15:46:24 +03:00
ilkin Balkanay
6910847c71 pass unit tests 2020-03-12 13:35:32 +03:00
ilkin Balkanay
62b19b53f9 added query comment append feature (wip) 2020-03-12 10:28:27 +03:00
Jeremy Cohen
8fb6592c6a Rework insert_overwrite incremental strategy 2020-03-11 22:21:11 -04:00
Jacob Beck
cda35e2a9a Bump version: 0.16.0rc2 → 0.16.0rc3 2020-03-11 09:20:06 -06:00
Jacob Beck
4472719362 fix twine check 2020-03-11 09:20:02 -06:00
Jacob Beck
6c12b7a951 Update changelog for rc3 2020-03-11 09:07:35 -06:00
Jacob Beck
ad3b63ed5d Merge pull request #2187 from fishtown-analytics/fix/no-creating-quoted-schemas
Do not create already-existing schemas (#2186)
2020-03-10 22:00:38 -06:00
Jacob Beck
e7545ad183 handle null database/schema issues 2020-03-10 21:29:11 -06:00
Jacob Beck
e390595551 when creating missing schemas, list the quoted database form, but use the unquoted for comparisons
add a test
2020-03-06 15:08:33 -07:00
Jacob Beck
b77b3a4566 Merge pull request #2184 from fishtown-analytics/fix/ugly-errors-undefined-values
For undefined variables, explicitly raise a useful error on pickling
2020-03-06 13:26:41 -07:00
Jacob Beck
16692e2e5f tests 2020-03-06 08:41:26 -07:00
Jacob Beck
757bdf69dc For undefined variables, explicitly raise a useful error on pickling 2020-03-06 08:31:39 -07:00
Jacob Beck
a259f154da Bump version: 0.16.0b3 → 0.17.0a1 2020-03-06 08:25:03 -07:00
Jacob Beck
e441c6dfa7 Merge pull request #2119 from fishtown-analytics/feature/pkg-build-script
Feature: package build script + Docker build script
2020-03-06 08:04:16 -07:00
Jacob Beck
c4c7af88c0 PR feedback + discovered small issues
run twine check before uploading
don't install netcat curl ssh on the docker image
skip the dbt resource block initial line when parsing the poet output
unlink existing versions so 'dbt install Formula/...' actually works
Wait a bit for pypi to settle down after uploading to pypi, add retries
2020-03-04 18:52:39 -07:00
Jacob Beck
d78b249827 Fix an issue in 0.16.0b3 where files were erroneously included
Previous release files that didn't exist in the new version were ending up in
the wheel (but not source dist). Attempt to fix that by removing build/
directories when we remove dist/ directories
2020-03-04 18:52:39 -07:00
Jacob Beck
af8cdedc07 Build docker images + requirements.txt files
some enhancements
2020-03-04 18:52:39 -07:00
Jacob Beck
fc7a62b650 add an initial pass at a dbt package build script
Bumps the version (including commit)
builds pypi packages
uploads them to test pypi + prompts for enter/ctrl+c
uploads them to regular pypi
builds homebrew package
overwrites the default homebrew package, if you want
makes appropriate commits
Has a bunch of flags

Requires python 3.8 (homebrew's python version)
Requires .pypirc w/creds
Does not push, so no gh creds required (should it?)

appease homebrew audit:

"version" is supplied by the url
dependencies must be alphabetized
whitespace/newline stuff
2020-03-04 18:52:39 -07:00
Jacob Beck
7919c90b81 Bump version: 0.16.0rc1 → 0.16.0rc2 2020-03-04 16:39:43 -07:00
Jacob Beck
1854d20566 Merge pull request #2181 from fishtown-analytics/fix/pin-another-dang-dependency
Pin ciffi to snowflake's versions
2020-03-04 16:29:57 -07:00
Jacob Beck
c5c5c3d863 cffi released a new version, breaking snowflake as a dependency due to version conflicts 2020-03-04 15:58:06 -07:00
Jacob Beck
6c47ae1d88 Bump version: 0.16.0b3 → 0.16.0rc1 2020-03-04 15:27:00 -07:00
Jacob Beck
70ad200dca Set release version/date in changelog 2020-03-04 15:26:33 -07:00
Drew Banin
341956768a Merge pull request #2153 from fishtown-analytics/feature/bq-insert-overwrite
Feature/bq incremental strategy insert_overwrite
2020-03-04 16:56:42 -05:00
Drew Banin
0656477131 Merge branch 'dev/barbara-gittings' of github.com:fishtown-analytics/dbt into feature/bq-insert-overwrite 2020-03-04 13:45:11 -05:00
Jeremy Cohen
e878d0e76e Add insert_overwrite as incremental strategy on BQ 2020-03-04 13:43:56 -05:00
Drew Banin
dc6a38c360 Merge pull request #2148 from fishtown-analytics/0.16.0-changelog-updates
Update CHANGELOG.md
2020-03-04 12:57:40 -05:00
Drew Banin
f883e11c7a Merge branch 'dev/barbara-gittings' into 0.16.0-changelog-updates 2020-03-04 12:57:02 -05:00
Drew Banin
7e4402b128 Merge pull request #2179 from fishtown-analytics/0.16.0-docs-site-2
Upgrade docs site to support 0.16.0 functionality
2020-03-04 12:54:41 -05:00
Drew Banin
e045e14522 Upgrade docs site to support 0.16.0 functionality 2020-03-04 11:59:31 -05:00
Jacob Beck
f209e17215 Merge pull request #2171 from fishtown-analytics/feature/use-show-schemas-snowflake
use show schemas for snowflake list_schemas (#2166)
2020-03-02 21:27:49 -07:00
Drew Banin
07461d24e0 Merge branch 'dev/barbara-gittings' of github.com:fishtown-analytics/dbt into 0.16.0-changelog-updates 2020-03-02 15:34:09 -05:00
Jacob Beck
47cef1d907 PR feedback: Add exception handler around macro execution, add a message about what is going on when list_schemas fails 2020-03-01 20:48:15 -07:00
Jacob Beck
c59adc3369 strip out the database qutoes on bigquery for the API 2020-02-28 12:04:31 -07:00
Jacob Beck
cc3ba20ec9 refactor, fix flake8/mypy 2020-02-28 11:26:34 -07:00
Jacob Beck
562f3d0bb7 use show schemas for snowflake list_schemas
Also fix up the changelog to better reflect releases
2020-02-28 10:30:57 -07:00
Jacob Beck
e8d321cbab Merge pull request #2164 from mhmcdonald/feature/snowflake_key
removing the requirement to have a passphrase on a Snowflake key
2020-02-27 18:36:51 -07:00
Mark McDonald
1edffac2bc Merge branch 'dev/barbara-gittings' into feature/snowflake_key 2020-02-27 16:05:29 -06:00
Jacob Beck
735ffb3c92 Merge pull request #2154 from dholleran-lendico/dev/barbara-gittings
Feature: adding optional sslmode parameter to postgres connection
2020-02-27 13:12:25 -07:00
Mark McDonald
66b60d860e adding myself to contributors 2020-02-27 11:40:09 -06:00
Mark McDonald
f99d31f7c5 Update CHANGELOG.md
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2020-02-27 11:34:06 -06:00
dholleran-lendico
170b1d80b5 Update plugins/postgres/dbt/adapters/postgres/connections.py
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2020-02-27 18:24:03 +01:00
dholleran-lendico
10846653f0 Merge branch 'dev/barbara-gittings' into dev/barbara-gittings 2020-02-27 18:21:31 +01:00
Jacob Beck
5d4d4f32d2 Merge pull request #2157 from fishtown-analytics/feature/threadpool-startup
Use threadpools for filling the cache and listing schemas (#2127)
2020-02-27 09:52:00 -07:00
Mark McDonald
1f49150d30 updating changelog 2020-02-27 09:37:36 -06:00
Mark McDonald
3f583bd927 removing the requirement to have a passphrase on a Snowflake key 2020-02-27 09:31:01 -06:00
Jacob Beck
d11987a2ad Bump version: 0.16.0b2 → 0.16.0b3 2020-02-26 16:43:26 -07:00
Jacob Beck
79f7985784 Update CHANGELOG.md with release date 2020-02-26 16:43:18 -07:00
Jacob Beck
104422eed9 Bump version: 0.16.0b1 → 0.16.0b2 2020-02-26 16:39:00 -07:00
Jacob Beck
d77e4205c1 un-ignore 2020-02-26 16:38:01 -07:00
Jacob Beck
847bc0995b Merge pull request #2160 from fishtown-analytics/feature/expanded-schema-search-paths
Allow search in macro and analysis paths (#2155)
2020-02-26 16:31:50 -07:00
Jacob Beck
529b053620 Update CHANGELOG.md
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2020-02-26 15:01:01 -07:00
Jacob Beck
4d72e4e257 integration tests fixups 2020-02-26 15:01:01 -07:00
Jacob Beck
8e18af5912 fix unit tests 2020-02-26 15:01:01 -07:00
Jacob Beck
bee5bb8660 Allow search in macro and analysis paths
add tests
2020-02-26 15:01:01 -07:00
Jacob Beck
e486872d81 Merge pull request #2163 from fishtown-analytics/fix/broken-merge-0-15-3
actually update the connection
2020-02-26 14:57:29 -07:00
Jacob Beck
6e8d4a6fa6 actually update the connection 2020-02-26 10:37:12 -07:00
Jacob Beck
9081303a97 Merge branch 'dev/0.15.3' into dev/barbara-gittings 2020-02-26 10:08:44 -07:00
Jeremy Cohen
40c07afd85 Merge pull request #2156 from fishtown-analytics/fix/bq-range-part-match
Bug: fixup bq _partitions_match for range partitions
2020-02-25 19:01:12 -05:00
Jacob Beck
da46a679f3 flake8 2020-02-25 15:52:12 -07:00
Jeremy Cohen
283405dc17 Add test for bq part int<>date switch 2020-02-25 14:41:19 -05:00
Jacob Beck
ed38cbbc0b Use threadpools for filling the cache and listing schemas 2020-02-25 11:43:07 -07:00
Daniel Holleran
ad91636c74 updating CHANGELOG 2020-02-25 12:10:15 +01:00
Daniel Holleran
5543dab97c uncommenting sslmode in postgres _coonection_keys 2020-02-25 11:52:37 +01:00
Daniel Holleran
d4ae9a89df adding 'sslmode' parameter to postgres connection 2020-02-25 11:49:06 +01:00
Jeremy Cohen
032faf2dce Fixup range_partitioning comparison logic 2020-02-25 01:33:00 -05:00
Jacob Beck
bcea7cc8ad Merge pull request #2140 from fishtown-analytics/feature/cost-effective-bq-incremental-followup
Feature/cost effective bq incremental followup
2020-02-24 10:32:05 -07:00
Jacob Beck
0ad94f80cd Merge pull request #2151 from fishtown-analytics/fix/more-requirements-changes
Dependency revs/bounding (#2147)
2020-02-24 10:31:27 -07:00
Jacob Beck
7e9533afc7 Merge pull request #2143 from fishtown-analytics/feature/generate-database-name
add generate_database_name macro (#1695)
2020-02-24 10:31:08 -07:00
Jacob Beck
6ca6233387 PR feedback: Changelog update 2020-02-24 08:39:47 -07:00
Jacob Beck
503050904c add generate_database_name macro
Remove old deprecation tests
Add new tests
Fix all the integration tests that do manifest things
2020-02-24 08:39:24 -07:00
Jacob Beck
ba2f9d5781 Dependency revs/bounding
remove jinja._compat calls
set an upper bound on jinja anyway
set bound on idna/requests to match snowlfake
update snowflake-connector-python
2020-02-24 08:38:32 -07:00
Jacob Beck
e571cbaeec Merge pull request #2150 from fishtown-analytics/fix/source-test-arg-rendering
Fix source test arg rendering (#2114)
2020-02-24 08:33:36 -07:00
Jacob Beck
202f8a1678 Update plugins/bigquery/dbt/adapters/bigquery/impl.py
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2020-02-24 08:07:31 -07:00
Jacob Beck
4068c6694e fix dict/PartitionConfig confusion
Also, make tests run
2020-02-21 13:36:47 -07:00
Jacob Beck
7d43037d4e fix tests and types, enhance error message for bad partition configs 2020-02-21 13:00:38 -07:00
Jacob Beck
d6bdb466ae Actually git add tests
Update changelog further to reflect that this is breaking
2020-02-21 11:48:18 -07:00
Jacob Beck
1114885429 Do not render source test arguments during schema.yml parsing
Remove extra jinja rendering pass on wrapped_sql
2020-02-21 11:08:23 -07:00
Drew Banin
e29078e5d1 wip 2020-02-21 12:49:53 -05:00
Drew Banin
2bd56de934 push some gnarly logic out of jinja and into python 2020-02-19 21:33:09 -05:00
Drew Banin
df82cd5aec fix tests 2020-02-19 21:11:42 -05:00
Drew Banin
67571f4ede quiet bq error logs; refactor to use dataclasses 2020-02-19 18:07:29 -05:00
Drew Banin
fa22c5b071 Merge branch 'dev/barbara-gittings' of github.com:fishtown-analytics/dbt into feature/cost-effective-bq-incremental-followup 2020-02-19 16:34:20 -05:00
Drew Banin
5b7c81eb2d Update CHANGELOG.md 2020-02-19 14:53:53 -05:00
Jacob Beck
8b20136571 Bump version: 0.15.3a1 → 0.15.3rc1 2020-02-19 12:38:01 -07:00
Jacob Beck
2a426edfe5 Set release date in changelog 2020-02-19 12:37:56 -07:00
Jacob Beck
5dd9c997e2 Merge pull request #2141 from fishtown-analytics/feature/snowflake-refresh-token
Support refresh tokens (#2126)
2020-02-19 12:11:02 -07:00
Jacob Beck
be5fa32c0c PR feedback: make oauth test skippable 2020-02-19 10:54:21 -07:00
Jacob Beck
0c3fb9528a Merge pull request #2146 from fishtown-analytics/feature/bigquery-user-agent
add a user agent with version to the bigquery client info (#2121)
2020-02-18 12:42:02 -07:00
Jacob Beck
d5c24b0c63 Support refresh tokens
Add a little webserver that spits out a refresh token
Add tests + notes on test support
2020-02-18 10:44:45 -07:00
Jacob Beck
9b8e06c51d Bump version: 0.15.2 → 0.15.3a1 2020-02-18 10:42:48 -07:00
Jacob Beck
ea4948ff8a Merge pull request #2120 from nchammas/dbt-debug-empty-project
Correctly handle an empty dbt_project.yml in `dbt debug`
2020-02-18 10:39:00 -07:00
Nicholas Chammas
5dc939355a handle empty dbt_project 2020-02-18 11:32:06 -05:00
Jacob Beck
2672859ee0 add a user agent with version to the bigquery client info
Updated unit tests
2020-02-18 08:46:33 -07:00
Jacob Beck
542a65323f Merge pull request #2124 from fishtown-analytics/fix/requirements-update
rev boto3/botocore/snowflake-connector-python/google libraries (#2102)
2020-02-18 08:00:45 -07:00
Jacob Beck
85e6d7f649 Merge pull request #2125 from fishtown-analytics/feature/macro-sql
add macro_sql field (#2118)
2020-02-17 07:52:59 -07:00
Drew Banin
e73e276e2b rm semicolons 2020-02-16 18:16:09 -05:00
Drew Banin
1879fb5109 use not-real temp tables on bigquery, fix snapshots 2020-02-16 17:55:30 -05:00
Drew Banin
15cac5afc4 add tests 2020-02-15 18:25:54 -05:00
Drew Banin
1ae17d5745 report bytes billed for scripts, add _dbt_max_partition field 2020-02-15 16:31:56 -05:00
Jeremy Cohen
7106a7c1d1 Implement partition-aware BigQuery merge statements 2020-02-15 15:45:28 -05:00
Drew Banin
0115e469c1 Merge pull request #2130 from fishtown-analytics/fix/add-changelog-0160b1
changelog updates, PR template
2020-02-15 13:48:42 -05:00
Drew Banin
7729295bce Merge pull request #2131 from aaronsteers/aaronsteers/clickable-url
Clickable doc serve URL
2020-02-15 13:46:36 -05:00
Drew Banin
3265825446 Merge pull request #2137 from shooka/postgres-role-scope
Support Postgres role scope
2020-02-15 13:45:17 -05:00
Nicolai Østby
90fdc9b2e2 Test 2020-02-14 17:51:34 +01:00
Nicolai Østby
9853951aa5 Support act-as in postgres 2020-02-14 17:41:55 +01:00
Jacob Beck
b839c79144 Merge pull request #2135 from fishtown-analytics/fix/rev-dockerfile-pin-stuff
fix a bunch of issues caused by virtualenv 20, tox 3.14, six, and ubuntu (#2128)
2020-02-13 12:54:39 -07:00
Jacob Beck
fad8813911 PR feedbck, fix python_bin 2020-02-13 11:52:34 -07:00
Aaron Steers
ed57876068 adding space 2020-02-13 10:34:09 -08:00
Jacob Beck
5143dbb167 broke building, oops 2020-02-13 10:56:40 -07:00
Jacob Beck
75126aa167 Fix a bunch of issues caused by virtualenv 20, tox 3.14, six, and ubuntu
Pin circle to the newest version of the test container
Have the dockerfile also install pinned versions of things
update dev_requirements.txt to match the dockerfile changes
Add python 3.9 while I am in here
2020-02-13 09:59:14 -07:00
Aaron Steers
00a1840a46 remove '.' from after url 2020-02-12 14:12:28 -08:00
Jacob Beck
127f8768aa changelog updates, PR template 2020-02-12 12:37:47 -07:00
Jacob Beck
6e330fa993 add macro_sql field
Add the adapter type to the manifest metadata field.
2020-02-12 11:15:56 -07:00
Jacob Beck
c6ab6459fb rev boto3/botocore/snowflake-connector-python/google libraries 2020-02-12 08:13:29 -07:00
Jacob Beck
4e58589afd Merge pull request #2080 from bubbomb/update-docs-duplicates
fix docstring duplication error issue #2054
2020-02-11 11:09:05 -07:00
Jacob Beck
89c65affea Merge pull request #2107 from fishtown-analytics/feature/node-docs-show
add a "docs" field to models (#1671)
2020-02-11 11:04:49 -07:00
Jacob Beck
099b487560 Merge pull request #2104 from fishtown-analytics/fix/rpc-cli-vars-arguments
Attach cli vars to configs when they come in via the RPC server (#2092)
2020-02-11 07:14:42 -07:00
Drew Banin
25235a4d5a Merge pull request #2078 from sonac/packages-err-msg
making error msg for malformed packages.yml better
2020-02-10 23:39:38 -05:00
Jacob Beck
1d329e0364 Bump version: 0.16.0a2 → 0.16.0b1 2020-02-10 15:17:01 -07:00
Jacob Beck
d07f23395c add a "docs" field to models, parsed from schema.yml. it contains a boolean "show" which defaults to True.
Clean up the contracts tests a bit
2020-02-10 14:37:14 -07:00
Jacob Beck
5e3eba6662 Merge pull request #2106 from fishtown-analytics/feature/column-quoting
add column-level quoting for tests (#2047)
2020-02-10 13:58:14 -07:00
Jacob Beck
61adfe42ff PR feedback 2020-02-10 13:57:45 -07:00
Jacob Beck
80e7e71526 Merge pull request #2105 from fishtown-analytics/fix/docs-generate-bad-docs
Fix docstring for dbt docs/source to not include misleading flags (#2038)
2020-02-10 13:55:04 -07:00
Jacob Beck
1e6790ab05 Merge pull request #2103 from fishtown-analytics/feature/macro-depends-on
Feature: node depends_on.macros (#2082)
2020-02-10 13:39:16 -07:00
Jacob Beck
18358db467 Merge pull request #2097 from fishtown-analytics/feature/ci-builds
CI stuff
2020-02-10 13:23:07 -07:00
Jacob Beck
2eea89a0a6 Merge pull request #2099 from heisencoder/fix/debug-log-format
Update DEBUG_LOG_FORMAT to correctly zero-pad the microsecond portion
2020-02-10 11:00:05 -07:00
Jacob Beck
dcce90bcc4 Merge pull request #2096 from fishtown-analytics/feature/document-macro-args
add macro argument definitions (#2081, #2083)
2020-02-10 10:51:04 -07:00
Jacob Beck
b5fc1ef4a3 CI stuff
circle: Run unit tests + postgres on circle for outside contributors automatically
circle+azure: skip non-postgres integration tests on for outside contributors
circle+azure: build wheels after tests pass
Update the docker image so venvs can pip install -U pip
2020-02-10 09:35:54 -07:00
Brayden Connole
4ae129d565 Fix broken test 2020-02-07 15:40:43 -07:00
Jacob Beck
3ec9d358f7 add column-level quoting for tests
add support in columns for a quote field
add support in source quoting for a column field
add support in models for a quote_columns field
add tests
2020-02-07 14:10:16 -07:00
Jacob Beck
1fc4718d36 Fix docstring for dbt docs/source to not include misleading flags 2020-02-07 12:58:32 -07:00
Jacob Beck
bf3f5e1b92 Attach cli vars to configs when they come in via the RPC server 2020-02-07 12:39:21 -07:00
Jacob Beck
20496651c5 depends_on macros
Clean up macro generation a bit
2020-02-07 12:11:08 -07:00
Jacob Beck
9df123a180 Merge pull request #2085 from fishtown-analytics/feature/contexts-redux
Feature: contexts cleanup
2020-02-07 12:09:23 -07:00
Brayden Connole
e7b597787d Fix doc string duplicate issue and add test 2020-02-06 15:42:07 -07:00
Jacob Beck
048b96b540 Render all description fields, regardless of docrefs
remove the whole idea of docrefs
2020-02-06 12:03:31 -07:00
Jacob Beck
be29156f6f add macro argument definitions 2020-02-06 12:03:31 -07:00
Jacob Beck
140cfd70ce Merge pull request #2093 from fishtown-analytics/fix/generate-no-compile
fix --no-compile flag for docs generate
2020-02-06 10:51:57 -07:00
Jacob Beck
2e5fdbf5ce PR feedback
Fix comments
Implement a TODO around duplicate macro names
 - added unit tests for it
Implement a TODO around ref resolution
2020-02-06 08:59:09 -07:00
Jacob Beck
ee710e3922 add some tests
Make slight tweaks to project initialization to support tests
2020-02-06 08:40:58 -07:00
Jacob Beck
b8febddad5 dbt Contexts
Rewrite/reorganize contexts
 - now they are objects that have a to_dict()
 - special decorators on properties/methods to indicate membership
 - reorganize contexts/ to remove many many import cycles

Context behavior changes:
 - 'dbt_version' is alwways available
 - 'target' is available as long as the profile has been parsed
 - 'project_name' is available in: query headers, hooks, models, and macro execution

Profiles/projects now render at load time
 - reading a profile or project file requires a ConfigRenderer
 - projects get an extra-fiddly special load for use during initial parsing
    - it returns the profile name and a function that, given a renderer, can return the rest of the Project
    - idea is: use the profile name to load the Profile, use that to build a ConfigRenderer that has a TargetContext, render the project with that
 - profiles.yml is rendered with the 'base' context
 - dbt_project.yml/schema.yml/packages.yml are rendered with the 'target' context: 'base' context + 'target'
 - docs are rendered with the docs context: 'target' context + the 'doc' function
 - query headers are rendered with the query header context: 'target' context + macros + 'project_name'
 - executed macros/models should have the same context as previously (query headers + adapter/model/etc related functions)

Moved actual ref/source searching into the manifest
Moved the rest of the parse utils into parser/manifest.py
Made the ref/source resolvers defined in the provider context a bit more sane/well-defined
Picked consistent-but-not-great names for all the various context generation functions
Moved write_node into ParsedNode
2020-02-06 08:40:57 -07:00
Jacob Beck
da74681de2 PR feedback 2020-02-06 07:54:24 -07:00
Jacob Beck
c8605647f1 fix --no-compile flag for docs generate 2020-02-06 07:53:40 -07:00
Jacob Beck
032d77dd15 Merge pull request #2094 from fishtown-analytics/fix/debug-from-subdir
allow "dbt debug" from subdirectories (#2086)
2020-02-06 07:37:33 -07:00
Jacob Beck
0df49c59c0 Merge pull request #2037 from fishtown-analytics/feature/faster-snowflake-catalogs
Feature: faster snowflake catalogs (#2009)
2020-02-06 07:36:58 -07:00
sonac
d354aa6f09 removing trailing space 2020-02-06 10:01:51 +01:00
Matt Ball
551bf42c66 Update DEBUG_LOG_FORMAT to correctly zero-pad the microsecond portion of the timestamp 2020-02-05 16:42:53 -07:00
Jacob Beck
b691a73dba Bump version: 0.15.2 → 0.16.0a2 2020-02-04 19:28:34 -07:00
Jacob Beck
26720997ea Merge branch 'dev/0.15.2' into dev/barbara-gittings 2020-02-04 19:28:15 -07:00
Jacob Beck
431ce50964 allow "dbt debug" from subdirectories
When dbt_project.yml is missing, fail more gracefully
When dbt_project.yml is missing and no profile is provided, validate all profiles:
 - read everything in profiles.yml
 - validate the default target for each profile
 - print no connection info
 - mark profiles.yml as valid
2020-02-04 13:19:52 -07:00
Jacob Beck
04bc2a800a PR feedback
Added a custom table merge implementation that tracks if a row is all null and merges those as "any type".
 - added unit tests for that!
Removed some schema casing things
fixed pluralization (it was reversed)
2020-02-04 07:48:00 -07:00
Jacob Beck
ccab27a1e2 Merge pull request #2089 from fishtown-analytics/feature/batched-tracking
batch anonymous usage statistics calls
2020-02-04 06:45:25 -07:00
sonac
fa40c41124 splitting long line 2020-02-04 10:08:03 +01:00
Jacob Beck
1881c0b932 batching 2020-02-03 08:56:30 -07:00
Connor McArthur
0d44dbf078 Bump version: 0.15.2a1 → 0.15.2 2020-02-02 14:16:13 -05:00
Connor McArthur
c5a6634d59 changelog 2020-02-02 14:16:07 -05:00
Connor McArthur
81dee7b857 Merge branch '0.15.latest' of github.com:fishtown-analytics/dbt into dev/0.15.2 2020-02-02 14:14:15 -05:00
Drew Banin
b1f88cb1a3 Merge pull request #2087 from fishtown-analytics/docs/0.15.2
Update CHANGELOG.md
2020-01-31 17:13:34 -05:00
Drew Banin
f2ba4b03a2 Update CHANGELOG.md 2020-01-31 17:11:48 -05:00
Jacob Beck
c1af3abbdc PR feedback w/ improved catalog results behavior 2020-01-31 11:52:43 -07:00
Jacob Beck
0bf6ecafa0 migrate other adapters to use the snowflake technique 2020-01-31 10:56:29 -07:00
Jacob Beck
0658a420ee Attempt getting snowflake to go faster and use less ram
- Thread information schema queries, one per db
- Pass schema list into catalog queries and filter on that in SQL
- break the existing interface for get_catalog (sorry, not sorry)
2020-01-31 10:53:15 -07:00
sonac
5823683265 injecting only message from validation error 2020-01-31 17:03:44 +01:00
Jacob Beck
5b65591cc2 Merge pull request #2068 from fishtown-analytics/feature/macro-docs
Add documentation for macros/analyses (#1041)
2020-01-31 07:28:06 -07:00
Andrii
7fae368c0d Apply suggestions from code review
adding actual error text into wrapper

Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2020-01-31 13:36:07 +02:00
Jacob Beck
04ebdbb00b PR feedback 2020-01-30 21:18:46 -07:00
Jacob Beck
b030b4eba5 Add documentation for macros/analyses
Refactored parsing a bit to support the idea of:
 - macro patches (no columns)
 - node patches that don't get tests (analyses)
2020-01-30 21:12:34 -07:00
Jacob Beck
b01226fe62 Merge pull request #2035 from fishtown-analytics/fix/manifest-search
Fix manifest search (#2032)
2020-01-30 21:10:47 -07:00
Jacob Beck
e080bfc79a Merge pull request #2069 from fishtown-analytics/feature/snowflake-oauth
add oauth authentication to snowflake adapter (#2050)
2020-01-30 15:51:48 -07:00
sonac
9eaf2438dd making error msg for malformed packages.yml better 2020-01-30 10:56:31 +01:00
Drew Banin
bd5e6bd1e6 Merge pull request #2076 from markberger/setuptools-check
add setuptools version check for overall setup.py
2020-01-29 15:32:22 -05:00
Jacob Beck
62755fe5b1 More macro search changes
Unify the lookup for the various macros, all of them are done in the manifest now:
 - dbt's built-in package + all plugins are part of 'core'
 - finding macros has the concept of an optional package name
 - materializations are a special case of finding macros
 - the `generate_*_name` macros are another special case (non-core dependency packages are ignored)
 - generating the macro execution context now uses the concept of an optional search_package_name
 - macro execution now binds these two together

Unify the lookup for models/docs/sources
 - added a search_name property to those three types
 - create a 'Searchable' protocol, have the node search code accept that
 - simplify matching logic/push it into the correct types accordingly

Rename get_materialization_macro to find_materialization_macro_by_name (consistency)

context namespacing behavior:
 - dbt run-operation now passes the named package along to macro execution if a package name is specified
 - so `dbt run-operation dependency.ad` runs with the dependency namespace as local, overriding globals
 - but `dbt run-operation my_macro` runs in the current package namespace as local, even if it ultimately runs the dependency's my_macro()
 - the current package namespace is always treated as "global", overriding core macros

Tons of tests
2020-01-29 13:29:43 -07:00
Jacob Beck
e570d22f40 Merge pull request #2010 from fishtown-analytics/fix/mypy-cleanups
Fix some mypy checking
2020-01-29 13:19:49 -07:00
Jacob Beck
3e48dc3b4d PR feedback 2020-01-29 10:15:48 -07:00
Mark Berger
9b06679d56 add setuptools version check for overall setup.py 2020-01-28 16:51:26 -08:00
Jacob Beck
9cc7a7a87f Fix mypy checking
Make mypy check our nested namespace packages by putting dbt in the mypy_path.
Fix a number of exposed mypy/type checker complaints. The checker mostly
passes now even if you add `--check-untyped-defs`, though there are a couple lingering issues so I'll leave that out of CI
Change the return type of RunOperation a bit - adds a couple fields to appease mypy

Also, bump the mypy version (it catches a few more issues).
2020-01-27 12:48:10 -07:00
Jacob Beck
4e23e7dbd1 Merge pull request #2039 from fishtown-analytics/feature/source-tags
Tags for sources and columns (#1906, #1586)
2020-01-27 11:26:25 -07:00
Jacob Beck
d9ec8125c9 Merge pull request #2071 from fishtown-analytics/fix/avoid-disabled-models-crash
Fix crash on disabled models (#2070)
2020-01-27 11:26:20 -07:00
Drew Banin
73c418cf57 Merge pull request #2060 from emilieschario/dev/0.15.1
Add logging for a file path that can't be cleaned
2020-01-27 12:56:36 -05:00
Jacob Beck
8b722c7951 allow single name tags in test configs 2020-01-27 10:38:26 -07:00
Drew Banin
fdfcd4c651 Merge pull request #2062 from fishtown-analytics/fix/overwrite-clobber-config-fields
Overwrite clobber config fields
2020-01-27 12:23:37 -05:00
Jacob Beck
4357c3c74e fix partial parsing to avoid a KeyError, tests 2020-01-27 10:19:59 -07:00
Jacob Beck
5c80d6ab4e Add a tags parameter to tests, like severity
- edited a test to exercise it
2020-01-27 09:30:58 -07:00
emilielimaburke
199b1ed1dc fix protected_abs_paths 2020-01-25 18:00:45 -05:00
Jacob Beck
b9f8dedc8b Merge branch 'dev/0.15.2' into dev/barbara-gittings 2020-01-24 13:56:59 -07:00
Jacob Beck
96a9c6cb92 Merge pull request #2067 from fishtown-analytics/fix/docs-missing-tag-hang
fix an RPC hang when enddocs tags were missing (#2066)
2020-01-24 13:38:34 -07:00
Jacob Beck
2ad5122ef4 Add tags for sources
Allow column-level tags, which are inherited by their tests
2020-01-24 13:33:55 -07:00
Drew Banin
1f749805a5 (#2049) Fix for clobber config fields which were errantly extended 2020-01-24 15:22:44 -05:00
Jacob Beck
de5ff68943 PR feedback: lines not chars 2020-01-24 13:08:53 -07:00
Jacob Beck
e7d2221d08 add oauth authentication to snowflake adapter 2020-01-24 10:26:16 -07:00
Jacob Beck
0b8f3bc4c5 Merge pull request #2065 from fishtown-analytics/fix/ignore-alternative-resource-types
ignore alternative resource types when reading partial parse cache (#2064)
2020-01-24 10:20:59 -07:00
Jacob Beck
9d5488e361 Merge pull request #2051 from fishtown-analytics/feature/docs-for-everyone
Make dbt support documentation for snapshots and seeds (and analyses) (#1974)
2020-01-24 09:45:53 -07:00
Jacob Beck
80574a5a6a Merge pull request #2046 from fishtown-analytics/feature/is-number
Add Column.is_number/is_float (#1969)
2020-01-24 09:43:21 -07:00
Jacob Beck
affdbe719a PR feedback 2020-01-24 08:27:19 -07:00
Jacob Beck
ba3e14cefd fix an RPC hang when enddocs tags were missing, add tests 2020-01-24 08:21:58 -07:00
emilielimaburke
105dc001e5 put on two lines 2020-01-23 20:24:01 -05:00
Jacob Beck
8bdaa69a2c flake8 2020-01-23 11:08:21 -07:00
Jacob Beck
760d46af5c when going through the partial parsing, ignore cached nodes that do not match the current parser resource type 2020-01-22 12:51:35 -07:00
Jacob Beck
21c3f41814 fix dedent/formatting with "Deprecation Warning" prefix 2020-01-22 11:42:48 -07:00
Jacob Beck
5c42f1af14 Merge pull request #2058 from fishtown-analytics/feature/expand-schema-search
expand search for docs and schema.yml (#1832)
2020-01-22 11:25:01 -07:00
Jacob Beck
7a89ef2465 PR feedback 2020-01-22 10:20:26 -07:00
Drew Banin
38244bfdb3 Merge pull request #2026 from NiallRees/fix/disabled_tests_in_stats
Exclude tests for disabled models in compile stats
2020-01-20 22:43:23 -05:00
Emilie Lima Schario
e7759b4bae Update error message with error warning 2020-01-20 19:56:59 -05:00
Jacob Beck
66a4f76c74 Merge pull request #2056 from fishtown-analytics/fix/partial-parsing-disabled-models
Fix partial parsing with disabled models (#2055)
2020-01-18 21:37:04 -07:00
emilielimaburke
80c9e623ef add logging for a file path that can't be cleaned 2020-01-18 15:28:05 -05:00
Drew Banin
722d87ca5c Merge pull request #2057 from aescay/feature/add_target_flag_alias
Add -t shorthand for --targets
2020-01-17 14:57:31 -05:00
Jacob Beck
f0221b1fdb expand search for docs and schema.yml
update tests
appease mypy
2020-01-17 11:34:03 -07:00
aescay
51e65a35a6 add -t shorthand for --targets 2020-01-17 11:49:18 -05:00
Jacob Beck
a8b93f6d48 fix an issue where partial parsing assumed model ids could not overlap between disabled and enabled 2020-01-17 09:32:05 -07:00
Jacob Beck
9b7de69f08 Bump version: 0.15.1 → 0.15.2a1 2020-01-17 09:31:00 -07:00
NiallRees
554b32efb5 Split expression onto two lines 2020-01-16 22:32:42 +00:00
Jacob Beck
c4a5eb9803 PR feedback
add support for newlines in output
line-wrap deprecations
2020-01-16 09:19:10 -07:00
Jacob Beck
f92f389612 Make dbt support documentation for snapshots and seeds (and analyses)
Add a deprecation warning for snapshots/seeds documented under "models"
Add a bunch of tests
2020-01-16 08:02:09 -07:00
Jacob Beck
1021637283 Merge pull request #2045 from fishtown-analytics/feature/detect-duplicate-macros
Detect duplicate macros (#1891)
2020-01-16 08:01:22 -07:00
Jacob Beck
d85a54a01c PR feedback: improve error message formatting 2020-01-16 07:32:34 -07:00
Connor McArthur
77dceab7be Merge pull request #2053 from fishtown-analytics/dev/0.15.1
dbt 0.15.1
2020-01-16 09:27:54 -05:00
Jacob Beck
01e97ff285 Merge pull request #2042 from fishtown-analytics/feature/select-on-seeds
support --select on dbt seed (#1711)
2020-01-16 07:19:49 -07:00
Connor McArthur
f6f0f5ce1e Bump version: 0.15.1rc2 → 0.15.1 2020-01-16 08:46:19 -05:00
Connor McArthur
75a5c09981 Merge branch 'dev/0.15.1' of github.com:fishtown-analytics/dbt into dev/0.15.1 2020-01-16 08:44:51 -05:00
Connor McArthur
16674275ed add changelog entry for source snapshot-freshness command 2020-01-16 08:44:49 -05:00
Connor McArthur
a0524af242 Merge pull request #2041 from fishtown-analytics/fix/source-freshness-rpc
Add source snapshot-freshness to dbt rpc (#2040)
2020-01-16 08:43:23 -05:00
Jacob Beck
54b64f8922 add "is_number" and "is_float" Column methods
Split out snowflake column type
added column type test integration tests
2020-01-13 16:32:50 -07:00
Jacob Beck
706cea8996 detect duplicate macro names in the same project, and raise an exception about it 2020-01-13 12:19:28 -07:00
Jacob Beck
1a72660a3f Merge pull request #1989 from franloza/fix/project-dir-debug
Fix project dir argument when running debug (#1733)
2020-01-10 15:31:59 -07:00
Jacob Beck
efe75d9b51 Add support for --select and --exclude for seeds 2020-01-10 14:51:19 -07:00
Jacob Beck
2ac24991c1 Add source snapshot-freshness to dbt rpc 2020-01-10 14:16:40 -07:00
Jacob Beck
b11aab5369 Merge pull request #2036 from fishtown-analytics/feature/toyaml-fromyaml
add toyaml/fromyaml (#1911)
2020-01-10 12:20:34 -07:00
Jacob Beck
a348c3f7c1 Merge branch 'dev/0.15.1' into dev/barbara-gittings 2020-01-08 13:31:52 -07:00
Jacob Beck
78a731b9e2 add toyaml/fromyaml 2020-01-08 13:23:17 -07:00
Fran Lozano
94ede83462 Remove unnecessary call to use_default_project call from test 2020-01-08 00:01:26 +01:00
Fran Lozano
1085fb8cf4 Split line too long 2020-01-07 23:46:05 +01:00
Drew Banin
0a5b436da1 Merge pull request #2015 from tayloramurphy/add-data-dictionary
Handle "meta:" key in schema/sources.yml
2020-01-07 10:49:29 -05:00
Drew Banin
7d3591f5c1 Merge pull request #2012 from fishtown-analytics/fix/pedantic-pluralize
Fix: pedantic pluralization
2020-01-06 17:23:12 -05:00
Drew Banin
4ed1986c26 Merge pull request #2028 from alanmcruickshank/ref_override
Allow ref override
2020-01-06 17:19:33 -05:00
Jacob Beck
33b4e72401 Merge pull request #2022 from fishtown-analytics/fix/better-plugin-import-errors
improve errors on importing plugins (#2006)
2020-01-06 15:11:29 -07:00
Jacob Beck
c36a463f44 Merge pull request #2024 from fishtown-analytics/fix/ephemeral-cancelled
Fix cancelled ephemeral models (#1993)
2020-01-06 15:11:09 -07:00
Jacob Beck
f85015365f Merge pull request #2025 from fishtown-analytics/fix/parse-refs-in-hooks
Parse model hooks for refs (#1957)
2020-01-06 15:05:15 -07:00
Jacob Beck
b9a47cafb2 Merge pull request #2031 from fishtown-analytics/feature/improve-on-run-end
improve on run end (#1924)
2020-01-06 15:05:05 -07:00
Jacob Beck
22ceecaa87 Merge pull request #2023 from fishtown-analytics/fix/unit-test-concurrency
make unit tests use a one-per-test temp directory
2020-01-06 15:04:01 -07:00
Alan Cruickshank
37e373a68f Allow ref override + tests 2020-01-06 18:15:09 +00:00
Jacob Beck
ff9013d977 add db, schema pairs to the context
Update tests to use that information
2020-01-06 10:47:37 -07:00
Jacob Beck
39f92e527c Merge pull request #2030 from fishtown-analytics/fix/detect-deps-same-name-as-root
detect deps with the same name as the root
2020-01-05 11:06:25 -07:00
Jacob Beck
f1ef68c166 fix the unit tests 2020-01-05 10:32:38 -07:00
Jacob Beck
260752f501 when the root project name is the same as a dependency project name, raise an error 2020-01-03 19:13:51 -07:00
Connor McArthur
01c23c3414 Bump version: 0.15.1rc1 → 0.15.1rc2 2019-12-30 13:44:38 -05:00
Taylor A. Murphy
dea5f98c4e Additional meta references 2019-12-30 09:12:38 -06:00
Niall Woodward
38d51cd97c Exclude tests for disabled models in compile stats 2019-12-28 12:06:12 +00:00
Jacob Beck
22db36c4a6 Parse model hooks for refs
Add a parse pass for model hooks, parse them like models
Attach the hook refs as if they were on the model
Add a test exercising refs
2019-12-24 19:06:42 -07:00
Connor McArthur
71a293ce46 Merge pull request #2021 from fishtown-analytics/0.15.1-changelog-rc-2
Update CHANGELOG.md
2019-12-24 14:25:25 -05:00
Jacob Beck
75356cd9ce suppress cancel logs on ephemeral nodes
During ctrl+c handling, check the name of the canceled connection
if it's the name of an ephemeral node, suppress the cancel message
2019-12-23 14:33:41 -07:00
Jacob Beck
c7e2d2270d make unit tests use a one-per-test temp directory - fix concurrent rm/create conflict 2019-12-23 13:04:49 -07:00
Jacob Beck
df05037841 improve errors on import to be more specifically correct
When the error is about not being able to import the plugin, indicate that
Otherwise log the original error stack trace at debug and re-raise it
2019-12-23 11:28:40 -07:00
Jacob Beck
1235b3f468 Merge branch 'dev/0.15.1' into dev/barbara-gittings 2019-12-23 09:48:11 -07:00
Fran Lozano
99e778b9b7 Update order of imports in test_debug 2019-12-21 14:19:27 +01:00
Fran Lozano
c1b3690671 Use --project-dir outside current dir in debug when it exists #1733 2019-12-21 14:06:01 +01:00
Drew Banin
df1384bf31 Update CHANGELOG.md 2019-12-20 16:38:28 -05:00
Drew Banin
5a4ddd685d Merge pull request #1967 from kconvey/feature/bq_sql_header
Add support for sql as a header to create or replace
2019-12-20 16:35:37 -05:00
Jacob Beck
8cd8705d67 Merge pull request #2018 from fishtown-analytics/fix/cleanup-retries
clean up some retry logic (#2016)
2019-12-19 15:02:28 -07:00
Taylor A. Murphy
4927674306 Alter formatting for another commit 2019-12-19 13:55:07 -06:00
Taylor A. Murphy
eb464752fb Bane of my existence 2019-12-19 13:42:17 -06:00
Taylor A. Murphy
210092eeda Update docs blocks integration test 2019-12-19 13:20:24 -06:00
Taylor A. Murphy
9f7ecd28f9 Update docs generate integration test 2019-12-19 13:18:41 -06:00
Taylor A. Murphy
a05b013ecc Fix a few more tests 2019-12-19 12:32:00 -06:00
Taylor A. Murphy
bc49fc2e10 Remove straggling comma 2019-12-19 12:12:21 -06:00
Taylor A. Murphy
344af97656 Forgot some commas 2019-12-19 12:01:53 -06:00
Taylor A. Murphy
ac650c1034 Update ColumnInfo tests 2019-12-19 11:32:17 -06:00
Taylor A. Murphy
b411630ad4 More test updates 2019-12-19 11:24:19 -06:00
Jacob Beck
86b917fd33 fixes 2019-12-19 10:22:10 -07:00
Taylor A. Murphy
c1815d6750 Update tests 2019-12-19 11:12:06 -06:00
Kurt Convey
e6ab64ee45 Fix expected number of test models 2019-12-19 09:39:50 -07:00
Taylor A. Murphy
f7d39ae7b3 Switch to 'meta' from 'data' 2019-12-19 10:25:24 -06:00
Kurt Convey
98c6eace0c materialize test model as table 2019-12-18 14:26:37 -07:00
Connor McArthur
6be4ee00aa Bump version: 0.15.0 → 0.15.1rc1 2019-12-18 15:37:30 -05:00
Connor McArthur
88b35f7ee1 Merge pull request #2014 from fishtown-analytics/changelog/0.15.1
Update CHANGELOG.md for v0.15.1
2019-12-18 15:36:25 -05:00
Drew Banin
27212a16dd Update CHANGELOG.md 2019-12-18 15:33:20 -05:00
Jacob Beck
74672c8fad Merge branch 'dev/0.15.1' into dev/barbara-gittings 2019-12-18 12:58:21 -07:00
Taylor A. Murphy
d4108ca840 Add data from sources to manifest.json 2019-12-18 12:39:41 -06:00
Taylor A. Murphy
499aa57ac2 Handle "data:" key in schema/sources.yml 2019-12-18 12:04:49 -06:00
Drew Banin
0b05bf0fa6 Update CHANGELOG.md
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2019-12-18 11:59:59 -05:00
Jeremy Cohen
d98613db13 Mutatis mutandis 2019-12-18 10:06:44 -05:00
Drew Banin
c4baf72fd4 Update CHANGELOG.md 2019-12-18 09:55:15 -05:00
Jeremy Cohen
d725c7c5e6 One analysis, two analyses 2019-12-17 21:35:23 -05:00
Kurt Convey
0dfb5d4441 Fix merge conflicts and integration test 2019-12-17 10:42:50 -07:00
Kurt Convey
d9ee6ea917 Add semicolon after UDF 2019-12-16 11:02:44 -07:00
Jacob Beck
0fe5fb628a Merge pull request #2007 from fishtown-analytics/fix/bigquery-upper-bounds
put an upper bound on bigquery
2019-12-13 14:31:40 -07:00
Drew Banin
fea06c6fdb Merge pull request #1964 from kconvey/feature/bq_labels
BigQuery Labels
2019-12-13 15:58:45 -05:00
Jacob Beck
02c2926ea4 Merge pull request #2005 from fishtown-analytics/fix/bigquery-catalog-info-schema-queries
Fix bigquery catalog queries with nonexistent schemas (#1984)
2019-12-13 13:58:31 -07:00
Jacob Beck
b151e2a15a Merge pull request #2001 from fishtown-analytics/fix/snapshot-check-all-added-column
Fix snapshot check all with an added column (#1797)
2019-12-13 13:49:25 -07:00
Jacob Beck
a78b4fda8f put an upper bound on bigquery, add six 2019-12-13 11:42:33 -07:00
Jacob Beck
a702f58ed7 Merge pull request #1994 from fishtown-analytics/fix/overlapping-valid-timestamps
Avoid overlapping validity timestamps (#1736)
2019-12-13 10:22:48 -07:00
Jacob Beck
8311285adb In bigquery, filter out missing schemas before querying the catalog 2019-12-13 09:17:22 -07:00
Jacob Beck
62e22c27d5 Merge pull request #1920 from fishtown-analytics/feature/tighten-agate-values
Agate fixes (#999) (#1639)
2019-12-13 08:42:22 -07:00
Jacob Beck
89dd04d06e Merge pull request #2002 from fishtown-analytics/fix/rpc-thread-names
call pull_information() before we enqueue the log record (#1905)
2019-12-13 08:38:23 -07:00
Jacob Beck
9222f803f4 Merge pull request #1963 from kconvey/feature/retries
Implement retries in BQ adapter
2019-12-12 11:25:34 -07:00
Jacob Beck
2456b6dc8b Merge pull request #2003 from fishtown-analytics/fix/postgres-redshift-wildcard-pg
escape the wildcard underscore in postgres "like" queries (#1960)
2019-12-12 10:33:44 -07:00
Jacob Beck
91b97641a2 tests 2019-12-12 09:30:15 -07:00
Jacob Beck
123c3fa78b escape the wildcard underscore in postgres "like" queries 2019-12-12 08:15:43 -07:00
Jacob Beck
ab4925f59f this is ok now 2019-12-11 13:30:04 -07:00
Kurt Convey
0356a74d4c clean up merge conflict 2019-12-11 13:18:51 -07:00
Kurt Convey
37a1288ab0 Merge branch 'feature/retries' of github.com:kconvey/dbt into feature/retries 2019-12-11 12:59:04 -07:00
Kurt Convey
43959dc4d0 Return function results 2019-12-11 12:57:32 -07:00
Jacob Beck
11e5e1b0f2 call pull_information() before we enqueue the log record 2019-12-11 11:59:59 -07:00
Jacob Beck
9589e7aaa1 Fix snapshot check all strategy with added column 2019-12-11 11:21:29 -07:00
Kurt
c5c7932ee7 Update plugins/bigquery/dbt/adapters/bigquery/connections.py
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2019-12-11 12:47:41 -05:00
Kurt Convey
5d181c3ef2 Clean up tests 2019-12-11 08:50:21 -07:00
Kurt
ce4c58a1f0 Update plugins/bigquery/dbt/adapters/bigquery/connections.py
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2019-12-10 12:56:33 -05:00
Kurt
e6b4a12c08 Update plugins/bigquery/dbt/adapters/bigquery/connections.py
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2019-12-10 12:56:24 -05:00
Kurt
7c8a21ddcf Update plugins/bigquery/dbt/adapters/bigquery/connections.py
Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2019-12-10 12:55:56 -05:00
Jacob Beck
40839c79fc Merge pull request #1996 from Fokko/remove-duplicates
Remove the duplicate get_context_modules
2019-12-10 09:40:01 -07:00
Fokko Driesprong
284f5a4968 Add mypy ignore 2019-12-10 15:53:49 +01:00
Fokko Driesprong
6241ff4bef Whoops, missed that one 2019-12-10 15:43:22 +01:00
Fokko Driesprong
cf41203504 Remove the duplicate get_context_modules 2019-12-10 15:38:22 +01:00
Kurt Convey
460d73f55e Fix formatting 2019-12-09 16:47:50 -07:00
Kurt Convey
09403094cd Still get table ref 2019-12-09 15:10:38 -07:00
Kurt
40c9328e84 Apply suggestions from code review
Clean up retries unit test's connection manager mocking

Co-Authored-By: Jacob Beck <beckjake@users.noreply.github.com>
2019-12-09 17:09:28 -05:00
Jacob Beck
382a993e68 make the test even more strict 2019-12-09 15:05:29 -07:00
Kurt Convey
0f05404f72 Add sql_header common default 2019-12-09 14:45:02 -07:00
Jacob Beck
33836ea5f8 Avoid overlapping validity timestamps, tests 2019-12-09 14:21:31 -07:00
Jacob Beck
cc491904bc Merge pull request #1992 from fishtown-analytics/feature/lazy-load-connections
lazy-load connections (#1584)
2019-12-09 12:02:48 -07:00
Jacob Beck
be36c5d974 lazy-load connections 2019-12-09 11:57:43 -07:00
Jacob Beck
2dd604b039 Merge pull request #1976 from fishtown-analytics/fix/override-materializations
add a formal search order for materializations, tests (#1962)
2019-12-09 11:43:01 -07:00
Fran Lozano
58a371f9d9 Fix #1733 2019-12-08 01:09:28 +01:00
Jacob Beck
564cc22d95 Merge pull request #1988 from Fokko/fd-use-generated-at
Use generated_at instead of datetime.utcnow()
2019-12-06 09:45:36 -07:00
Jacob Beck
1da96b56e5 Merge pull request #1982 from Fokko/fd-add-annotations
Add annotations to the code
2019-12-06 09:10:01 -07:00
Fokko Driesprong
5a929b095c Limit the line length 2019-12-06 16:00:53 +01:00
Fokko Driesprong
3bc4834c82 Use generated_at instead of datetime.utcnow() 2019-12-06 09:15:49 +01:00
Fokko Driesprong
43213cddfd Merge branch 'dev/0.15.1' into fd-add-annotations 2019-12-06 08:49:45 +01:00
Fokko Driesprong
9975cd5f27 Fix code style voilations 2019-12-06 08:36:32 +01:00
Jacob Beck
65b4c18ab6 Merge pull request #1977 from fishtown-analytics/feature/emit-warning-context
add exceptions.warn to the dbt context (#1970)
2019-12-05 16:45:48 -07:00
Jacob Beck
d14d250e0a Merge pull request #1983 from fishtown-analytics/fix/restore-drop-schema
Restore drop_schema to adapter in jinja context (#1980)
2019-12-05 16:45:28 -07:00
Jacob Beck
a993d9c355 Merge pull request #1978 from fishtown-analytics/feature/detect-downlevel-setuptools
fail more gracefully when setuptools is downlevel (#1975)
2019-12-05 16:44:52 -07:00
Jacob Beck
14c4bc7967 rename emit_warning -> warn 2019-12-05 14:26:27 -07:00
Jacob Beck
143402dace do not commit after dropping the schema 2019-12-05 14:23:52 -07:00
Jacob Beck
3dff3e71bc PR feedback 2019-12-05 14:20:24 -07:00
Jacob Beck
549168b835 Merge pull request #1979 from fishtown-analytics/feature/configurable-test-warehouses
make tests honor environment variables (#1939)
2019-12-05 14:18:00 -07:00
Fokko Driesprong
d4c1dbb133 Remove BaseRelation 2019-12-05 20:53:10 +01:00
Fokko Driesprong
ca454360d8 Process comments 2019-12-05 20:34:03 +01:00
Fokko Driesprong
2f17424b89 Add annotations to the code 2019-12-05 19:24:56 +01:00
Jacob Beck
b80fd81e65 Restore drop_schema to contexts
Made a few related fixes to caching behavior w.r.t schemas
Added a test
2019-12-05 11:15:49 -07:00
Jacob Beck
5797be9b14 make tests honor environment variables 2019-12-04 14:49:36 -07:00
Jacob Beck
a771c637d7 fail more gracefully when setuptools is missing 2019-12-04 14:36:15 -07:00
Jacob Beck
dd5e47f2e0 add exceptions.emit_warning to the dbt context 2019-12-04 14:10:17 -07:00
Jacob Beck
1b0d635152 add a formal search order for materializations, tests 2019-12-04 10:45:02 -07:00
Drew Banin
ace777e495 Merge pull request #1973 from fishtown-analytics/drew-update-contributing-guidelines
update contributing guidelines and PR template
2019-12-04 12:09:45 -05:00
Drew Banin
e882ed39dc Update CONTRIBUTING.md
Co-Authored-By: Connor McArthur <connor@fishtownanalytics.com>
2019-12-04 12:09:31 -05:00
Kurt Convey
7c6f878c69 Update bq integration test's expected number of models 2019-12-04 09:50:32 -07:00
Kurt Convey
fc94a5e19d Add snowflake support and test UDF 2019-12-04 09:21:03 -07:00
Drew Banin
7538f1eb9c Update CONTRIBUTING.md 2019-12-03 21:59:17 -05:00
Drew Banin
43028fe89d Update pull_request_template.md 2019-12-03 20:15:41 -05:00
Drew Banin
5f9b8b92ba Create pull_request_template.md 2019-12-03 20:13:55 -05:00
Drew Banin
4c865ac793 Update CONTRIBUTING.md 2019-12-03 20:01:00 -05:00
Kurt Convey
7a51ef664a Another newline 2019-12-03 16:04:58 -07:00
Kurt Convey
9e9f0307df newline and better test 2019-12-03 16:04:19 -07:00
Kurt Convey
5eafbb794a Make sql_header universal accross adapters 2019-12-03 16:01:52 -07:00
Kurt Convey
b5483754d0 Merge branch 'feature/retries' of https://github.com/kconvey/dbt into feature/retries 2019-12-03 15:21:39 -07:00
Kurt Convey
e03fd44d6d Use client.create_dataset and client.delete_dataset 2019-12-03 15:19:58 -07:00
Kurt
9b9c1db05e Update plugins/bigquery/dbt/adapters/bigquery/connections.py
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2019-12-03 15:02:05 -05:00
Kurt Convey
63d14f3a0c Fix integration test and enable labels for views as well 2019-12-03 12:57:32 -07:00
Kurt Convey
bef02456eb newline 2019-12-02 15:44:56 -07:00
Kurt Convey
a4efd223a4 Add integration test and implement suggestions 2019-12-02 15:42:45 -07:00
Kurt Convey
5058049096 Add support for sql as a header to create or replace 2019-12-02 10:45:26 -07:00
Kurt Convey
c0e854007e Remove fake credentials in unit test 2019-11-27 17:22:48 -07:00
Kurt Convey
118344ba62 Mock 2019-11-27 14:29:47 -07:00
Kurt Convey
33e75f8fe1 Mock 2019-11-27 14:17:21 -07:00
Kurt Convey
03472381a0 Mock 2019-11-27 13:56:14 -07:00
Kurt Convey
3aabe2da1a Add query_comment 2019-11-27 13:47:53 -07:00
Kurt Convey
ad0bd8763a fix 2019-11-27 13:27:34 -07:00
Kurt Convey
3b696eedb2 fix test better 2019-11-27 13:13:34 -07:00
Kurt Convey
b2c1727b67 Fix test 2019-11-27 12:52:49 -07:00
Kurt Convey
b602c9cdf6 Fix 2019-11-27 12:05:09 -07:00
Kurt Convey
86f0609931 blank line 2019-11-27 08:36:07 -07:00
Kurt Convey
3b456593a6 fix imports 2019-11-27 08:33:19 -07:00
Kurt Convey
a3e8eabc66 cleanup 2019-11-27 08:28:02 -07:00
Kurt Convey
df2c498e9a Implement bq labels as a configurable option 2019-11-27 08:22:37 -07:00
Kurt Convey
21da0ed5b3 Implement retries in BQ adapter 2019-11-27 06:03:44 -07:00
Connor McArthur
e51c942e91 Bump version: 0.15.0rc2 → 0.15.0 2019-11-25 13:38:30 -05:00
Connor McArthur
9dec850072 set release date for 0.15.0 2019-11-25 13:38:06 -05:00
Drew Banin
7814ea05be Merge pull request #1953 from fishtown-analytics/fix/0.15.0-changelog-docs
Update CHANGELOG.md
2019-11-25 10:22:33 -05:00
Drew Banin
7f9750dd4f Update CHANGELOG.md 2019-11-25 10:20:42 -05:00
Jacob Beck
5b1e4f56a0 Merge pull request #1951 from fishtown-analytics/fix/change-psycopg-name
Use 'psycopg2-binary' package by default on all platforms
2019-11-25 08:15:54 -07:00
Drew Banin
9a9f705c42 Merge pull request #1954 from fishtown-analytics/fix/lma-contributing
Update CONTRIBUTING.md
2019-11-25 10:12:59 -05:00
Drew Banin
08bb956a20 Merge pull request #1952 from fishtown-analytics/fix/bq-catalog-gen-query
Fix for catalog generation with external tables (bigquery)
2019-11-25 10:12:23 -05:00
Drew Banin
c4938e6567 change fallback column name from "null" to "unknown" 2019-11-25 09:35:06 -05:00
Drew Banin
0dc34c347d Update CONTRIBUTING.md 2019-11-25 09:21:53 -05:00
Drew Banin
faf5a88727 Update CHANGELOG.md 2019-11-24 19:23:15 -05:00
Drew Banin
97722bef03 Update CHANGELOG.md 2019-11-24 16:01:12 -05:00
Drew Banin
a268f79878 (#1950) Coalesce BQ types and names for external tables 2019-11-22 17:00:29 -05:00
Jacob Beck
45a155a765 Use psycopg2-binary package on all platforms 2019-11-22 13:42:10 -07:00
Jacob Beck
7ae632d33f Merge pull request #1946 from fishtown-analytics/fix/catalog-partially-quoted-resources
Fix catalog generation with unquoted resources on postgres/redshift (#1943)
2019-11-22 11:59:32 -07:00
Jacob Beck
582e8e183c just ignore it if we get multiple databases on redshift/postgres, and pick the first one instead 2019-11-21 11:13:05 -07:00
Jacob Beck
c28f2dc5c1 fix snowflake test 2019-11-20 15:09:01 -07:00
Jacob Beck
0b18212e69 Agate fixes
Restrict agate date+datetime formats
remove timedelta
When column types are specified for a seed, parse those columns as Text unconditionally
2019-11-20 13:53:08 -07:00
Jacob Beck
67d499e9de Merge pull request #1936 from fishtown-analytics/fix/postgres-mixedcase-database
Fix postgres mixedcase database (#1800)
2019-11-20 07:27:48 -07:00
Jacob Beck
46c28405ec update changelog 2019-11-19 14:20:21 -07:00
Jacob Beck
2e825963f1 Merge pull request #1938 from fishtown-analytics/fix/loosen-iam-restrictions
Fix credential validation on iam auth (#1925)
2019-11-19 14:16:25 -07:00
Jacob Beck
b8858cc1f5 Redshift: Make iam_duration_seconds and password optional again for "iam"
Move password field to be the last postgres non-optional
  - now redshift can override it to be optional
Add a check for redshift "database" method that password is set
Fix up some optional/default behavior
Unit test to make sure we don't regress
2019-11-19 11:24:33 -07:00
Jacob Beck
77252de54c add test + new db configuration stuff 2019-11-19 10:52:21 -07:00
Jacob Beck
2869467b4f handle cased databases properly on pg/rs 2019-11-19 10:05:31 -07:00
Connor McArthur
099adf9bab Bump version: 0.15.0rc1 → 0.15.0rc2 2019-11-19 11:08:23 -05:00
Jacob Beck
31e49fb662 Merge pull request #1935 from fishtown-analytics/feature/optional-quote-columns
make column quoting optional (#1917)
2019-11-19 09:06:30 -07:00
Jacob Beck
3405745182 PR feedback 2019-11-19 06:59:36 -07:00
Drew Banin
09f2aae866 Merge pull request #1931 from fishtown-analytics/fix/drop-backup-tables-on-full-refresh
Fix for drop statement which was not namespaced to a schema
2019-11-18 23:27:47 -05:00
Drew Banin
0579c49ab0 Merge pull request #1932 from fishtown-analytics/fix/remove-jsonschema-requirement-from-core
rm jsonschema requirement from core
2019-11-18 19:06:01 -05:00
Drew Banin
7df980b5d4 Merge pull request #1934 from fishtown-analytics/fix/get-catalog-requires-one-database
Fix for broken catalog generation on pg/redshift
2019-11-18 17:58:07 -05:00
Jacob Beck
c2b2f70a69 make column quoting optional 2019-11-18 15:26:00 -07:00
Drew Banin
5c723bf92d pr feedback 2019-11-18 17:16:47 -05:00
Drew Banin
905f634781 (#1926) Fix for broken catalog generation on pg/redshift 2019-11-18 16:40:06 -05:00
Drew Banin
b62f0ee511 Update CHANGELOG.md 2019-11-18 16:08:37 -05:00
Drew Banin
ba111db2a8 Update CHANGELOG.md 2019-11-18 16:06:20 -05:00
Drew Banin
03d6b40afe rm jsonschema requirement from core 2019-11-18 15:28:34 -05:00
Drew Banin
6f1df6ae65 Fix for drop statement which was not namespaced to a schema 2019-11-18 14:58:00 -05:00
Drew Banin
2d6fc280fe Merge pull request #1927 from fishtown-analytics/fix/stdout-logging-rpc
fix for stdout logging of on-run- hooks
2019-11-18 14:12:24 -05:00
Drew Banin
e7ffdcd09f Merge pull request #1928 from fishtown-analytics/fix/anonymous-event-tracking-errors
fix for serialization issue around node timing
2019-11-18 14:12:12 -05:00
Drew Banin
17dacd31f4 fix for stdout logging of on-run- hooks 2019-11-18 13:05:02 -05:00
Drew Banin
f7b9874a93 fix for serialization issue around node timing 2019-11-18 12:45:51 -05:00
Drew Banin
36fa9bd8bf Merge pull request #1852 from fishtown-analytics/lma-changelog
Update CHANGELOG for Louisa May Alcott release
2019-11-17 23:31:38 -05:00
Drew Banin
51372897bd Update CHANGELOG.md 2019-11-17 23:26:35 -05:00
Drew Banin
1831acbaec Update CHANGELOG.md 2019-11-17 22:49:08 -05:00
Drew Banin
4dffd524e9 Merge pull request #1923 from fishtown-analytics/bump/docs-0150
bump index.html for 0.15.0 in docs site
2019-11-17 20:26:16 -05:00
Drew Banin
a5a78e6173 bump index.html for 0.15.0 in docs site 2019-11-17 17:58:07 -05:00
Jacob Beck
51bca114a0 Merge pull request #1918 from JusLarsen/fix/dbname-case-sensitivity
remove case sensitivity from postgres/redshift dbname verification
2019-11-13 11:38:06 -07:00
Justin Larsen
72f0559f8a remove case sensitivity from postgres dbname verification 2019-11-13 09:29:18 -07:00
Jacob Beck
9be47e67d9 Merge pull request #1903 from JusLarsen/fix/schema-test-error
Fix tuple index out of range in custom schema tests
2019-11-13 06:34:08 -07:00
Justin Larsen
5190b65b14 add error for empty test result set 2019-11-12 12:28:20 -07:00
Drew Banin
f7a92603c1 Merge pull request #1912 from fishtown-analytics/fix/logging-tweaks-2
Tweak rpc logging settings
2019-11-12 09:50:46 -05:00
Drew Banin
0c6bc3ec9b pr review 2019-11-11 20:05:07 -05:00
Jacob Beck
53af0233a7 add tests, make log default to True, make the uid context bigger 2019-11-11 12:13:37 -07:00
Drew Banin
9c9d261433 pep8 2019-11-11 12:08:22 -07:00
Drew Banin
f64ddd6fcb Tweak rpc logging settings
- respect logs_start parameter even when tasks have completed
- fix for incorrect hook index for on-run-end hooks
- clean up on-run hook log messages
- include SQL run in the context of a hook in the hook log messages
2019-11-11 12:08:22 -07:00
Jacob Beck
c26cf1977c Merge pull request #1909 from fishtown-analytics/feature/honor-threads-flag
add threads to commands (#1897)
2019-11-11 07:49:27 -07:00
Jacob Beck
abc766280a Merge pull request #1910 from fishtown-analytics/fix/changelog-update
update changelog to reflect 0.14.3 and 0.14.4
2019-11-08 17:28:22 -07:00
Jacob Beck
2ca4c1a344 Merge pull request #1885 from tjengel/add_auto_dist_style
Add auto dist style
2019-11-08 17:23:08 -07:00
Jacob Beck
58b2955cf1 Merge pull request #1851 from kconvey/kms-encryption
Add a configuration option `kms_key_name` to dbt_project.yml for BigQ…
2019-11-08 14:50:03 -07:00
Jacob Beck
a75a22d71a update changelog to reflect 0.14.4 2019-11-08 14:14:07 -07:00
tjengel
d245bcbc6e Merge remote-tracking branch 'upstream/dev/louisa-may-alcott' into add_auto_dist_style 2019-11-08 15:02:42 -06:00
Jacob Beck
6ab537db6e add threads to commands
Add tests accordingly
2019-11-08 13:32:22 -07:00
Jacob Beck
94009584ec Merge pull request #1907 from fishtown-analytics/fix/no-deepcopy-manifest
Don't deep copy the manifest unless we're in single threaded mode (#1904)
2019-11-08 12:30:50 -07:00
Jacob Beck
dc0e10f6a7 Don't deep copy the manifest unless we're in single threaded mode
Move environment variable handling into dbt.flags so it's easier to check
2019-11-08 12:03:19 -07:00
Jacob Beck
235ec1e3d1 Merge pull request #1901 from fishtown-analytics/fix/warehouse-to-snowflake-warehouse
rename "warehouse" config key to "snowflake_warehouse" (#1899)
2019-11-08 09:40:29 -07:00
Kurt Convey
83de6b1617 Get latest changes. Merge branch 'dev/louisa-may-alcott' of https://github.com/fishtown-analytics/dbt into kms-encryption 2019-11-08 09:40:14 -07:00
Drew Banin
1f1d10033f Merge pull request #1902 from fishtown-analytics/feature/run-sql-rpc-perf
fix for manifest reload logic
2019-11-08 11:10:40 -05:00
Jacob Beck
9f2a707b2b update snowflake connection to handle new closed state properly, rename "warehouse" config key to "snowflake_warehouse", tests 2019-11-08 07:29:53 -07:00
Drew Banin
08cb38b342 fix for manifest reload logic 2019-11-07 19:11:51 -05:00
Jacob Beck
6ac347813a Merge pull request #1900 from fishtown-analytics/feature/hook-rpc-logging
log hook info in json logs (#1890)
2019-11-07 15:26:07 -07:00
Jacob Beck
b9b4ce30e8 PR feedback 2019-11-07 15:01:19 -07:00
Jacob Beck
f1f14d237e log hook info in json logs 2019-11-07 13:36:42 -07:00
Kurt Convey
563dfc1371 Merge branch 'dev/louisa-may-alcott' of https://github.com/fishtown-analytics/dbt into kms-encryption 2019-11-07 12:47:17 -07:00
Connor McArthur
6198584f7d Merge branch 'dev/louisa-may-alcott' of github.com:fishtown-analytics/dbt into dev/louisa-may-alcott 2019-11-07 12:59:06 -05:00
Jacob Beck
0003d57163 Merge pull request #1898 from fishtown-analytics/fix/psycopg-binary-optout
pin psycopg2 (#1221)
2019-11-07 10:40:51 -07:00
Connor McArthur
5e7f718df3 Bump version: 0.15.0b2 → 0.15.0rc1 2019-11-07 10:27:33 -05:00
Jacob Beck
8bdc6877c0 pin psycopg2, add an env variable for opting out of binary
Set an env var so you can override the choice
2019-11-07 08:26:10 -07:00
Jacob Beck
d7eee61dd4 Merge pull request #1888 from fishtown-analytics/fix/column-quoting
Fix quoting on columns for seeds/incremental models (#1847)
2019-11-05 14:14:27 -07:00
Jacob Beck
e0d4e9392a re-case all the snowflake seed column names 2019-11-05 13:38:42 -07:00
Jacob Beck
7f8e198074 Fix quoting on columns for seeds/incremental models
Added tests
Also fixed quoting on column expansions
2019-11-05 09:22:06 -07:00
tjengel
e1b040c8f9 Add auto as diststyle 2019-11-04 21:56:34 -06:00
Jacob Beck
2d0f93850c Merge pull request #1886 from fishtown-analytics/feature/test-python-3.8
Test python 3.8
2019-11-04 15:23:49 -07:00
Jacob Beck
610c02abc4 add some arbitrary-ish classifiers (importantly: the versions we support) 2019-11-04 14:58:47 -07:00
Jacob Beck
452cc6e78a point this branch at the docker image it builds 2019-11-04 14:58:47 -07:00
Jacob Beck
642ebeef52 Add more versions to the dockerfile, make it build more modular-ly
use a PPA to get python versions instead of compiling them all
add python 3.8 tests to tox.ini and circleci.yml
update psycopg2 to 2.8.4 since they build binary wheels when possible now
2019-11-04 14:58:45 -07:00
Jacob Beck
31ca9a1041 Merge pull request #1881 from fishtown-analytics/fix/bigquery-case-sensitive
Fix bigquery case sensitive caching issue (#1810)
2019-11-04 13:41:25 -07:00
Jacob Beck
670c26bdbd use exists_ok, not_found_ok, and delete_contents on bigquery dataset operations 2019-11-04 12:23:07 -07:00
Jacob Beck
1bbc00c346 handle funky case bigquery models, add tests 2019-11-04 12:23:07 -07:00
Jacob Beck
ca4979099c so much for being a SQLAdapter 2019-11-04 12:23:07 -07:00
Jacob Beck
30cd27e5fc Fix BQ list_relations_without_caching + check_schema_exists 2019-11-04 12:23:07 -07:00
Jacob Beck
72d83f988e Make bigquery adapters SQLAdapters
Implement more things via macros
Refactor Relations vs InformationSchemas to handle BQ better
Fix a bug where bigquery cached uppercase schema names wrong
 - by using information_schema this just goes away :)
2019-11-04 12:23:07 -07:00
Jacob Beck
c4cd4fc42f Merge pull request #1864 from fishtown-analytics/feature/query-comments
inject query comments (#1643)
2019-11-04 12:22:26 -07:00
Jacob Beck
b56d93b909 if the comment macro is null/empty, no comments 2019-11-04 10:43:35 -07:00
Jacob Beck
ab9fcb4624 pin urllib3 to a version that snowflake supports 2019-11-04 09:01:34 -07:00
Jacob Beck
84d585c14c macro support, tests, add yet another mypy env for development 2019-11-04 09:01:06 -07:00
Jacob Beck
1873f4018d move macro stuff around 2019-11-04 09:01:06 -07:00
Jacob Beck
57f4221fc9 use block-style comments per PR feedback
Fix the tests
2019-11-04 09:01:06 -07:00
Jacob Beck
15ff08dc6f less query commenting 2019-11-04 09:01:06 -07:00
Jacob Beck
5b6586de3c Move query comments into the project config
Add special handling to 'dbt debug' for this behavior
Rework the dependencies/requirements for adapters since they now require more of a config object
tests...
2019-11-04 09:01:06 -07:00
Jacob Beck
ff158b8353 inject query comments
Make a fake "macro" that we parse specially with a single global context
Macro takes an argument (the node, may be none)
Users supply the text of the macro in their 'user_config' under a new 'query_comment'
No macros available
query generator is an attribute on the connection manager
 - has a thread-local comment str
 - when acquiring a connection, set the comment str
new 'connection_for' context manager: like connection_named, except also use the node to set the query string
Updated unit tests to account for query comments
Added a hacky, brittle integration test
  - log to a custom stream and read that
Trim down the "target" context value to use the opt-in connection_info
 - Make sure it contains a superset of the documented stuff
 - Make sure it does not contain any blacklisted items
Change some asserts to raise InternalExceptions because assert error messages in threads are useless
2019-11-04 09:01:06 -07:00
Jacob Beck
f985902a00 Merge pull request #1878 from fishtown-analytics/feature/run-operation-snapshot-rpc
add run-operation + snapshot to the RPC server (#1875)
2019-11-01 13:44:46 -06:00
Drew Banin
e022e73b0c Merge pull request #1876 from clausherther/add/quote-accepted-values
Adds "quote" parameter to "accepted_values" test
2019-11-01 14:43:39 -04:00
Jacob Beck
658be46831 pin dependencies of dependencies 2019-11-01 10:14:25 -06:00
Jacob Beck
06018ee3b5 PR feedback 2019-11-01 09:22:57 -06:00
Jacob Beck
1b4c76365c add run_operation + snapshot to the RPC server 2019-10-30 14:30:37 -06:00
Jacob Beck
8239f9c4e3 Merge pull request #1872 from fishtown-analytics/fix/bigquery-snowflake-unknown-relation-types
Fix bigquery snowflake unknown relation types
2019-10-30 11:03:40 -06:00
Claus Herther
239e6738a8 Adjusts yaml idents 2019-10-29 10:20:23 -07:00
Claus Herther
d605ff6fc2 Adds quote parameter to accepted_values schema test 2019-10-29 10:13:12 -07:00
Jacob Beck
76dc41b86e Merge pull request #1871 from fishtown-analytics/fix/log-rotation
rotate the file at 10mb instead of by date
2019-10-29 07:30:46 -06:00
Jacob Beck
2f85d83090 Allow for unknown relation types in more places
For bigquery:
  if a relation type is not known, use 'RelationType.External' in place of None
For snowflake/other sql adapters:
  if the relation type returned by list_relations_without_caching macro is not
     a known RelationType, treat it as External
2019-10-28 15:40:59 -06:00
Jacob Beck
1f34139df3 on bigquery, default to External instead of None when we do not recognize the table type 2019-10-28 15:14:53 -06:00
Jacob Beck
78115cf12a rotate the file at 10mb instead of by date 2019-10-28 14:59:01 -06:00
Jacob Beck
3a030ab74b Merge pull request #1867 from fishtown-analytics/fix/update-hologram-005
Updated hologram to handle parallel json_schema
2019-10-28 14:58:01 -06:00
Jacob Beck
ef3d63f30c Updated hologram to handle parallel json_schema 2019-10-28 08:13:30 -06:00
Kurt Convey
0d8e0cb1ca Merge branch 'kms-encryption' of https://github.com/kconvey/dbt into kms-encryption 2019-10-24 10:13:06 -06:00
Kurt Convey
a8ee33ff08 Sync latest version
Merge branch 'dev/louisa-may-alcott' of https://github.com/fishtown-analytics/dbt into kms-encryption
2019-10-24 10:11:36 -06:00
Kurt Convey
16f93ceee5 Sync latest version
Merge branch 'dev/louisa-may-alcott' of https://github.com/fishtown-analytics/dbt into kms-encryption
2019-10-24 10:09:33 -06:00
Jacob Beck
b3ef028361 Merge pull request #1859 from fishtown-analytics/feature/top-level-poll-timings
Feature: poll timing information, status -> state
2019-10-23 17:44:57 -06:00
Jacob Beck
672363bff4 Provide consistent timings between poll and ps results
Also rename status -> state in results
Add tests
2019-10-23 17:08:39 -06:00
Jacob Beck
a1d651aed2 Merge pull request #1855 from fishtown-analytics/fix/corrupted-task-state
Fix corrupted task state
2019-10-23 15:47:25 -06:00
Jacob Beck
5fa24d7880 Merge pull request #1856 from fishtown-analytics/fix/no-normcase-filenames
remove os.path.normcase calls
2019-10-23 15:37:59 -06:00
Jacob Beck
78d23c8243 Merge pull request #1858 from fishtown-analytics/fix/re-add-metadata
fix the `metadata` behavior for dbt-utils (#1857)
2019-10-23 15:37:50 -06:00
Jacob Beck
d43d49494d Merge pull request #1837 from fishtown-analytics/feature/dbt-deps-rpc
Add a "dbt deps" api call (#1834)
2019-10-23 15:21:46 -06:00
Jacob Beck
f4ca94f6d8 Add a special handler to support the dbt_utils._is_relation macro 2019-10-23 14:07:08 -06:00
Jacob Beck
0dedac6a2e remove normcase calls
add tests for shouting model names
2019-10-23 13:46:29 -06:00
Jacob Beck
f268ee9ae9 Make builtins just another kind of RemoteMethod
Move GC into its own object
Make parsing/parse failed into special callables and return those
2019-10-23 12:54:38 -06:00
Jacob Beck
57aa443bd7 fix an rpc-only deps bug, fix some stack traces going to stderr 2019-10-23 12:53:38 -06:00
Jacob Beck
45bc25d589 PR feedback
Fix a bug where failed deps were "lost" forever
Un-support 'cli_args' + 'deps', skip those tests
Add a new test suite for RPC tests
Move flaky RPC tests into new suite where they passes like they should
2019-10-23 09:07:00 -06:00
Drew Banin
c98da40873 Update CHANGELOG.md 2019-10-23 10:24:37 +01:00
Kurt Convey
bcd953490d Add a configuration option kms_key_name to dbt_project.yml for BigQuery
projects where a Cloud KMS key can be specified and used to encrypt
models. The key is specified to bigquery in the ddl OPTIONS.
2019-10-22 14:16:41 -06:00
Connor McArthur
3103442447 Merge pull request #1849 from fishtown-analytics/use-setuptools
Use setuptools instead of distutils.core
2019-10-22 12:37:30 -04:00
Connor McArthur
e08bc540cf use setuptools instead of distutil.core 2019-10-22 12:28:26 -04:00
Connor McArthur
d6682f092c Bump version: 0.15.0b1 → 0.15.0b2 2019-10-22 12:18:08 -04:00
Jacob Beck
bf5a6b84b4 fix the dbt deps cleanup
dbt clean does not clean the modules path
shutil.rmtree does :)
bump test timeouts
SIGHUP also reloads the config
2019-10-21 14:15:51 -06:00
Jacob Beck
97e0cdebbc Merge pull request #1845 from fishtown-analytics/fix/more-packaging-issues
More packaging fixes
2019-10-21 13:20:51 -06:00
Jacob Beck
6f64f51f8d normalize comparisons 2019-10-21 11:05:58 -06:00
Jacob Beck
f94a7a73c1 More packaging fixes
- Add both 'dbt' and 'dbt.*' to the namespace package search path
- set zip_safe=False, because I think zipping breaks macros
- Make CI tests use non-editable installs
- make tox use editable mode for explicit-* so development is still possible without rebuilding tox
- remove {toxinidir} stuff that breaks core installation
  - as of tox 1.6.1, requirements executes within toxinidir.
- fix paths to account for installing things
2019-10-21 09:52:29 -06:00
Jacob Beck
5cd8cff11d dbt deps now blocks incoming API calls, reloads the config, runs deps, and builds a new manifest 2019-10-20 08:40:33 -06:00
Jacob Beck
c293fd94d3 fix a couple tests 2019-10-18 17:38:52 -06:00
Jacob Beck
91f0e444c0 Add Kill/Fail task states + handling for them
Move setting real_task into set_args on the CLI handler
Move set_args into the handle() method, and out of task bootstrap
 - now it happens in the parent/handling process
 - now the parent process can know what real task was chosen
Make the cli handler report status properly
2019-10-18 17:38:52 -06:00
Jacob Beck
abdcdefb5f Add a "dbt deps" api call
Refactor deps
 - split it up
 - move most of it into dbt.deps
Add the idea of a remote method that does not require a manifest
 - it does still require config/args
 - set up the various reloading logic to not care about reloading these methods
Add dbt deps api call
 - also available as if it were from the cli
Add tests
2019-10-18 17:38:32 -06:00
Jacob Beck
6164733489 Merge pull request #1841 from fishtown-analytics/fix/namespace-package-builds
Fix namespace package builds
2019-10-18 15:59:20 -06:00
Jacob Beck
f9a0ccf318 Fix namespace package builds
use find_namespace_packages() instead of find_packages()
2019-10-18 15:18:02 -06:00
Connor McArthur
bb82093ffc Bump version: 0.15.0a1 → 0.15.0b1 2019-10-18 14:00:59 -04:00
Drew Banin
d3139a531c Merge pull request #1840 from fishtown-analytics/fix/logging-tweaks
change info log lines to debug logs
2019-10-18 13:53:12 -04:00
Drew Banin
3a40cf2373 change info log lines to debug logs 2019-10-18 13:20:07 -04:00
Jacob Beck
a7b0d05d3c Merge pull request #1839 from fishtown-analytics/fix/snowflake-query-bad-errors
Handle a number of bad failure-path behaviors (#1807)
2019-10-17 14:42:37 -04:00
Jacob Beck
ae796a8497 Handle a number of bad failure-path behaviors
- When a connection is missing, raise a special exception
   - instead of RuntimeError, so we catch it better
- Be graceful if a connection does not exist during nice_connection_name
- Handle the case where exceptions caught by the Snowflake exception handler do not have a 'msg' attr
- Re-raise exceptions in the adapter exception handlers "from" the originating error
2019-10-17 07:42:00 -06:00
Jacob Beck
6d44caa4e3 Merge pull request #1836 from fishtown-analytics/feature/partial-parse-upgrades
Partial parse updates (#1835)
2019-10-16 09:08:45 -04:00
Jacob Beck
f32519b156 Partial parse upates
Use the user's profile config and the CLI arguments to determine whether to try partial parsing
  - CLI wins
Check the dbt version number as part of deciding if a cached result is ok
2019-10-15 18:25:13 -06:00
Jacob Beck
d5824d9238 Merge pull request #1833 from fishtown-analytics/fix/postgres-external-materialized-views
postgres: gracefully handle materialized views (#1698)
2019-10-15 19:14:32 -04:00
Drew Banin
e83aab202b Merge pull request #1682 from fishtown-analytics/fix/minimize-incremental-downtime
(#525) drop existing relation at end of full-refresh incremental build
2019-10-15 14:44:43 -04:00
Drew Banin
00a22e18cc Merge pull request #1795 from fishtown-analytics/fix/bq-catalog-query
(#1576) use the information schema on BigQuery
2019-10-15 14:44:18 -04:00
Jacob Beck
fee9382c7f Properly ignore materialized views
add_link now creates a relation if it doesn't exist (as "external")
add/fix tests
2019-10-15 11:58:23 -06:00
Drew Banin
f757a08a99 improve the BigQuery Relations understanding of the information schema 2019-10-15 12:28:09 -04:00
Drew Banin
414716b841 pr feedback 2019-10-15 11:32:27 -04:00
Drew Banin
50f4f8a5b0 pr feedback 2019-10-15 11:31:09 -04:00
Jacob Beck
73d0308e35 Merge pull request #1831 from fishtown-analytics/feature/avoid-osx-flaky-rpc
Fix for issues with some Mojave installs
2019-10-15 09:43:18 -04:00
Drew Banin
43b8293a07 pep8; code cleanup 2019-10-14 23:13:09 -04:00
Drew Banin
15c26192e8 Include Location field in catalog from the SCHEMATA table 2019-10-14 23:07:42 -04:00
Drew Banin
95a0587499 handle changing partition/cluster configs on BQ 2019-10-14 22:48:52 -04:00
Drew Banin
0f1693a9d7 (#525) drop existing relation at end of full-refresh incremental build 2019-10-14 21:54:22 -04:00
Drew Banin
4c624d0a85 (#1576) use the information schema on BigQuery 2019-10-14 21:42:25 -04:00
Jacob Beck
6287d6d30b Merge pull request #1830 from fishtown-analytics/feature/improve-rpc-compile-performance
Feature/improve rpc compile performance (#1824)
2019-10-14 18:59:53 -04:00
Jacob Beck
c19260ec4e Speculative fix for issues with some Mojave installs (python 3.7-related?)
Use an internal multiprocessing.context in dbt.flags for multiprocessing things
 - On osx, force the use of "spawn" to handle https://bugs.python.org/issue33725
 - Bring _nt_setup back from the dead as _spawn_setup
2019-10-14 16:42:09 -06:00
Jacob Beck
43daea05c1 remove inscrutable __getattr__ override 2019-10-14 16:28:28 -06:00
Jacob Beck
75916754a6 add register_adapter to dbt debug 2019-10-14 13:12:28 -06:00
Jacob Beck
14d8683135 Avoid deepcopying the manifest 1x per task per sighup 2019-10-14 13:02:13 -06:00
Jacob Beck
61f8e6d4a1 fix unit tests 2019-10-14 12:05:12 -06:00
Jacob Beck
b658f879f9 fix field serialization for hologram+mypy 2019-10-14 12:05:12 -06:00
Jacob Beck
4aa4295508 Make mypy totally happy
Some circular import cleanups
remove is_type function, just compare to resource_type
Add type checking for dbt deps
2019-10-14 12:05:12 -06:00
Jacob Beck
75c8feaeb9 Make ManifestMetadata a first-class object 2019-10-14 12:05:12 -06:00
Jacob Beck
66ff79dfbd Refactor RPC tests 2019-10-14 12:05:12 -06:00
Jacob Beck
7206c202bf Manifests are only parsed, not compiled at SIGHUP/startup time
The _sql tasks now compile any ref'ed CTE chains at RPC call time

Give RPC tasks their own folder
 - task/rpc_server -> task/rpc/server
 - task/remote -> task/rpc/{project_commands,sql_commands,base}

Linker enhancements:
  - Expose subset graph building so multiple methods can use it
  - Expose a way for the linker to provide an interable of the ephemeral ancestors of a node
     - it's guaranteed to be ordered (so nested CTEs behave)
2019-10-14 11:37:40 -06:00
Jacob Beck
773c979955 Refactor internal RPC logic to support just getting parsed manifests
RemoteCallableResult -> RPCResult
RemoteCallable -> RemoteMethod
 - move some things from RPCTask -> RemoteMethod
   - recursive_subclasses classmethod
things in core/dbt/rpc now are all based on RemoteMethods, not RPCTasks
2019-10-14 11:37:37 -06:00
Jacob Beck
ef16a99f88 Refactors for mypy:
initial refactoring of adapter factory stuff
Move HasCredentials protocol into connection contract and use that in the base connection
2019-10-14 11:37:33 -06:00
Jacob Beck
d86092ae78 loader.GraphLoader -> parser.manifest.ManifestLoader
Create new helper function dbt.perf_utils.get_full_manifest
Update task.runnable accordingly
Update RPC server accordingly
2019-10-14 11:37:30 -06:00
Jacob Beck
6c87bed66b Add manifest.expect method 2019-10-14 11:37:26 -06:00
Drew Banin
2d100c33b8 Merge pull request #1823 from heisencoder/fix/printer-log-level
update logging levels in printer.py to match the intent
2019-10-11 15:39:59 -04:00
Jacob Beck
81b755ff0f Merge pull request #1828 from fishtown-analytics/feature/task-tags
Add task tags parameter (#1822)
2019-10-11 14:42:09 -04:00
Jacob Beck
fb9747b6be Add task tags parameter
Create base class for parameter types
 - include arbitrary "task_tags" parameter
 - include "timeout" so we can extract/document full/valid json schemas
   - this includes some extra-fiddly behavior around numbers
Add output to ps and poll results (including errors)
Fix a number of type annotation issues along the way
2019-10-11 11:51:44 -06:00
Drew Banin
cd1eccf5f4 Merge pull request #1826 from fishtown-analytics/fix/bq-result-type-inference
fix for incorrect type inference on bigquery
2019-10-10 22:39:49 -04:00
Drew Banin
3b69d310dd (#1825) fix for incorrect type inference on bigquery 2019-10-10 16:26:15 -04:00
Matt Ball
dc2ebf292b update logging levels in printer.py to match the intent
See https://github.com/fishtown-analytics/dbt/issues/1818
2019-10-10 10:28:03 -06:00
Jacob Beck
2815b33b0a Merge pull request #1821 from fishtown-analytics/fix/json-compatible-regexes
jsonschema+deps fixes
2019-10-10 12:08:37 -04:00
Jacob Beck
9819c3a01e Merge pull request #1806 from fishtown-analytics/feature/transactional-json-logging
Transactional json logging
2019-10-10 09:22:40 -04:00
Jacob Beck
cf619eacc5 Merge pull request #1812 from fishtown-analytics/feature/cli-args-rpc
Add cli execution support (#1811)
2019-10-10 09:22:01 -04:00
Jacob Beck
410086df6b jsonschema+deps fixes
Fix the offending regex to use js-compatible syntax
Fix the werkzeug pin
Pin hologram to 0.0.4
Pin dataclasses out of an excess of caution
2019-10-10 06:58:42 -06:00
Jacob Beck
2b9c5ae7d4 PR feedback
s/model/node/
include resource_type
2019-10-10 05:22:36 -06:00
Jacob Beck
eb921e9281 Merge pull request #1813 from fishtown-analytics/fix/custom-snapshot-strategies
Fix custom snapshot strategy support (#1794)
2019-10-10 07:11:06 -04:00
Jacob Beck
6d2aca182c Merge pull request #1816 from fishtown-analytics/feature/test-nodes-include-name-kwargs
add test metadata for schema tests, add test tests (#1154)
2019-10-10 07:10:43 -04:00
Jacob Beck
a2691d5dad add test metadata for schema tests, add test tests 2019-10-08 14:01:38 -06:00
Jacob Beck
c1b7c4c1b7 Merge pull request #1814 from fishtown-analytics/feature/upgrade-deps
Upgrade dependencies
2019-10-08 13:19:04 -04:00
Jacob Beck
66ffcf1e1b Upgrade dependencies
Networkx minimum version set to 2.3, dropped 1.x support
 - removed a backwards compatibility fix
Give colorama a range of supported versions, preserving the old minimum
Give sqlparse a range of supported versions, preserving the old minimum
Bump werkzeug to 0.15.x, 0.16 also works so allow that
 - when 1.0 releases it looks like it will work as well, but wait on that
 - drop 0.14.x support
Set an upper bound on snowflake-connector-python of 2.0.x
2019-10-08 10:43:35 -06:00
Jacob Beck
e2d9eb4772 Fix custom snapshot strategy support, add tests 2019-10-08 09:52:20 -06:00
Jacob Beck
0bbaa5be21 deal with MRO issues by making another intermediate class 2019-10-08 08:01:37 -06:00
Drew Banin
54d0230d1b Merge pull request #1809 from captainEli/fix/source-arguments
Improve Error Messaging for Invalid Source Arguments
2019-10-07 21:24:33 -04:00
Jacob Beck
4018e550fe PR feedback
Add node count/total node to log lines
Make model status structured
2019-10-07 20:14:23 -04:00
Jacob Beck
21f2e01921 Add cli execution support 2019-10-07 19:50:12 -04:00
Eli Kastelein
34cb9c7186 Reduce line length for pep8 2019-10-07 16:06:09 -07:00
Jacob Beck
52ac98ea1c Address feedback, appease flake8/mypy/unit tests 2019-10-07 11:58:16 -04:00
Jacob Beck
aa254b350b Transactional logging 2019-10-07 11:58:14 -04:00
Jacob Beck
8194fd4d13 Merge pull request #1801 from fishtown-analytics/feature/add-docs-generate-rpc
docs generate RPC task (#1781)
2019-10-07 11:56:51 -04:00
Jacob Beck
2031e232ca PR feedback, also remove an extra manifest-building from "dbt docs generate" 2019-10-07 10:15:53 -04:00
Eli Kastelein
b71e51e30f Fix typo in error message 2019-10-04 20:59:37 -07:00
Eli Kastelein
a1d93f92cd refactor to make more similar to RefResolver 2019-10-04 20:38:59 -07:00
Eli Kastelein
3a5c0ebc78 SourceResolver call to use *args 2019-10-04 20:31:03 -07:00
Jacob Beck
5061397e66 docs generate RPC task 2019-10-02 17:38:43 -04:00
Jacob Beck
31e085b7df Merge pull request #1780 from fishtown-analytics/refactor/relations-as-jsonschemamixins
Remove APIObject (#1762)
2019-10-02 11:10:41 -06:00
Jacob Beck
13a7d96d84 Merge pull request #1798 from fishtown-analytics/feature/rename-rpc-methods
rename RPC methods (#1779)
2019-10-02 09:01:07 -06:00
Jacob Beck
3765435ee6 Merge pull request #1791 from fishtown-analytics/feature/log-format-parameter
add log-format flag (#1237)
2019-10-02 09:00:33 -06:00
Jacob Beck
e61f08b981 rename RPC methods 2019-10-02 08:28:04 -06:00
Jacob Beck
1fbd82d19c Merge pull request #1793 from fishtown-analytics/feature/merge-0.14.3-louisa-may-alcott
Feature/merge 0.14.3 louisa may alcott
2019-10-02 07:57:26 -06:00
Jacob Beck
dfb4b3a2c8 PR feedback
add typing extensions module to setup.py
Update changelog
2019-10-02 07:13:16 -06:00
Drew Banin
5f30d5ddaf Merge pull request #1787 from darrenhaken/dev/louisa-may-alcott
Add trailing comma when creating BQ options to each list item
2019-10-01 10:39:52 -04:00
Jeremy Cohen
aceda5ec62 Merge pull request #1784 from fishtown-analytics/feature/external-source-config
Feature/external source config
2019-09-27 16:51:47 -04:00
Jacob Beck
626e642d90 PR feedback 2019-09-27 14:50:47 -06:00
Jacob Beck
812c549156 Merge pull request #1788 from fishtown-analytics/feature/warehouse-model-config
Add runtime per-model warehouse config on snowflake models (#1358)
2019-09-27 14:13:05 -06:00
Jacob Beck
34ca230473 Merge branch 'dev/0.14.3' into dev/louisa-may-alcott 2019-09-27 13:53:36 -06:00
Jacob Beck
c211d0e436 Merge pull request #1792 from fishtown-analytics/fix/run-operation-flat-graph
Make run-operation a ManifestTask
2019-09-27 13:52:06 -06:00
Jacob Beck
52f62430f4 remove extra transaction logic, not needed 2019-09-27 13:45:04 -06:00
Jacob Beck
6fdac5020f Call build_flat_graph everywhere that wants it
Make run-operation a ManifestTask and build a flat graph so it has the "graph" context member
Make sure the RPC server task calls build_flat_graph in __init__
2019-09-27 13:26:05 -06:00
Jacob Beck
dca02111be add log-format flag 2019-09-27 11:22:26 -06:00
Jeremy Cohen
fcd86e3298 Address failing docs int tests 2019-09-27 13:22:07 -04:00
Jacob Beck
286753b464 Merge pull request #1783 from fishtown-analytics/feature/seeds-path-resolution
Fix the seed path definition
2019-09-27 11:06:35 -06:00
Jacob Beck
a227b31162 Add runtime per-model warehouse config on snowflake models
Add warehouse config to snowflake
Add concept of adapter-level pre/post model hooks
Use those hooks to optionally set a warehouse on a per-model basis
Added some integration tests
 - one tests that the warehouse is applied by setting a bad one and failing
 - one that tests that overriding the warehouse to a good one is ok
2019-09-27 10:45:23 -06:00
Jacob Beck
d246bab0eb Fix seeds in dependencies
Modified a test to cover the bug
Update the FilePath definition
Add a new attribute to seeds (seed_file_path)
Test fixes
Added --no-version-check flag to dbt seed
2019-09-27 10:30:45 -06:00
Jacob Beck
78a199f946 Merge pull request #1785 from fishtown-analytics/feature/hologram-use-restrict-fields
Use the restrict hologram metadata field instead of multiple enum types
2019-09-27 09:50:32 -06:00
Jeremy Cohen
54009a05cf Merge branch 'dev/louisa-may-alcott' of github.com:fishtown-analytics/dbt into feature/external-source-config 2019-09-27 11:13:09 -04:00
Jacob Beck
eb9bfcda4a Convert Relation types to hologram.JsonSchemaMixin
Fix a lot of mypy things, add a number of adapter-ish modules to it
Split relations and columns into separate files
split context.common into base + common
 - base is all that's required for the config renderer
Move Credentials into connection contracts since that's what they really are
Removed model_name/table_name -> consolidated to identifier
 - I hope I did not break seeds, which claimed to care about render(False)
Unify shared 'external' relation type with bigquery's own
hack workarounds for some import cycles with plugin registration and config p
arsing
Assorted backwards compatibility fixes around types, deep_merge vs shallow merge
Remove APIObject
2019-09-27 09:01:18 -06:00
Jacob Beck
91c51c88cd Use the restrict hologram metadata field instead of multiple types
mypy stuff
Use restrict
node types
test fixes
contract tweaks
2019-09-27 09:00:42 -06:00
Jacob Beck
2a69a371fd fix tests broken by the merge 2019-09-27 08:54:28 -06:00
Jacob Beck
2e6398d5b5 fix bad merge 2019-09-27 08:44:37 -06:00
Jacob Beck
8b622c964f Merge branch 'dev/0.14.3' into dev/louisa-may-alcott 2019-09-27 08:37:14 -06:00
Darren Haken
fd8049c997 Add trailing comma when creating BQ options to each list item
- This is a bug in the existing code, BQ needs commas between option items.
Discovered it when creating a temp table using dbt_utils
2019-09-27 15:36:21 +01:00
Jeremy Cohen
2ab21ed710 Address failing tests 2019-09-26 17:46:35 -04:00
Jeremy Cohen
01be413927 Optional data_type. More rigorous subdataclass 2019-09-26 17:45:59 -04:00
Jeremy Cohen
b93563bfeb Create AdditionalPropertiesAllowed subdataclass 2019-09-26 17:45:47 -04:00
Jeremy Cohen
c68de1066d Support external table config 2019-09-26 17:45:38 -04:00
Jacob Beck
23484b18b7 Merge pull request #1776 from fishtown-analytics/feature/partition-filters-sources
Add filter field to source table definitions (#1495)
2019-09-26 07:56:18 -06:00
Drew Banin
aea03ebba4 Update CHANGELOG.md 2019-09-25 20:49:54 -04:00
Jacob Beck
0522535a15 move filter into the freshness settings 2019-09-24 13:49:56 -06:00
Jacob Beck
2799a8c34d Mypy fixes
Added some type annotations
Clean up some mypy issues around the "available" decorators
2019-09-24 13:41:27 -06:00
Jacob Beck
d22f3653b7 add filter field 2019-09-24 13:41:27 -06:00
Jacob Beck
509e0e8363 Merge pull request #1777 from fishtown-analytics/fix/cleanup-dbt-help
Address all the whitespace issues (#1767)
2019-09-24 09:43:52 -06:00
Jacob Beck
1cce96f4ee Merge pull request #1775 from fishtown-analytics/feature/validate-targets-programmatically
Add staticmethod on the debug task to validate targets
2019-09-24 08:14:28 -06:00
Jacob Beck
7981d8ed8c Address all the whitespace issues 2019-09-23 14:59:46 -06:00
Jacob Beck
b78fd60343 Add validation feature, add unit + integration tests for various aspects of dbt debug 2019-09-23 09:56:06 -06:00
Jacob Beck
f9c8442260 Merge pull request #1770 from fishtown-analytics/feature/expose-cache-to-materializations
Expose the cache in dbt's rendering contexts (#1683)
2019-09-23 09:54:28 -06:00
Jacob Beck
f84cbc3091 Update core/dbt/deprecations.py
Include the actual docs link!

Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2019-09-23 09:18:26 -06:00
Jacob Beck
f6406c95aa Merge pull request #1774 from fishtown-analytics/fix/include-database-comparing-catalog
Include the database when deciding if two tables are the same (#1708)
2019-09-20 14:16:42 -06:00
Jacob Beck
e83f47b82e integration tests for the deprecation 2019-09-20 14:16:00 -06:00
Jacob Beck
145e0b228e Add support for a materialization return value
Materializations now return a list of relations they added
 - those get added to the cache
Updated existing materializations
Added backwards compatibility support + a deprecation warning
2019-09-20 14:06:14 -06:00
Jacob Beck
ca1c84c9d4 expose the cache to macros
make new cache manipulation methods
mark the methods available to the sql context
move cache manipulation into appropriate macros and methods
update the changelog
fix some type checking
2019-09-20 14:05:39 -06:00
Jacob Beck
a9bb1aabbb Include the database when deciding if two tables are the same in catalog generation
Convert catalog intermediate structure into something more useful
Make comparing manifests to catalogs faster by generating an explicit identifier to id mapping
Make the identifier to unique ID mapping include databases
Convert catalog to use dataclasses/hologram types
Fix unit tests to test what we actually care about
No changes to integration tests means no need to change dbt docs, hooray
2019-09-20 10:42:23 -06:00
Drew Banin
e31a9af5be Merge pull request #1747 from Carolus-Holman/dev/louisa-may-alcott
Added Copy Grants to Snowflake Adapter
2019-09-18 09:51:42 -04:00
Drew Banin
c941434510 Merge pull request #1769 from fishtown-analytics/fix/snapshot-cli-output
Standardize snapshot CLI output
2019-09-18 09:40:59 -04:00
Jacob Beck
7a305ca456 Merge pull request #1759 from fishtown-analytics/feature/snapshot-config-block
snapshot config blocks (#1613)
2019-09-18 07:24:50 -06:00
Drew Banin
e2e509b8a4 (#1768) standardize snapshot cli output 2019-09-17 22:37:14 -04:00
Jacob Beck
92d08a5682 run hooks in snapshots, add tests 2019-09-17 12:59:41 -06:00
Jacob Beck
0fa4523450 Merge pull request #1761 from fishtown-analytics/fix/fix-all-test-names
Rename integration test methods to conform to standard
2019-09-17 09:41:23 -06:00
Jacob Beck
dbcb9c562f make all tests have the adapter in the name
enforce that restriction instead of warning
also shut up a name-related warning from unittest
2019-09-17 09:13:03 -06:00
Jacob Beck
aff1d1d9b3 snapshot config blocks
Snapshots can now be configured via {{ config(...) }}
Added tests that exercise that
2019-09-17 08:41:31 -06:00
carolush@slalom.com
226247d858 (#1744) add copy grants option for Snowflake 2019-09-17 08:24:59 -05:00
Jacob Beck
f9bc7c56e5 Merge pull request #1753 from fishtown-analytics/fix/merge-0.14.3
Fix/merge 0.14.3
2019-09-16 11:03:20 -06:00
Jacob Beck
4910d6cc9d Merge pull request #1752 from fishtown-analytics/fix/log-cli-errors
Fix: log cli errors
2019-09-16 10:59:15 -06:00
Jacob Beck
036fc07500 with main() logging fixed, this now causes double-logging, so remove it 2019-09-16 10:29:31 -06:00
Jacob Beck
189c1c0e0c Merge branch 'dev/0.14.3' into dev/louisa-may-alcott 2019-09-16 10:18:29 -06:00
Jacob Beck
017f4175f8 Merge pull request #1750 from fishtown-analytics/fix/pregenerate-flat-graph
Fix flat graph generation bug (#1740)
2019-09-16 10:14:50 -06:00
Jacob Beck
8cd93f51c2 handle an annoying bug where dbt silently failed sometimes 2019-09-16 10:06:37 -06:00
Jacob Beck
cbd824ceda Merge pull request #1751 from fishtown-analytics/fix/fail-on-parse-errors
Fail on parse errors (#1493)
2019-09-16 09:49:35 -06:00
Jacob Beck
1d91890c9f no more warnings for parse failures 2019-09-13 14:55:37 -06:00
Jacob Beck
726004bb3f Make flat graph an attribute and build it during runtime initialization 2019-09-13 13:47:45 -06:00
Jacob Beck
cd5d39dbce Merge pull request #1749 from fishtown-analytics/fix/close-connections-properly
force-cleanup all adapter connections before exiting handle_and_check (#1271)
2019-09-13 13:38:14 -06:00
Jacob Beck
29a14cf0c3 when parsing hits an exception, ensure we actually log it 2019-09-13 12:12:26 -06:00
Jacob Beck
b83b82849f force-cleanup all adapter connections before exiting handle_and_check 2019-09-13 11:18:50 -06:00
Connor McArthur
c4892d9f33 Merge pull request #1745 from fishtown-analytics/dev/0.14.2
dbt 0.14.2
2019-09-13 12:55:12 -04:00
Jacob Beck
3ebe95d669 Merge pull request #1748 from fishtown-analytics/feature/context-blacklist
add disallowed project names list to dbt projects (#1696)
2019-09-13 10:44:39 -06:00
Jacob Beck
cfb55efcf7 add disallowed project names list to dbt projects
Raise on disallowed project names
Set defaults in project contract for testability
Add tests
2019-09-13 08:35:37 -06:00
Jacob Beck
5283002951 Merge pull request #1735 from fishtown-analytics/feature/async-rpc
Feature: async rpc (#1706)
2019-09-13 07:58:48 -06:00
Connor McArthur
6e5456d14c update date in CHANGELOG 2019-09-13 09:49:23 -04:00
Connor McArthur
5e706816e4 undo accidental werkzeug bumpversion, fix RELEASE instructions 2019-09-13 09:48:51 -04:00
Jacob Beck
2cf2c82899 add early error on invalid parameters via inspect shenanigans 2019-09-12 14:17:11 -06:00
Jacob Beck
9ea6e8d0d0 flake8 2019-09-12 14:17:11 -06:00
Jacob Beck
0563061b25 More flexible json.dumps in rpc
If the rpc server gets a non-serializable response component, call str() on it
2019-09-12 14:17:11 -06:00
Jacob Beck
5937c20ed1 bigquery error handling: log strings, not Exception objects 2019-09-12 14:17:11 -06:00
Jacob Beck
58fcbcf7a5 Improve logging/error handling
Pass werkzeug logging through
suppress all non-dbt logs in the RPC response
filter out all non-dbt/werkzeug logs below WARNING (dbt and dbt rpc)
lower all remaining non-dbt/werkzeug logs to DEBUG (dbt and dbt rpc)
 - attach old_level in extras if we do
Handle some more protocol exceptions gracefully
2019-09-12 14:17:11 -06:00
Jacob Beck
34980655b2 Handle started/ended correctly 2019-09-12 14:17:11 -06:00
Jacob Beck
d51ebac71b remove windows support from RPC
Also disable RPC tests on windows
managing windows idiosyncrasies is just too much
2019-09-12 14:17:11 -06:00
Jacob Beck
5e52c64153 gc, tests 2019-09-12 14:17:11 -06:00
Jacob Beck
4cb4318426 Implement async RPC (no gc of tasks) 2019-09-12 14:17:11 -06:00
Jacob Beck
d23b1aa755 Clean up return types and return values for async rpc work + mypy
Add __len__, __iter__, and __getitem__ to ExecutionResult/FreshnessResult
  - tests and some internal callers are now happy without modifying them
2019-09-12 14:17:11 -06:00
Connor McArthur
7151ac1f81 Bump version: 0.14.1 → 0.14.2 2019-09-12 15:58:43 -04:00
Drew Banin
ee921cc0ca Update CHANGELOG.md 2019-09-12 15:01:05 -04:00
Drew Banin
5dc776f857 Merge pull request #1743 from Carolus-Holman/feature/snowflake-secure-view-4
Feature/snowflake secure view 4
2019-09-12 10:29:52 -04:00
carolush@slalom.com
c4288b1e82 Added Support for Snowflake Secure Views. 2019-09-11 14:21:57 -05:00
carolush@slalom.com
5b29c19afe Revert "Adding support for Snowflake Secure Views - {{ config(materialized='view',secure=true) }} in Model"
This reverts commit ac1b906888.
2019-09-11 09:05:30 -05:00
carolush@slalom.com
ac1b906888 Adding support for Snowflake Secure Views - {{ config(materialized='view',secure=true) }} in Model 2019-09-11 08:48:52 -05:00
Jacob Beck
3e09319f8c Merge pull request #1734 from fishtown-analytics/fix/0.14.2-disable-cache-logging
Fix/0.14.2 disable cache logging (#1725)
2019-09-10 12:31:50 -06:00
Jacob Beck
1fab0aba7e Merge pull request #1732 from fishtown-analytics/fix/dbt-cache-logging-performance
disable dbt cache logging by default (#1725)
2019-09-10 11:50:02 -06:00
Jacob Beck
2d52eda730 disable dbt cache logging 2019-09-10 11:15:43 -06:00
Jacob Beck
6f51e0de13 fix flag setting 2019-09-10 11:08:22 -06:00
Drew Banin
491ad692d7 Merge pull request #1729 from fishtown-analytics/fix/0141-regression-snapshot-freshness
Fix/0141 regression snapshot freshness
2019-09-10 13:03:49 -04:00
Drew Banin
26f573fdc8 pr feedback 2019-09-10 11:34:52 -04:00
Drew Banin
689a0ea4cc (#1728) exclude ephemeral models from freshness task 2019-09-10 11:15:58 -04:00
Jacob Beck
5c6f4ff637 disable dbt cache logging 2019-09-10 09:04:31 -06:00
Jacob Beck
693574212d Merge pull request #1727 from fishtown-analytics/merge-0.14.1-into-lma
Merge dev/0.14.1 into dev/louisa-may-alcott
2019-09-05 15:24:17 -06:00
Jacob Beck
cd0e119737 re-order update_parsed_node so it makes logical sense
database -> schema -> alias instead of schema -> alias -> database
2019-09-05 14:52:40 -06:00
Jacob Beck
64193b5cc9 pr feedback 2019-09-05 14:49:24 -06:00
Jacob Beck
4a4fdb1452 Merge branch 'dev/0.14.1' into dev/louisa-may-alcott 2019-09-05 14:06:28 -06:00
Drew Banin
23bfc67d62 Merge pull request #1726 from fishtown-analytics/fix/0.14.2-docs-updates
(#1724) bump docs site for 0.14.2
2019-09-05 16:04:05 -04:00
Drew Banin
e0f725363c (#1724) bump docs site for 0.14.2 2019-09-05 14:36:39 -04:00
Drew Banin
19f052e016 Merge pull request #1719 from fishtown-analytics/fix/dont-rerender-in-statements
(#1717) fix for re-rendering in statement calls
2019-09-04 09:22:33 -04:00
Connor McArthur
141bdd6f96 Bump version: 0.14.1rc2 → 0.14.1 2019-09-03 19:52:54 -04:00
Drew Banin
d1a49d3128 (#1717) fix for re-rendering in statement calls 2019-09-03 18:55:10 -04:00
Jacob Beck
48bd3cac39 Merge pull request #1720 from fishtown-analytics/refactor/rpc-pre-async
refactor dbt rpc in prep for async
2019-09-03 15:01:30 -06:00
Jacob Beck
db3c58ddb1 point dbt to a hologram v0.0.3, which handles List/List[str] encoding and decoding 2019-09-03 14:26:05 -06:00
Jacob Beck
279e4d5976 oops, we never use these (broken) methods 2019-09-03 09:20:40 -06:00
Jacob Beck
15840bd1c7 refactor dbt rpc in prep for async
Split rpc.py -> rpc/...
Move async entries in node_runners.py -> rpc/node_runners.py
Move RemoteCallable -> rpc/task.py
Move all RPC tasks -> task/remote.py
Clean up results for kill into a more coherent hologram type structure
Increased some test timeouts to improve some flaky tests around start-up compilation
Add many more type annotations for mypy
2019-09-03 08:49:27 -06:00
Jacob Beck
5c3874a392 Merge pull request #1715 from fishtown-analytics/feature/logbook-structured-rpc-logging
Feature/logbook structured rpc logging (#1703, #1704)
2019-08-30 12:35:46 -06:00
Jacob Beck
43c5c011d8 PR feedback 2019-08-30 12:09:19 -06:00
Jacob Beck
fb12595bdd fix missing exception export 2019-08-30 11:36:24 -06:00
Jacob Beck
3163f090dd Allow an initial disable to prevent a path from ever being set 2019-08-30 11:10:56 -06:00
Jacob Beck
e47b77d50a refactor log management a bit to better support hooking into it 2019-08-30 10:56:19 -06:00
Jacob Beck
544dbfd746 Replace logging with logbook
Log rpc as json, preserve existing log behavior
Force werkzeug logs into logbook handling
Make rpc a package and set up mypy checking on it
fix a lot of logging-related bugs
Provide a log manager to toggle various log functionalities
add disabled flag to file handler for "dbt debug" and friends
Test changes to support logbook
lots of fiddly process/thread handling
2019-08-30 08:39:29 -06:00
Drew Banin
54f548eec8 Update CHANGELOG.md 2019-08-29 17:01:23 -04:00
Connor McArthur
786791670e Bump version: 0.14.1rc1 → 0.14.1rc2 2019-08-28 18:08:14 -04:00
Drew Banin
ce58da82c5 Merge pull request #1709 from fishtown-analytics/fix/simplify-snapshot-check-logic
make snapshot check strategy simpler and still correct
2019-08-28 15:39:50 -04:00
Drew Banin
3437b0f2b9 Update CHANGELOG.md 2019-08-28 14:43:21 -04:00
Drew Banin
0970285956 make snapshot check strategy simpler and still correct 2019-08-28 11:30:27 -04:00
Drew Banin
c0028587dd Merge pull request #1701 from fishtown-analytics/feature/expose-config-in-generation-macros
add configs to ParsedNodes before generating a schema/alias
2019-08-27 14:33:35 -04:00
Drew Banin
7fafa2adeb Update CHANGELOG.md 2019-08-27 13:10:35 -04:00
Jacob Beck
513a3050c7 tests 2019-08-27 10:57:59 -06:00
Jacob Beck
57a2aae83f log compiles to an array of log messages, disply it in status 2019-08-27 10:57:51 -06:00
Jacob Beck
c621e7d381 Fix some connection-related bugs
- fix a bug where failed connections caused an AttributeError
 - fix an issue where the rpc server and its child processes secretly shared mutable state
2019-08-27 10:54:21 -06:00
Jacob Beck
fe48478993 Merge pull request #1699 from fishtown-analytics/feature/sighup-reload-manifest
reload RPC server manifest on SIGHUP (#1684)
2019-08-27 07:05:23 -06:00
Drew Banin
d1cc5deaac add tests for accessing config properties in generate macros 2019-08-26 22:54:17 -04:00
Drew Banin
952b1fc61b add configs to ParsedNodes before generating a schema/alias 2019-08-26 22:10:51 -04:00
Jacob Beck
bd63aac8b3 PR feedback 2019-08-26 15:17:45 -06:00
Jacob Beck
9581f39186 Trigger rpc server reloads with sighup
From the sighup until completion, the sever enters an error state where only builtin commands work
 - management of tasks should keep functioning properly
Add new builtin command 'status'
 - returns 'pid', 'status' enum, 'timestamp'
 - if the status is 'error', an 'error' field is populated
    - just a Dict[str, Any] with, only 'message' for now
2019-08-26 10:01:47 -06:00
Jacob Beck
6649840b46 make mypy happy with deprecations
Requires git-only hologram branch
TODO: update hologram version + do another release
2019-08-26 09:53:04 -06:00
Drew Banin
1b03db6ab5 Update CHANGELOG.md 2019-08-26 09:55:45 -04:00
Connor McArthur
8a19ba4862 Bump version: 0.14.1a1 → 0.14.1rc1 2019-08-22 12:02:16 -04:00
Jacob Beck
eb8bce4e22 Merge branch 'dev/0.14.1' into dev/louisa-may-alcott 2019-08-21 16:04:19 -06:00
Drew Banin
f73d561557 Merge pull request #1678 from fishtown-analytics/0.14.1-changelog
0.14.1 changelog
2019-08-21 15:54:03 -04:00
Drew Banin
83003a7d8f Update CHANGELOG.md 2019-08-21 15:50:48 -04:00
Jacob Beck
01534c1fc1 Merge pull request #1646 from fishtown-analytics/feature/know-about-files
Feature: Partial Parsing
2019-08-21 09:45:15 -07:00
Drew Banin
84a991520f Merge pull request #1694 from fishtown-analytics/fix/connection-name-snowflake
Fix for errant method call in error handler
2019-08-21 09:40:33 -04:00
Drew Banin
89c4dbcdba Fix for errant method call in error handler 2019-08-20 20:35:28 -04:00
Drew Banin
e7a24a2062 Merge pull request #1689 from bastienboutonnet/feature/snowflake_clustering
Implement Clustering for Snowflake
2019-08-20 12:27:22 -04:00
Drew Banin
0e897f7751 Merge pull request #1688 from mikaelene/project_cols_data_test
Updated injected_node.wrapped_sql for data tests
2019-08-20 12:26:14 -04:00
Drew Banin
ea898e25ad Merge pull request #1690 from fishtown-analytics/fix/no-col-expansion-on-snowflake
(#1687) no-op column expansion on Snowflake + BQ
2019-08-20 11:54:40 -04:00
Mikael Ene
6d38226a1c last try before I start over 2019-08-20 16:55:51 +02:00
Mikael Ene
b415e0eed3 last try before I start over 2019-08-20 16:54:02 +02:00
Bastien Boutonnet
90e8e75716 Apply suggestions from code review
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2019-08-20 16:27:06 +02:00
Drew Banin
5a74918e1e pep8 2019-08-20 10:26:14 -04:00
Drew Banin
d8551a6c9c use atomic column type alter statement on snowflake 2019-08-20 09:40:29 -04:00
Mikael Ene
51b546f50f try again 2019-08-20 15:25:16 +02:00
Mikael Ene
4bc4c65795 fixed indention for flake and passed the local test. Cmon 2019-08-20 06:42:46 +02:00
Bastien Boutonnet
a0493b000e add a check before clustering that cluster keys were provided 2019-08-19 21:21:24 +02:00
Bastien Boutonnet
7071c1d200 fix god aweful logical flow 2019-08-19 21:13:23 +02:00
Drew Banin
64ee763c99 Merge pull request #1686 from fishtown-analytics/fix/panoply
Fix for run and docs generate on Panoply Redshift
2019-08-19 13:04:01 -04:00
Drew Banin
e78d979d4c Merge pull request #1650 from boxysean/add_postgres_ctas_adapter
Add Postgres CTAS adapter macro with support for unlogged parameter
2019-08-19 09:23:16 -04:00
mikael.ene
845529cc9d made line shorter according to flake8 2019-08-19 12:01:38 +02:00
mikael.ene
c71b2dc312 made line shorter according to flake8 2019-08-19 11:52:03 +02:00
Sean McIntyre
d8775d1054 Add unlogged as a Postgres AdapterSpecificConfigs 2019-08-18 12:07:51 -04:00
Bastien Boutonnet
db2e564e7a remove cluster key list join from if statement block 2019-08-17 21:21:04 +02:00
Bastien Boutonnet
7fc71272fd apply patch from PR 1591 2019-08-17 15:46:29 +02:00
Mikael Ene
8011bc7b7a changed the sql for data-tests for supporting sql server 2019-08-17 07:59:11 +02:00
Drew Banin
171fdf792e (#1687) no-op column expansion on Snowflake + BQ 2019-08-16 15:18:00 -04:00
Drew Banin
7a01ba7b49 (#1479) fix for run and docs generate on Panoply Redshift 2019-08-16 10:51:52 -04:00
Connor McArthur
403d000a6f Merge pull request #1680 from fishtown-analytics/pypi-hologram
hologram is on pypi
2019-08-13 17:01:13 -04:00
Connor McArthur
880b6666ef hologram is on pypi 2019-08-13 16:50:14 -04:00
Drew Banin
2aee9eedad Merge pull request #1670 from fishtown-analytics/fix/snowflake-unpickleable-datetime-timezone
fix for unpickleable datetime tzs set by snowflake
2019-08-13 13:17:04 -04:00
Connor McArthur
db203371fe Bump version: 0.14.0 → 0.14.1a1 2019-08-12 20:18:30 -04:00
Drew Banin
50fa1baf96 fix for unpickleable datetime tzs set by snowflake 2019-08-12 13:46:46 -04:00
Drew Banin
da7c9501d2 Merge pull request #1673 from sjwhitworth/bq-priority
Support job priority in BigQuery
2019-08-09 19:27:56 -04:00
Stephen
be53b67e68 Add unit test for priority 2019-08-09 23:22:15 +01:00
Drew Banin
ffcaac5b4c Merge pull request #1672 from sjwhitworth/fix-iternal-execption
Fix typo: "IternalException" -> "InternalException"
2019-08-09 14:46:10 -04:00
Stephen
65c3bf6d0f Support job priority in BigQuery 2019-08-08 21:50:29 +01:00
Stephen
58976b46f9 Fix typo: "IternalException" -> "InternalException" 2019-08-08 20:25:14 +01:00
Jacob Beck
fe2a9fe097 Fix rebase issues 2019-08-06 13:20:50 -05:00
Drew Banin
3e3c69eaf9 Merge pull request #1663 from edmundyan/ey_create_schemas_lowered
Do case-insensitive schema comparisons to test for schema existance
2019-08-06 13:45:26 -04:00
Jacob Beck
bcb3df383f PR feedback
fix compiled seeds to never be empty, like parsed seeds
target_database fixes
 - make target_database/target_schema set node.database/schema
 - use .database/.schema in the snapshot materialization
 - allow target_database to not be set
 - change a test to not include target_database to make sure we don't break it
 - (carefully) rename misleading test
2019-08-06 07:45:44 -05:00
Jacob Beck
24c0179048 mypy rebase cleanup 2019-08-06 07:45:44 -05:00
Jacob Beck
e54661b5df PR feedback 2019-08-06 07:45:44 -05:00
Jacob Beck
85cc8cd542 much better unit tests for the cache 2019-08-06 07:45:44 -05:00
Jacob Beck
9076806a9b More windows nonsense 2019-08-06 07:45:44 -05:00
Jacob Beck
17378812a4 normcase everywhere, windows is terrible 2019-08-06 07:45:44 -05:00
Jacob Beck
0e97b53e70 PR feedback
fix windows tests
mypy + added more stubs for easy modules
remove __future__ imports from python2
gitignore
2019-08-06 07:45:42 -05:00
Jacob Beck
0caa90751b invalidate the cache on profile/target changes 2019-08-06 07:44:15 -05:00
Jacob Beck
c38c8d539b hide partial parsing behind a flag 2019-08-06 07:44:15 -05:00
Jacob Beck
23883303ff Partial parsing
Refactor parsing
Store files in the manifest
some speed improvements
test fixes
Remove some old python 2 compatibility stuff
2019-08-06 07:44:13 -05:00
Jacob Beck
d08d2915e0 mypy quality of life fixes 2019-08-06 07:37:57 -05:00
Jacob Beck
1a618e7240 I can't live like this anymore, bumped to 0.15.0a1 2019-08-06 07:37:57 -05:00
Jacob Beck
60f66fc288 Make database/schema mandatory for all credentials 2019-08-06 07:37:57 -05:00
Jacob Beck
4faa633fb2 Merge pull request #1668 from fishtown-analytics/merge-0.14.1-into-lma
Merge 0.14.1 into louisa-may-alcott
2019-08-06 07:36:40 -05:00
Jacob Beck
8bb99547e3 Merge dev/0.14.1 into louisa-may-alcott 2019-08-06 02:42:47 -05:00
Edmund Yan
e867cfa4a2 Remove profile schema as we no longer run 'use schema' 2019-08-05 23:28:48 -04:00
Drew Banin
7901413a97 Merge pull request #1666 from fishtown-analytics/fix/snapshot-support-old-pg-versions
(#1665) Fix for casting error on old versions of postgres in snapshots
2019-08-05 22:50:49 -04:00
Drew Banin
b7e8670b43 (#1665) Fix for casting error on old versions of postgres in snapshot queries 2019-08-05 22:16:16 -04:00
Drew Banin
e2531edb02 Merge pull request #1662 from fishtown-analytics/template/bug-and-feature-templates
GitHub issue / PR touchups
2019-08-05 16:41:47 -04:00
Drew Banin
96913732e4 Merge pull request #1658 from fishtown-analytics/docs/0.14.1
Update CHANGELOG.md
2019-08-05 16:41:25 -04:00
Drew Banin
06a89446ab Merge pull request #1664 from vitorbaptista/patch-1
Fix typo "paramteter" -> "parameter"
2019-08-05 16:31:21 -04:00
Vitor Baptista
ac128da45a Fix typo "paramteter" -> "parameter" 2019-08-05 20:56:04 +01:00
Edmund Yan
265f6d3ce5 Do case-insensitive schema comparison before trying to create a schema 2019-08-05 14:12:36 -04:00
Drew Banin
388fd0bd00 Update bug report and feature request templates 2019-08-05 12:11:57 -04:00
Drew Banin
1c6945cb75 Update CHANGELOG.md 2019-08-05 11:23:58 -04:00
Drew Banin
772cb0d326 Update CHANGELOG.md 2019-08-05 10:50:28 -04:00
Drew Banin
b2f2e69377 Merge pull request #1540 from elexisvenator/fix/postgres-catalog
Change postgres `get_catalog` to not use `information_schema`
2019-08-04 19:29:30 -04:00
Drew Banin
377d5b7f58 Merge pull request #1657 from fishtown-analytics/bump/0.14.1-docs-site
bump docs site to 0.14.1
2019-08-04 19:25:22 -04:00
Drew Banin
b4da0686c8 bump docs site to 0.14.1 2019-08-04 19:20:38 -04:00
Drew Banin
f3baa69aad Merge pull request #1656 from fishtown-analytics/fix/bad-profile-dir
(#1645) fix for errant cookie generation
2019-08-04 19:13:04 -04:00
Drew Banin
9527626ffe Merge pull request #1609 from aminamos/dev/0.14.1
updated dbt.exceptions reference to exceptions in .sql files
2019-08-04 18:06:22 -04:00
Drew Banin
e13568117b Merge pull request #1654 from fishtown-analytics/fix/summarize-warns
(#1597) summarize warnings at end of test invocations
2019-08-04 17:47:48 -04:00
Drew Banin
57c6b11d47 (#1645) fix for errant cookie generation 2019-08-03 14:14:04 -04:00
Drew Banin
800355ec2f (#1597) summarize warnings at end of test invocations 2019-08-03 12:49:28 -04:00
Drew Banin
4478a89f28 Merge pull request #1647 from levimalott/fix/recover-from-failed-cleanup-rollbacks
Log, but allow, failures during cleanup rollbacks.
2019-08-02 16:25:08 -04:00
Jacob Beck
07aedc0e61 Merge pull request #1652 from fishtown-analytics/feature/dbt-rpc-run
dbt rpc tasks
2019-08-02 13:01:39 -06:00
Jacob Beck
115ef7dd97 Add some dbt project-level tasks to the RPC server
Add seed_project
 - takes 'show' arg
Add run_project
 - takes 'models' and 'exclude' args
Add compile_project
 - takes 'models' and 'exclude' args
Add test_project
 - takes models and exclude args
 - takes 'data' and 'schema' args
Tests
2019-08-02 12:29:04 -06:00
Drew Banin
9e07912e1c Merge pull request #1644 from fishtown-analytics/fix/seed-nonascii-chars
Fix for unicode chars in seed files
2019-08-02 13:31:41 -04:00
Jacob Beck
8fd768e46b for agate, use the "Urb" mode on python 2, handle BOM fiddling 2019-08-01 12:41:51 -06:00
boxysean
dad3dcacfe Add Postgres-specific CTAS adapter macro 2019-07-31 18:20:19 -04:00
Levi Malott
0927093303 Slim down the rollback failure log to appease flake8. 2019-07-31 16:22:29 -05:00
Levi Malott
3099119815 Log failed rollbacks to debug logs rather than to stdout. 2019-07-31 14:59:14 -05:00
Levi Malott
7a026c7e10 Log, but allow, failures during cleanup rollbacks.
In Postgres, rollbacks can fail if the transaction was killed
by the database. One common scenario is that the
`idle_in_transaction_session_timeout` is enabled.

If the
transaction was cancelled, the connection is left open
in `dbt`. `dbt` attempts to close that connection after issuing
a `ROLLBACK`. But it fails since the transaction was severed.
Since the cleanup is carried out in a `finally` statement, the
`psycopg2.InternalDatabaseError` is thrown and prevents the
test case results from ever being shown.

Changes here wrap the `ROLLBACK` in a try-catch such that
if there is an exception thrown, it is logged appropriately,
but ultimately proceeds.
2019-07-31 10:18:12 -05:00
Drew Banin
7177a6543b Merge pull request #1638 from fishtown-analytics/fix/bq-repeated-record-execute
(#1626) fix for RPC error with BQ nested fields
2019-07-30 16:27:08 -04:00
Drew Banin
b2b0f78587 Merge pull request #1642 from fishtown-analytics/fix/ls-ephemeral-selection
exclude ephemeral addins in dbt ls command
2019-07-30 14:57:47 -04:00
Drew Banin
7001afbcbe (#1632) fix for unicode chars in seed files 2019-07-30 14:33:32 -04:00
Drew Banin
3eb28198bd remove unnecessary reordering of table cols 2019-07-30 12:47:02 -04:00
Drew Banin
81deb8d828 exclude ephemeral addins in dbt ls command 2019-07-30 12:39:11 -04:00
Drew Banin
e30ba80d6a Merge pull request #1636 from fishtown-analytics/feature/test-freshness-disable
add test for nulled out freshness spec
2019-07-30 09:45:42 -04:00
Drew Banin
22d13ba881 serialize as json 2019-07-29 23:31:15 -04:00
Drew Banin
6cfbcf1ac8 (#1626) fix for RPC error with BQ nested fields 2019-07-29 22:35:16 -04:00
Drew Banin
21daca9faf add test for nulled out freshness spec 2019-07-29 15:27:44 -04:00
Amin
142edcff38 Update core/dbt/exceptions.py
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2019-07-29 11:32:26 -07:00
aminamos
70d82ed48e removed CODE from NotImplementedException class 2019-07-26 13:52:15 -07:00
Jacob Beck
52c9234621 Merge pull request #1629 from fishtown-analytics/feature/compile-tricks-flag
Add environment variables for macro debugging flags (#1628)
2019-07-26 09:44:02 -06:00
Drew Banin
78d309551f Merge pull request #1633 from fishtown-analytics/fix/source-column-description-interpolation
Fix for unrendered source column descriptions
2019-07-26 11:29:17 -04:00
Drew Banin
f91109570c pep8 2019-07-26 09:19:31 -04:00
Drew Banin
291ef56bc7 (#1619) fix for unrendered source column descriptions 2019-07-25 22:25:04 -04:00
Drew Banin
b12484bb6f Merge pull request #1614 from fishtown-analytics/fix/snapshot-check-cols-cycle
possible fix for re-used check cols on BQ
2019-07-24 12:56:38 -04:00
Jacob Beck
709ee2a0e8 Add environment variables for macro debugging flags 2019-07-23 17:26:53 -06:00
Drew Banin
8d4f2bd126 Merge pull request #1623 from fishtown-analytics/feature/set-snowflake-application-name
Set application name in snowflake connections
2019-07-22 13:21:59 -04:00
Drew Banin
b6e7351431 snapshot surrogate key whitespace control 2019-07-22 11:14:03 -04:00
Drew Banin
329145c13f Merge branch 'dev/0.14.1' into fix/snapshot-check-cols-cycle 2019-07-21 15:26:44 -04:00
Drew Banin
35d1a7a1b5 add tests 2019-07-21 15:26:40 -04:00
Drew Banin
a2e801c2de pep8 2019-07-21 13:40:43 -04:00
Drew Banin
e86c11e5de set application name in snowflake connections 2019-07-20 15:59:36 -04:00
Jacob Beck
b0217ba299 Merge branch 'dev/0.14.1' into dev/louisa-may-alcott 2019-07-18 15:15:07 -04:00
Jacob Beck
62c3318fe5 Merge pull request #1610 from fishtown-analytics/feature/split-parsed-things
Split Parsed and Compiled nodes into subtypes (#1601)
2019-07-18 12:52:23 -06:00
Jacob Beck
b9a3fe59c8 PR feedback 2019-07-18 11:12:48 -04:00
Jacob Beck
e46800f5b4 Merge pull request #1615 from fishtown-analytics/fix/linear-time-selection
Make node selection O(n)
2019-07-17 14:50:22 -06:00
Jacob Beck
0648737fc1 add the flat graph back in with caching, PR feedback 2019-07-17 15:30:19 -04:00
Jacob Beck
1a4daaba10 That was the last reference to to_flat_graph, so goodbye to all that 2019-07-17 12:52:35 -04:00
Jacob Beck
6be4ac044c remove repated to_flat_graph() call 2019-07-17 12:46:44 -04:00
Jacob Beck
c0aabc7d0b linear time wooo 2019-07-17 12:34:46 -04:00
Drew Banin
4df0bbd814 touchup var name and sql formatting 2019-07-16 23:43:03 -04:00
Drew Banin
5e6e746951 possible fix for re-used check cols on BQ 2019-07-16 23:24:19 -04:00
aminamos
a55a27acf6 removed extra line 2019-07-16 16:13:58 -07:00
aminamos
8046992e08 added string interpolation to raise_not_implemented 2019-07-16 14:28:31 -07:00
aminamos
de56e88a00 updated raise_not_implemented, commented on profile.py 2019-07-16 14:18:52 -07:00
Jacob Beck
72afd76e1a Merge pull request #1589 from fishtown-analytics/feature/dataclasses-jsonschema-types
dataclasses-jsonschema types
2019-07-16 11:06:10 -06:00
Jacob Beck
12e53c732e Split Parsed and Compiled nodes into subtypes
A few test changes to account for removed fields based on types
2019-07-16 13:04:02 -04:00
Jacob Beck
49f7cf8eca Convert dbt to use dataclasses and hologram for representing things
Most of the things that previously used manually created jsonschemas
Split tests into their own node type
Change tests to reflect that tables require a freshness block
add a lot more debug-logging on exceptions
Make things that get passed to Var() tell it about their vars
finally make .empty a property
documentation resource type is now a property, not serialized
added a Mergeable helper mixin to perform simple merges
Convert some oneOf checks into if-else chains to get better errors
Add more tests
Use "Any" as value in type defs
 - accept the warning from hologram for now, PR out to suppress it
set default values for enabled/materialized
Clean up the Parsed/Compiled type hierarchy
Allow generic snapshot definitions
remove the "graph" entry in the context
 - This improves performance on large projects significantly
Update changelog to reflect removing graph
2019-07-16 12:00:31 -04:00
Jacob Beck
10be7bac2f Merge branch 'dev/0.14.1' into dev/louisa-may-alcott 2019-07-16 11:42:42 -04:00
aminamos
fa6fb1b53d updated dbt.exceptions reference to exceptions in .sql files 2019-07-15 19:14:22 -07:00
Drew Banin
5a1f0bdda5 Merge pull request #1608 from fishtown-analytics/fix/is-incremental-check-materialization
Make is_incremental live up to its name
2019-07-15 15:17:04 -04:00
Drew Banin
405748c744 make is_incremental live up to its name 2019-07-15 14:15:20 -04:00
Drew Banin
cc90b048af Merge pull request #1605 from fishtown-analytics/dev/0.14.1-merge-wilt
Merge 0.14.latest into dev/0.14.1
2019-07-12 16:39:48 -04:00
Drew Banin
6886228992 Merge branch '0.14.latest' into dev/0.14.1-merge-wilt 2019-07-12 09:14:43 -04:00
Drew Banin
4569c905a5 Merge pull request #1596 from fishtown-analytics/fix/contrib-typos
Update CONTRIBUTING.md
2019-07-09 21:49:47 -04:00
Connor McArthur
453e81e895 Bump version: 0.14.0rc1 → 0.14.0 2019-07-09 21:44:06 -04:00
Drew Banin
3af8696761 Update CONTRIBUTING.md 2019-07-09 21:43:59 -04:00
Connor McArthur
399b33822a update changelog date 2019-07-09 21:42:18 -04:00
Drew Banin
913a296cc4 Merge pull request #1583 from fishtown-analytics/update-contributing-guide
Update CONTRIBUTING.md
2019-07-09 17:59:45 -04:00
Drew Banin
bd55569703 Merge branch 'dev/wilt-chamberlain' into update-contributing-guide 2019-07-09 17:59:05 -04:00
Drew Banin
0e2d3f833d Merge pull request #1593 from fishtown-analytics/fix/docs-links
Fix immediately-obvious links that will break
2019-07-09 17:49:57 -04:00
Drew Banin
3646969779 update changelog links 2019-07-09 15:39:07 -04:00
Claire Carroll
d5bfb9f6aa Update docs link 2019-07-09 14:15:26 -04:00
Jacob Beck
5c05f709d8 Merge pull request #1594 from fishtown-analytics/feature/logging-again
Fix the test dockerfile to get the logfile's default encoding right
2019-07-09 10:20:26 -06:00
Jacob Beck
30a270b5f4 Fix the dockerfile to get the logfile's default encoding right
Fix a missing import
2019-07-09 08:46:08 -06:00
Jacob Beck
671836c47d Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-07-09 08:23:25 -06:00
Drew Banin
56a2d9dc0a Merge pull request #1590 from fishtown-analytics/fix/snapshot-check-cols-dupe-scd-id
[Wilt Chamberlain] fix for dupe check_cols values in snapshot strategy
2019-07-08 11:00:42 -04:00
Claire Carroll
e90c05c8f8 Fix immediately-obvious links that will break 2019-07-07 16:05:51 -04:00
Drew Banin
08d79cc324 (#1588) fix for dupe check_cols values in snapshot strategy 2019-07-05 21:14:54 -04:00
Drew Banin
990b0c93a5 Merge pull request #1587 from fishtown-analytics/fix/list-description
Fix the list subparser description
2019-07-03 17:04:45 -04:00
Claire Carroll
14d638c588 Fix the list subparser description 2019-07-03 16:34:47 -04:00
Jacob Beck
df8b12b2c0 Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-07-03 10:09:30 -06:00
Ben Edwards
2645667257 Fix table type
v = view
r, f, p = all are different forms of table
2019-07-03 07:32:54 +10:00
Ben Edwards
38c2d82c88 Cleaned up filtering to be in line with information_schema logic 2019-07-02 09:09:33 +10:00
Connor McArthur
986f5b7b4e Bump version: 0.14.0a2 → 0.14.0rc1 2019-07-01 15:29:32 -04:00
Drew Banin
ef76c04ae8 Merge pull request #1585 from fishtown-analytics/fix/persist-docs-validation
add validation for extend dict fields that receive non-dicts
2019-07-01 13:18:52 -04:00
Drew Banin
d4d5393faa add validation for extend dict fields that receive non-dicts 2019-07-01 11:45:46 -04:00
Drew Banin
9ffc4bf928 Update CONTRIBUTING.md 2019-06-28 12:26:26 -04:00
Jacob Beck
b9bfff19bb Merge pull request #1581 from fishtown-analytics/feature/remove-archive-config
remove archive block support and "dbt snapshot-migrate" (#1580)
2019-06-28 09:35:17 -04:00
Jacob Beck
51b6fd6f86 Merge pull request #1582 from fishtown-analytics/fix/set-io-encoding
set PYTHONIOENCODING on the dockerfile to suppress encoding errors
2019-06-27 23:15:14 -04:00
Jacob Beck
be765dc4e8 set PYTHONIOENCODING on the dockerfile to suppress encoding errors 2019-06-27 20:40:02 -06:00
Drew Banin
7febd9328d Merge pull request #1542 from fishtown-analytics/changelog/0.14.0
Update CHANGELOG.md
2019-06-27 22:25:28 -04:00
Drew Banin
33a80fca5a Update CHANGELOG.md 2019-06-27 22:24:51 -04:00
Jacob Beck
b0c6233b44 remove archive block support and "dbt snapshot-migrate" 2019-06-27 20:14:43 -06:00
Jacob Beck
f7158b233f Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-06-27 15:47:12 -06:00
Jacob Beck
f368820b7e Merge pull request #1578 from heisencoder/fix/test-cleanup
Fix/test cleanup
2019-06-27 12:06:26 -04:00
Matt Ball
2f1dbc2dae change assert{Not}Equals to assert{Not}Equal in integration tests
This is to remove deprecation warnings during testing.

Linux commands (with globstar shell option enable):

$ cd test
$ sed -i 's/self.assertEquals/self.assertEqual/g' **/*.py
$ sed -i 's/self.assertNotEquals/self.assertNotEqual/g' **/*.py
2019-06-27 09:10:57 -06:00
Matt Ball
3ad30217c4 remove DeprecationWarnings from test/unit files
We're getting a bunch of deprecation warnings when running the unit
tests due to using statements like assertEquals instead of assertEqual.
This change removes these warnings.

Change completed via these Linux commands:

$ cd test/unit
$ sed -i 's/self.assertEquals/self.assertEqual/g' test_*.py
$ sed -i 's/self.assertNotEquals/self.assertNotEqual/g' test_*.py
2019-06-27 08:36:11 -06:00
Matt Ball
4a10f2cb37 update test_context.py to use a local import of mock_adapter
This is consistent with the way that unit tests import utils.py and
also fixes an import issue with our test environment.
2019-06-27 08:28:02 -06:00
Drew Banin
f3948295e9 Update CHANGELOG.md 2019-06-26 19:35:05 -04:00
Drew Banin
42fb12027c Merge pull request #1575 from fishtown-analytics/tracking/adapter-type-and-rpc-requests
(#1574) track adapter type and rpc requests
2019-06-26 19:24:28 -04:00
Drew Banin
91124d2d4f (#1574) track adapter type and rpc requests 2019-06-26 17:55:37 -04:00
Jacob Beck
19fe9119cb Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-06-26 15:54:01 -06:00
Drew Banin
2e2ce9a57a Update CHANGELOG.md 2019-06-26 17:52:59 -04:00
Drew Banin
fdcb395739 Merge pull request #1571 from josegalarza/dev/wilt-chamberlain
Add ExternalTable relation type, update Snowflake adapter (issue #1505)
2019-06-26 11:24:32 -04:00
Drew Banin
7d1fed2eb9 Merge pull request #1572 from cclauss/patch-1
Undefined name: import shutil for line 60
2019-06-26 09:47:04 -04:00
cclauss
462a1516d2 Undefined name: import shutil for line 60 2019-06-26 13:24:12 +02:00
Jose
654f70d901 Add ExternalTable relation type, update Snowflake adapter (issue #1505) 2019-06-26 12:11:41 +10:00
Jacob Beck
b09b1da1f4 Merge pull request #1568 from fishtown-analytics/feature/remove-agate-from-nodes
Remove agate from nodes
2019-06-25 14:33:24 -04:00
Jacob Beck
8ca49cb7af lets not allow people to load arbitrary file paths, huh 2019-06-25 14:04:25 -04:00
Jacob Beck
e4d3942f36 set name back to agate_table in the sql 2019-06-25 12:48:29 -04:00
Jacob Beck
dd2f673083 Tests
make sure to run --show for every database
Fix the exit tests since we now fail at runtime, not compile time
2019-06-25 12:41:48 -04:00
Jacob Beck
43df84ae8f plumb the loaded agate table through the results for --show 2019-06-25 12:21:12 -04:00
Jacob Beck
f211d4c8ec Load agate tables at seed materialization time 2019-06-25 09:46:21 -04:00
Connor McArthur
31f20348c9 Bump version: 0.14.0a1 → 0.14.0a2 2019-06-25 09:45:09 -04:00
Jacob Beck
8c721ba561 Merge pull request #1535 from fishtown-analytics/feature/rm-rf-python2
Remove Python 2.7 support
2019-06-25 08:31:01 -04:00
Jacob Beck
01c0a5462b PR feedback 2019-06-25 06:38:20 -04:00
Jacob Beck
f73575a8c1 python 3.7 docker/tox support
Dockerfile that supports 2.7,3.6,3.7
Add 2.7.16 so we can run old dbt tests on the same image as the current one (I hope!)
add 3.7 to tox.ini
2019-06-25 06:38:20 -04:00
Jacob Beck
c7385ec512 Remove python 2.7
remove tests for 2.7
remove all dbt.compat
remove six
assorted 2-removal related cleanups
do things that we could not do before due to py2
make super super()
classes always derive from obect in 3.x
Enum-ify enum-y things
azure pipelines -> python 3.7
mock is part of unittest now
update freezegun
2019-06-25 06:38:20 -04:00
Drew Banin
bd0876e2e6 Update CHANGELOG.md 2019-06-24 19:48:57 -04:00
Jacob Beck
4f3dc7629a Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-06-21 15:49:08 -04:00
Jacob Beck
a47c09e5d2 Merge pull request #1565 from fishtown-analytics/feature/new-dockerfile
Pull in new dockerfile
2019-06-21 15:47:49 -04:00
Drew Banin
164468f990 Merge pull request #1562 from fishtown-analytics/feature/statement-sugar
add sugar over statements (run_query)
2019-06-21 13:40:19 -04:00
Jacob Beck
c56b631700 Pull in new dockerfile 2019-06-21 12:48:18 -04:00
Jacob Beck
84c487b577 Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-06-21 11:04:37 -04:00
Jacob Beck
2636969807 Merge pull request #1558 from fishtown-analytics/fix/created-relations-casing
Fix casing comparisons on dbt-created relations (#1555)
2019-06-21 10:10:56 -04:00
Jacob Beck
1e17303b97 Merge pull request #1561 from fishtown-analytics/fix/revert-strict-undefined
Remove strict undefined + tests
2019-06-20 15:22:07 -06:00
Drew Banin
7fa8d891ef Merge pull request #1560 from fishtown-analytics/fix/support-error-on-nondeterministic-merge-snowflake
add strategy switch for incremental merge behavior (snowflake)
2019-06-20 16:36:39 -04:00
Drew Banin
c029dfe3fa add sugar over statements (run_query) 2019-06-20 16:33:31 -04:00
Drew Banin
c4ef120b74 Merge branch 'dev/wilt-chamberlain' into fix/support-error-on-nondeterministic-merge-snowflake 2019-06-20 14:56:13 -04:00
Drew Banin
8644ce1cb8 rm strategy alias; rename overwrite strategy 2019-06-20 14:52:32 -04:00
Jacob Beck
b0b3cdc21f Remove strict undefined + tests 2019-06-20 14:48:07 -04:00
Drew Banin
e3d30d8a35 add strategy switch for incremental merge behavior (snowflake) 2019-06-20 14:04:24 -04:00
Jacob Beck
0ef9c189c0 Merge pull request #1550 from fishtown-analytics/fix/block-parser-ignore-more
clean up block parsing to make it dumber and more effective (#1547)
2019-06-20 06:33:48 -06:00
Jacob Beck
18953536f1 PR feedback 2019-06-19 14:36:07 -06:00
Jacob Beck
dd02f33482 new test 2019-06-19 11:33:57 -06:00
Jacob Beck
3576839199 oops, fix bq/sf too 2019-06-19 11:33:49 -06:00
Drew Banin
4ed668ef93 Update CHANGELOG.md 2019-06-19 13:30:06 -04:00
Drew Banin
f8344469e1 Merge pull request #1549 from heisencoder/feature/add-project-dir-flag
add --project-dir flag to allow specifying project directory
2019-06-19 13:28:24 -04:00
Jacob Beck
1d94fb67da Fix casing comparisons on dbt-created relations
When dbt creates a relation in the db, add a special flag
When checking a node name match:
 - if that flag is present and quoting is disabled, do a lowercase compare
 - otherwise remain case sensitive
2019-06-19 09:44:37 -06:00
Drew Banin
9ad85127e4 Merge pull request #1553 from fishtown-analytics/fix/include-stuff
Add macro subdirectories to redshift/postgres (#1552)
2019-06-18 19:13:46 -04:00
Drew Banin
3845abeff8 Update CHANGELOG.md 2019-06-18 18:34:52 -04:00
Jacob Beck
f95c712f95 Merge pull request #1551 from fishtown-analytics/fix/package-without-version
require versions in package defs (#1546)
2019-06-18 13:16:45 -06:00
Matt Ball
f5c3300304 remove tab character in base.py 2019-06-18 13:08:53 -06:00
Matt Ball
84fa83b4dd add unit test for new --project-dir flag
See https://github.com/fishtown-analytics/dbt/issues/1544
2019-06-18 12:51:02 -06:00
Jacob Beck
927c37470a a different message 2019-06-18 12:41:57 -06:00
Jacob Beck
b80fa53b2a include macros/**/*.sql as well 2019-06-18 12:19:48 -06:00
Jacob Beck
cce5ae01f8 require versions 2019-06-18 11:21:06 -06:00
Jacob Beck
92ef783948 clean up block parsing to make it dumber and more effective 2019-06-18 10:45:21 -06:00
Matt Ball
2e7c1fd2cc add --project-dir flag to allow specifying project directory
In particular, this is the directory that contains the dbt_project.yml
file and all the related project files and directories.

Without this flag, it is necessary to cd to the project directory before
running dbt.

See https://github.com/fishtown-analytics/dbt/issues/1544
2019-06-17 16:22:45 -06:00
Jacob Beck
85164b616e Merge pull request #1543 from fishtown-analytics/fix/unquoted-strings-errors
Handle quotes in jinja block bodies (#1533)
2019-06-14 12:03:18 -06:00
Jacob Beck
c67a1ac9f7 link to issue in test case comment 2019-06-14 11:37:24 -06:00
Jacob Beck
a2cae7df29 Block parsing now uses recursive descent... 2019-06-14 10:50:32 -06:00
Drew Banin
f44e3bc9d8 Update CHANGELOG.md 2019-06-14 09:44:24 -07:00
Ben Edwards
03bc58116c Fix incorrectly named column table_type 2019-06-14 17:21:08 +10:00
Ben Edwards
9b88eb67a1 Change postgres get_catalog to not use information_schema
- `information_schema` in Postgres is not very performant due to the complex views used to create it
 - use underlying `pg_catalog` tables/views instead
 - returns the same rows/columns as the `information_schema` version
 - order of rows is different, this is because there was only a partial sort on the `information_schema` version
 - `column_type` will return different values to before
   - some arrays were `ARRAY`, will now be `type[]`
   - user-defined types were previously `USER_DEFINED`, now will be the name of the user-defined type <-- main point of this PR
 - performance is 2-5x faster, depending on query caching
2019-06-14 13:51:10 +10:00
Connor McArthur
6e5fa7de3c add changelog section for wilt 2019-06-13 17:16:21 -04:00
Jacob Beck
d327394057 Merge pull request #1537 from fishtown-analytics/fix/bigquery-fix-schema-404s
List 10k datasets instead of all (#1536)
2019-06-13 14:32:24 -06:00
Drew Banin
b7c06941e3 Merge pull request #1539 from fishtown-analytics/docs/0.14.0-updates
(#1474) incorporate docs site updates
2019-06-13 10:42:44 -07:00
Drew Banin
57adfc8683 (#1474) incorporate docs site updates 2019-06-13 10:22:00 -07:00
Jacob Beck
e13d805197 list a ton of datasets instead of all to avoid pagination 2019-06-13 10:42:20 -06:00
Jacob Beck
f0635a0df4 Merge pull request #1514 from fishtown-analytics/fix/dbt-ls-selectors
Fix dbt ls selectors
2019-06-13 10:40:57 -06:00
Jacob Beck
24adb74498 Merge pull request #1526 from fishtown-analytics/fix/refs-in-run-operations
Add ref and source support to run-operation macros (#1507)
2019-06-13 10:18:33 -06:00
Jacob Beck
16519b11aa Merge pull request #1534 from fishtown-analytics/fix/deprecate-python-2
Emit a big deprecation message on python 2.x at the end of the run (#1531)
2019-06-13 10:14:10 -06:00
Jacob Beck
0a666caa13 Emit a big deprecation warning on python 2.x at the end of the run 2019-06-13 08:44:40 -06:00
Jacob Beck
7525da70ef Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-06-12 20:33:05 -06:00
Jacob Beck
5833acbc8c Print fqns, not unique ids
FQNs for sources
Allow package names as an optional prefix for source: selectors
make "dbt ls" output more intuitive by including source:* by default
Fix the tests
2019-06-12 19:46:27 -06:00
Jacob Beck
e57c7b651d Merge pull request #1519 from fishtown-analytics/feature/render-severity-configs
render severity configurations (#1512) (#1511)
2019-06-12 19:40:31 -06:00
Jacob Beck
9a40395cdb Add ref and source support to run-operation macros
Removed the "runtime" provider's generate_macro function
Added an "operation" provider that does similar but:
  ref() does not check for itself in depends_on
  source() does not try to append to sources
Macros assume that the rendered form the the ref exists in the database
Also changed `--macro` to a required positional argument
  you can run macro_name with 'dbt run-operation macro_name --args ...'
Make providers objects, unify some ref processing stuff
Remove an empty method call on operations, add some comments
Raise a compiler error if you try to ref() an ephemeral model in your run-operation macro
2019-06-12 19:36:16 -06:00
Jacob Beck
03aecc8d0c Merge pull request #1481 from fishtown-analytics/feature/archive-migration-script
archive table migration script
2019-06-12 19:01:59 -06:00
Jacob Beck
a554b383a2 Merge pull request #1520 from fishtown-analytics/feature/rename-models-to-select
for snapshots, make the argument "--select", not "--models" (#1517)
2019-06-12 16:14:04 -06:00
Jacob Beck
a4be1e1dcb when warning about undefined, handle the case where the node is none 2019-06-12 16:13:25 -06:00
Jacob Beck
7b498f4179 archive table migration script
Also migrate archive configs
PR feedback
archives are now snapshots
Make the script a subcommand
2019-06-12 15:45:43 -06:00
Jacob Beck
4a10c8dce7 fix bigquery test 2019-06-12 15:39:17 -06:00
Jacob Beck
0cc99c50a7 for snapshots, make the argument "--select", not "--models" 2019-06-12 15:39:12 -06:00
Jacob Beck
92fdf45f0c Support lowercase test severity 2019-06-12 15:38:05 -06:00
Jacob Beck
f3cafae030 render severity configurations 2019-06-12 15:37:35 -06:00
Jacob Beck
12e5bf6036 Merge pull request #1522 from fishtown-analytics/fix/check-cols-null
handle null check-cols (#1516)
2019-06-12 15:32:40 -06:00
Jacob Beck
f0ab957edb Merge pull request #1521 from fishtown-analytics/fix/quote-columns-in-snapshots
Snapshots: Quote columns, fix print output, fix tests (#1510)
2019-06-12 15:32:11 -06:00
Jacob Beck
4308b28aa6 Merge pull request #1527 from fishtown-analytics/feature/parallel-integration-tests
parallel tests
2019-06-12 15:07:40 -06:00
Jacob Beck
f79619ec0a parallelize the tests
Set -n4 in ci, as -n auto chose 32 and caused problems
python 2.x and flaky/pytest-xdist do not get along, so py3 only
Mark some rpc tests as flaky, especially with parallelism
 - socket contention is a problem
Remove coverage stuff
Coallate and upload the log files we generate
Added special logging for a particular rare CI error case
Set an "initial root" at import time
  chdir into it at setUp time as well as tearDown time

I think py2 might work now?
2019-06-12 14:37:54 -06:00
Jacob Beck
5dd147123a HACK to get docs tests happy, need to settle on rules for what path emits wrt symlinks 2019-06-12 14:37:34 -06:00
Jacob Beck
63d6ab2006 make integration tests run in their own directories
Make tempdir, write profile + project there, use it as the profile directory
fixed 015 tests to turn that behavior off and do custom things
Migrate all the tests
Make run_dbt_and_check more like run_dbt
Fix windows ls tests
2019-06-12 14:37:07 -06:00
Jacob Beck
1951e0f5e8 Merge pull request #1532 from fishtown-analytics/build/pr-fixup-1285
merged and updated PR for #1031
2019-06-12 14:29:13 -06:00
Jacob Beck
fb69b89ef9 Merge pull request #1509 from heisencoder/feature/upgrade-to-networkx-2
Feature/upgrade to networkx 2
2019-06-12 12:07:25 -06:00
Jacob Beck
2d84dd4fbd Merge branch 'dev/wilt-chamberlain' into build/pr-fixup-1285 2019-06-12 11:43:08 -06:00
Jacob Beck
d760229abc Merge pull request #1476 from buremba/fix/pg-schema
Pass schema in credentials to Postgresql
2019-06-12 11:38:35 -06:00
Jacob Beck
ffb38a21e3 redshift tests 2019-06-12 10:45:03 -06:00
Matt Ball
85a2f48c80 update test_linker.py to handle a temp file using try/finally
This is based on the recommendation from @beckjake:
b8d5a341f4 (discussion_r292032021)
2019-06-12 10:33:34 -06:00
Jacob Beck
9f208f711e Merge pull request #1515 from tbescherer/project-inner-loop
move target_model vars inside loop to avoid reuse on subsequent refs
2019-06-10 08:29:15 -06:00
Jacob Beck
ea2637395c Merge pull request #1525 from fishtown-analytics/fix/increase-tracking-timeout
increase tracking timeout 2s -> 5s (#1499)
2019-06-10 08:07:27 -06:00
Jacob Beck
0ca6026124 increase tracking timeout 2s -> 5s 2019-06-10 07:13:46 -06:00
Matt Ball
b8d5a341f4 update test_graph.py to handle different sequence type in networkx 2.x 2019-06-07 14:56:08 -06:00
Matt Ball
014a8f9222 Merge branch 'dev/wilt-chamberlain' of https://github.com/fishtown-analytics/dbt into feature/upgrade-to-networkx-2 2019-06-07 14:05:21 -06:00
Jacob Beck
9599b3f584 handle null check-cols 2019-06-07 14:00:14 -06:00
tom.bescherer
ca31b79cc0 add a newline for readability 2019-06-07 11:57:19 -04:00
Jacob Beck
675e858050 Quote columns, fix print output, fix tests 2019-06-07 09:19:36 -06:00
Jacob Beck
c04517bf04 Merge pull request #1506 from fishtown-analytics/feature/rename-archives-to-snapshots
Rename archives to snapshots (#1497)
2019-06-07 07:17:47 -06:00
tom.bescherer
eb12ef1dcd move target_model vars inside loop to avoid reuse on subsequent refs 2019-06-06 16:40:32 -04:00
Jacob Beck
03f50f560b PR feedback, fix "usage" line 2019-06-06 14:35:22 -06:00
Jacob Beck
c1387c5692 Merge pull request #1513 from fishtown-analytics/fix/only-parse-docs-blocks
Only parse docs blocks contents when reading md files (#988)
2019-06-06 13:53:15 -06:00
Jacob Beck
a4a9221d95 add a missing test file 2019-06-06 12:04:53 -06:00
Matt Ball
7ed0036af0 update networkx dependency to allow version 2.x
https://github.com/fishtown-analytics/dbt/issues/1496
2019-06-06 11:03:14 -06:00
Jacob Beck
1489393489 documentation -> docs 2019-06-06 10:59:29 -06:00
Matt Ball
c4939368ae update linker.py to be compatible with networkx-2.x
Also, update test_linker.py to run under Python 2 unit tests and add
test coverage for the modified functions.

linker.py should now be compatible with both networkx-1 and -2.

This addresses https://github.com/fishtown-analytics/dbt/issues/1496

This change was tested via unit tests, but was not tested via
integration tests.

As a follow-up change, core/setup.py should have its networkx dependency
updated to allow version 2.x.
2019-06-06 10:31:36 -06:00
Jacob Beck
ab59ebe4f2 Fix docs blocks parsing issues
Rename documentation node type to docs so we can filter on it (is this breaking?)
Fix block extractor bug with macros/docs that contain quotes
Fix block extractor bug with expressions
2019-06-06 09:32:50 -06:00
Jacob Beck
f3701ab837 archives -> snapshots, except legacy stuff 2019-06-05 09:43:11 -06:00
Jacob Beck
f48f78fc58 rename a couple things, this will not work atm 2019-06-05 07:45:55 -06:00
Jacob Beck
ddb1785698 Merge pull request #1473 from fishtown-analytics/fix/windows-rpc
Fix windows rpc
2019-06-04 15:50:46 -06:00
Jacob Beck
963b0e23ee remove drop_existing flag + its deprecation warning, make flags update themselves, reset flags from args on windows processes 2019-06-04 12:01:07 -06:00
Jacob Beck
2a9ae83270 Re-enable windows tests
Disable kill tests
Don't expect logs on timed-out windows tests (windows is slow!)
2019-06-04 11:59:33 -06:00
Jacob Beck
788507e046 Get windows up and running for RPC
Refactor process bootstrapping to a function
 - avoid trying to pickle "RequestTaskHandler"s on windows
Move user config consequences out of main
 - We need this for RPC stuff too
Reset profile values and plugins on windows process start
Disable "kill" command on Windows
2019-06-04 11:59:33 -06:00
Jacob Beck
60001ad6b4 Merge pull request #1483 from fishtown-analytics/fix/swap-alias-generator-args
flip around generate_alias_name args, add node to generate_schema_name args
2019-06-04 09:32:20 -06:00
Jacob Beck
6c9d5c7370 Merge pull request #1500 from fishtown-analytics/feature/remove-archive-blocks
remove archive configs
2019-06-04 09:32:08 -06:00
Jacob Beck
1c3a02b2c8 PR feedback 2019-06-04 07:20:55 -06:00
Jacob Beck
28dc10ed98 PR feedback 2019-06-03 16:32:20 -06:00
Jacob Beck
0d49295b94 tests, my ancient nemesis 2019-06-03 16:32:14 -06:00
Jacob Beck
679784735e Swap aliases ordering and add node parameter to generate_schema_name
Fix many tests
Support single-arg generate_schema_name macros
Add repeat flag to warn_or_error to suppress duplicate warnings
Add a warning if a user's macro does not take a second argument
2019-06-03 16:32:14 -06:00
Drew Banin
99f62e850f fix tests 2019-06-03 16:32:14 -06:00
Drew Banin
001b9abce9 flip around generate_alias_name args 2019-06-03 16:32:14 -06:00
Jacob Beck
248ca3ff76 fix more tests 2019-06-03 16:22:50 -06:00
Jacob Beck
704ee58846 fix the exit code tests too 2019-06-03 16:17:21 -06:00
Jacob Beck
f26948dde2 remove archive blocks 2019-06-03 15:57:32 -06:00
Drew Banin
3cac2d3ab7 Merge pull request #1478 from fishtown-analytics/feature/archive-blocks-as-regex-materialization
Use merge pattern for Archival queries
2019-06-03 11:50:18 -04:00
Drew Banin
82793a02d3 fix for tests in different logical databases 2019-06-01 11:40:14 -04:00
Jacob Beck
434eb94730 Merge branch 'dev/wilt-chamberlain' into dev/louisa-may-alcott 2019-05-30 13:26:00 -06:00
Jacob Beck
73b70622a1 Merge pull request #1490 from fishtown-analytics/refactor/node-selector-separated
Refactor node selection
2019-05-30 12:59:21 -06:00
Jacob Beck
00cbe3ec3b Merge pull request #1494 from fishtown-analytics/fix/render-descriptions-properly
Render source_description fields in sources (#1484)
2019-05-30 12:58:48 -06:00
Drew Banin
8ecdab817a fix for pg tests 2019-05-30 13:14:27 -04:00
Drew Banin
94ae9fd4a7 fix test 2019-05-30 13:08:27 -04:00
Jacob Beck
75c8f32186 Render source_description fields in sources, fix tests to make sure we actually do that... 2019-05-30 10:54:47 -06:00
Drew Banin
69621fe6f9 cleanup tests 2019-05-30 12:21:42 -04:00
Drew Banin
81f4c1bd7c cleanup merge 2019-05-30 12:18:58 -04:00
Drew Banin
bb7cfb7dc2 Merge branch 'dev/wilt-chamberlain' into feature/archive-blocks-as-regex-materialization 2019-05-30 12:17:24 -04:00
Drew Banin
b98ea32add code review 2019-05-30 12:11:08 -04:00
Jacob Beck
2b89c5cd9a shut up assertEquals warnings 2019-05-30 10:04:17 -06:00
Jacob Beck
74e5b8bdff factor node selection into components that know about the manifest vs the graph 2019-05-30 10:04:17 -06:00
Jacob Beck
17157f2973 Merge pull request #1489 from fishtown-analytics/fix/redshift-concurrent-tests
fix concurrent transactions test
2019-05-30 09:58:01 -06:00
Drew Banin
478b17a4dc fixups 2019-05-30 11:12:22 -04:00
Jacob Beck
f14225f7e4 Merge pull request #1491 from fishtown-analytics/feature/hub-site-retries
add a retry + sleep loop to registry calls (#1451)
2019-05-30 07:31:40 -06:00
Drew Banin
8a8f7a9929 Merge branch 'dev/wilt-chamberlain' into build/pr-fixup-1285 2019-05-30 09:07:11 -04:00
Drew Banin
7d490d4886 Implement archival using a merge abstraction 2019-05-29 21:41:10 -04:00
Jacob Beck
af13b2c745 add a retry + sleep loop to registry calls 2019-05-29 15:29:53 -06:00
Jacob Beck
a164d83dad in the concurrent transactions test, use a completely separate adapter for our goofy sql running 2019-05-29 13:08:33 -06:00
Jacob Beck
d10e340823 Merge pull request #1482 from fishtown-analytics/fix/archives-no-overwrite
detect duplicate archive and node names (#1480)
2019-05-29 06:15:42 -06:00
Jacob Beck
e7bb9d14b2 Merge pull request #1475 from fishtown-analytics/fix/faster-snowflake-tests
Try to make snowflake tests a bit more performant (#1433)
2019-05-29 06:13:26 -06:00
Burak Emre Kabakcı
4e7c096c34 Fix Code Style 2019-05-28 12:59:58 +03:00
Burak Emre Kabakcı
0f5ce12dad Update .gitignore 2019-05-27 15:09:41 +03:00
Burak Emre Kabakcı
2f4e92a728 Fix linting issues 2019-05-27 15:08:14 +03:00
Jacob Beck
b047ed82b6 detect duplicate archive and node names 2019-05-24 12:47:51 -06:00
Burak Emre Kabakcı
82f165625f Use get method for extracting search_path 2019-05-24 11:55:39 +03:00
Burak Emre Kabakcı
2f5aa3bd0e Remove redundant init 2019-05-22 14:24:03 +03:00
Burak Emre Kabakcı
63ef8e3f17 Rename config to search_path 2019-05-22 14:14:05 +03:00
Burak Emre Kabakcı
f1eaeb4ed2 Fix typo in Redshift tests 2019-05-21 19:48:32 +03:00
Burak Emre Kabakcı
a2ffe8e938 Add tests & fix failing tests 2019-05-21 19:36:29 +03:00
Burak Emre Kabakcı
355d2ad6fc Pass schema in credentials to Postgresql 2019-05-21 19:09:06 +03:00
Jacob Beck
f089b4b077 try to make snowflake assertManyTablesEqual faster
use "show columns" instead of selecting from the information schema
calculate numbers of rows while we calculate mismatched rows
use "show objects" instead of selecting from the information schema for get_models_in_schema
2019-05-21 09:30:32 -06:00
Jacob Beck
73607b85b7 Merge pull request #1465 from fishtown-analytics/dev/merge-in-0.13.1
merge 0.13.1
2019-05-16 07:23:55 -06:00
Drew Banin
7b022f3afa Merge pull request #1459 from fishtown-analytics/feature/printer-width-tests
Make printer width configurable
2019-05-15 17:49:50 -04:00
Drew Banin
0a2e4f761b Merge pull request #1409 from bastienboutonnet/snowflake_create_or_replace
Snowflake create or replace
2019-05-15 17:48:25 -04:00
Jacob Beck
ddd73cd73b Merge branch '0.13.latest' into dev/wilt-chamberlain 2019-05-15 12:44:44 -06:00
Kriselle Sanchez
efdb837a50 Make printer width configurable 2019-05-13 14:52:20 -04:00
Drew Banin
90abc2d2f3 (closes #1455) Qualify Snowflake temp tables with a database and schema 2019-05-13 14:12:30 -04:00
Connor McArthur
f60938aab0 Bump version: 0.13.1a2 → 0.13.1 2019-05-13 11:58:03 -04:00
Connor McArthur
af7c752fc6 set release date 2019-05-13 11:57:29 -04:00
Connor McArthur
a80989952a Merge pull request #1392 from fishtown-analytics/dev/0.13.1
dbt 0.13.1
2019-05-13 11:56:37 -04:00
Drew Banin
8d74550609 fix tests 2019-05-10 19:12:38 -04:00
Jacob Beck
d5774b3da4 Merge pull request #1452 from fishtown-analytics/feature/strip-seed-bom
Handle seeds with utf-8 BOM (#1177)
2019-05-10 10:40:53 -06:00
Jacob Beck
7f7002f36c Merge pull request #1454 from fishtown-analytics/feature/increment-version
Bump version: 0.13.0 → 0.14.0a1
2019-05-10 10:40:41 -06:00
Drew Banin
b62ba4a985 py2 compat 2019-05-10 10:52:57 -04:00
Drew Banin
2b3370887e add missing import 2019-05-10 10:19:27 -04:00
Jacob Beck
26427d2af0 PR feedback 2019-05-10 07:50:46 -06:00
Jacob Beck
d502b33ef4 Merge pull request #1453 from fishtown-analytics/feature/warn-on-unpinned-packages
Warn on unpinned git packages (#1446)
2019-05-10 07:01:28 -06:00
Jacob Beck
210cf43574 Merge pull request #1440 from fishtown-analytics/feature/output-run-hooks
Output run hooks (#696)
2019-05-09 21:06:37 -06:00
Jacob Beck
d74e37d4ea Always specify encodings to open()
Fixes some neat windows behavior where the default code page can be cp1252
Add BOM unit test
2019-05-09 21:04:52 -06:00
Jacob Beck
ea8825996d PR feedback 2019-05-09 20:57:56 -06:00
Jacob Beck
336368195e Bump version: 0.13.0 → 0.14.0a1 2019-05-09 20:21:24 -06:00
Jacob Beck
70206b1635 warn on unpinned dependencies, unless warn-unpinned: False is set in packages.yml 2019-05-09 20:09:20 -06:00
Jacob Beck
191ae61b02 python 2 2019-05-09 19:48:26 -06:00
Jacob Beck
f6bf8d912a PR feedback
Order hooks in a deterministic way:
 - in the root project, hooks in index order
 - foreach dependency project in alphabetical order, hooks in index order
Since these hooks always have a valid "index", no need to devise a default
Add some tests
2019-05-09 19:16:53 -06:00
Jacob Beck
30b7407597 if the first byte of a csv seed is a BOM, strip it before passing it to agate 2019-05-09 17:56:05 -06:00
Jacob Beck
58bf73d4bf Update core/dbt/task/run.py
Co-Authored-By: Drew Banin <drew@fishtownanalytics.com>
2019-05-09 14:30:30 -06:00
Drew Banin
ec61073560 update plugin readmes 2019-05-09 15:58:44 -04:00
Drew Banin
b0f81edf96 0.13.1a2 bump, include README.md in pypi description 2019-05-09 15:14:49 -04:00
Jacob Beck
cc8ef47747 Merge pull request #1436 from fishtown-analytics/feature/dbt-ls
dbt ls command (#467)
2019-05-09 12:55:06 -06:00
Jacob Beck
5d05bf0aba PR feedback 2019-05-09 12:54:38 -06:00
Drew Banin
1d18a54b5e Update CHANGELOG.md 2019-05-09 14:52:17 -04:00
Drew Banin
a4605ec844 Merge pull request #1450 from fishtown-analytics/0-13-1-docs
bump docs for 0.13.1
2019-05-09 14:46:03 -04:00
Drew Banin
9f58400ba8 bump docs for 0.13.1 2019-05-09 14:44:42 -04:00
Jacob Beck
ec8277b0e4 add color to hooks, give hooks names 2019-05-09 10:53:46 -06:00
Jacob Beck
8c7763acf6 Merge pull request #1445 from fishtown-analytics/fix/anonymous-tracking-no-hangs
Improve tracking failure handling (#1063)
2019-05-09 10:47:06 -06:00
Jacob Beck
8e426e60c9 remove completed TODO 2019-05-09 10:40:46 -06:00
Jacob Beck
3a7f931a3a Merge pull request #1428 from fishtown-analytics/fix/strip-url-trailing-dotgit
On mostly-duplicate git urls, pick whichever came first (#1084)
2019-05-08 19:33:07 -06:00
Drew Banin
314ca6c361 Update core/dbt/tracking.py
Co-Authored-By: beckjake <beckjake@users.noreply.github.com>
2019-05-08 12:40:33 -06:00
Jacob Beck
12f0887d28 PR feedback
remove incorrect comment
fix an issue where we looked deps up by the wrong key
add a test
2019-05-08 10:24:24 -06:00
Jacob Beck
a986ae247d Improve tracking failure handling
Add a 1s timeout to tracking http calls
Add an on-failure handler that disables tracking after the first failure
2019-05-08 09:43:32 -06:00
Jacob Beck
9507669b42 PR feedback
Truncate long run-hooks, replacing newlines with spaces
Include run-hook status/timing in output
Clean up run-hook execution a bit
2019-05-07 09:53:37 -06:00
Jacob Beck
715155a1e9 Merge pull request #1441 from fishtown-analytics/feature/dynamic-target-paths
Add modules and tracking info to the configuration parsing context (#1320)
2019-05-07 08:00:11 -06:00
Jacob Beck
32c5679039 PR Feedback
Fixed error logging to display errors in dbt ls
Add models flag
Make all of models, select, exclude have a metavar of 'SELECTOR' for -h
2019-05-07 07:57:06 -06:00
Jacob Beck
89d211b061 Merge pull request #1442 from fishtown-analytics/fix/no-source-links
Only link schemas with executable things in them
2019-05-06 14:27:18 -06:00
Jacob Beck
f938fd4540 fix a test bug
dbt doing two things in the same second incorrectly failed the test
2019-05-06 12:24:40 -06:00
Jacob Beck
61e4fbf152 add a test 2019-05-06 11:27:16 -06:00
Jacob Beck
fda38e7cbb Only link schemas with executable things in them 2019-05-06 10:46:10 -06:00
Jacob Beck
c0a3b02fb4 Add modules and tracking info to the config parsing context
Add a test to make sure setting the target path works properly
2019-05-03 10:45:09 -06:00
Jacob Beck
80482aae34 add on-run-start/on-run-end hook logging 2019-05-03 08:00:08 -06:00
Jacob Beck
c19085862a suppress some cache logger noise 2019-05-03 07:59:33 -06:00
Connor McArthur
9672d55c1e Bump version: 0.13.0 → 0.13.1a1 2019-05-03 09:22:37 -04:00
Jacob Beck
e043643a54 Add a new ListTask, and 'dbt list'/'dbt ls'
All tasks now have a 'pre_init_hook' classmethod, called by main
 - runs after args are parsed, before anything else
2019-05-02 08:53:11 -06:00
Connor McArthur
ade108f01c changelog 2019-05-02 10:32:03 -04:00
Jacob Beck
6b08fd5e8d Merge pull request #1432 from convoyinc/dev/0.13.1
Add MaterializedView relation type, update Snowflake adapter
2019-05-02 07:27:11 -06:00
Jacob Beck
3c8bbddb5f Merge pull request #1413 from fishtown-analytics/feature/stub-adapter-in-parsing
Stub out methods at parse time
2019-05-01 10:39:49 -06:00
Jacob Beck
4c02b4a6c3 Make an stderr handler available as well and provide a way to swap between them 2019-05-01 07:11:29 -06:00
Adrian Kreuziger
786726e626 Add MaterializedView relation type, update Snowflake adapter 2019-04-30 09:54:37 -07:00
Drew Banin
1f97fe463e Merge branch 'dev/wilt-chamberlain' into snowflake_create_or_replace 2019-04-30 12:27:25 -04:00
Jacob Beck
5a3e3ba90f Merge pull request #1402 from fishtown-analytics/fix/quote-databases-properly
Quote databases properly (#1396)
2019-04-30 10:15:37 -06:00
Jacob Beck
154aae5093 Merge pull request #1410 from fishtown-analytics/feature/test-severity
Add a "severity" for tests (#1005)
2019-04-30 10:15:15 -06:00
Jacob Beck
3af88b0699 Update PackageListing to handle git packages having or not having .git
First write wins, on the assumption that all matching URLs are valid
2019-04-30 10:12:50 -06:00
Jacob Beck
0fb620c697 Merge pull request #1416 from fishtown-analytics/feature/strict-undefined
make undefined strict when not capturing macros (#1389)
2019-04-30 09:39:29 -06:00
Jacob Beck
7d66965d0b Merge branch 'dev/wilt-chamberlain' into feature/stub-adapter-in-parsing 2019-04-30 08:40:29 -06:00
Jacob Beck
acca6a7161 Merge pull request #1420 from fishtown-analytics/fix/postgres-text-types
Fix postgres text handling (#781)
2019-04-30 08:35:07 -06:00
Jacob Beck
ad2f228048 Merge pull request #1429 from fishtown-analytics/fix/vars-in-disabled-models
Fix: missing vars in disabled models fail compilation (#434)
2019-04-30 08:34:28 -06:00
Drew Banin
3a7dcd9736 Merge branch 'dev/wilt-chamberlain' into snowflake_create_or_replace 2019-04-30 09:44:43 -04:00
Drew Banin
ca15b44d0f Update core/dbt/clients/jinja.py
Co-Authored-By: beckjake <beckjake@users.noreply.github.com>
2019-04-30 07:35:32 -06:00
Jacob Beck
bf9c466855 Handle warnings vs errors
raise errors in strict mode
log warnings on warning states
Add tests
2019-04-29 19:58:05 -06:00
Jacob Beck
abcbacaf69 Implement test failure severity levels
A small refactor to make test parsing easier to modify
add concept of test modifier kwargs, pass them through to config
plug the severity setting into test result handling
Update existing tests
Add integration tests
severity settings for data tests, too
2019-04-29 19:57:07 -06:00
Jacob Beck
ffceff7498 Merge branch 'dev/wilt-chamberlain' into feature/stub-adapter-in-parsing 2019-04-29 19:51:37 -06:00
Jacob Beck
25ac1db646 make undefined strict when not capturing
Fix a number of tests and some table/view materialization issues
2019-04-29 19:46:14 -06:00
Jacob Beck
c6d6dae352 PR feedback: Make a RedshiftColumn, make the RedshiftAdapter use it 2019-04-29 19:33:26 -06:00
Jacob Beck
aa4f771df2 Merge pull request #1419 from fishtown-analytics/feature/destroy-non-destructive
remove non-destructive mode (#1415)
2019-04-29 19:28:00 -06:00
Jacob Beck
4715ad9009 Merge pull request #1426 from fishtown-analytics/feature/allow-null-vars
Allow vars to be set to null and differentiate them from unset vars (#608)
2019-04-29 19:22:24 -06:00
Jacob Beck
a4e5a5ac78 Merge pull request #1425 from fishtown-analytics/fix/propagate-undefined-to-calls
Propagate ParserMacroCapture undefineds into the calls (#1424)
2019-04-29 18:53:27 -06:00
Jacob Beck
f587efde60 Merge pull request #1427 from fishtown-analytics/feature/better-schema-test-errors
Improve invalid test warnings/errors (#1325)
2019-04-29 18:33:54 -06:00
Jacob Beck
d57f4c54d8 at parse time, return None for any missing vars 2019-04-29 14:39:08 -06:00
Jacob Beck
b9c74e0b07 Improve invalid test warnings/errors 2019-04-29 14:08:39 -06:00
Jacob Beck
aebefe09b5 Allow vars to be set to null and differentiate them from unset vars 2019-04-29 13:32:42 -06:00
Jacob Beck
78c13d252e Propagate ParserMacroCapture undefineds into the calls
Fix an issue when users attempt to use the results of missing macro calls
Add a test
2019-04-29 08:33:10 -06:00
Jacob Beck
8270c85ffd Merge pull request #1421 from fishtown-analytics/fix/dbt-debug-connect
fix dbt debug connections (#1422)
2019-04-29 07:10:13 -06:00
Bastien Boutonnet
7a2279e433 move unique key workaround to snowflake macro 2019-04-28 09:59:17 -07:00
Bastien Boutonnet
3ef519d139 todo and comments clean up 2019-04-28 07:43:16 -07:00
Bastien Boutonnet
85eac05a38 cleaner select 2019-04-28 07:33:49 -07:00
Bastien Boutonnet
8af79841f7 remove non-destructive logic 2019-04-28 07:31:14 -07:00
Bastien Boutonnet
afe236d9ac cleaning up some commented out stuff 2019-04-27 10:25:26 -07:00
Bastien Boutonnet
90f8e0b70e Revert "Revert "Merge branch 'dev/wilt-chamberlain' into snowflake_create_or_replace""
This reverts commit 4f62978de5.
2019-04-27 10:18:30 -07:00
Bastien Boutonnet
0432c1d7e3 conflic resolve 2019-04-27 10:11:59 -07:00
Bastien Boutonnet
08820a2061 fixing my jetlagged introduced bugs 2019-04-27 08:15:23 -07:00
Bastien Boutonnet
4f62978de5 Revert "Merge branch 'dev/wilt-chamberlain' into snowflake_create_or_replace"
This reverts commit 3ab8238cfb, reversing
changes made to 43a9db55b1.
2019-04-27 06:46:41 -07:00
Bastien Boutonnet
3ab8238cfb Merge branch 'dev/wilt-chamberlain' into snowflake_create_or_replace 2019-04-27 15:09:36 +02:00
Bastien Boutonnet
43a9db55b1 quick todo marker 2019-04-27 14:35:10 +02:00
Jacob Beck
08fdcad282 prevent getting this helper method collected as a test 2019-04-26 13:50:13 -06:00
Jacob Beck
7df6e0dc68 Fix postgres text handling
Fix incorrect maximum text field length on redshift
On postgres, pass through text without size information to fix archival of long text fields
Add a test that makes sure postgres archives work with very long text entries
Fix tests
Add a redshift test
2019-04-26 13:50:03 -06:00
Bastien Boutonnet
5c1c5880b6 more explicit comments and quick formatting 2019-04-26 20:10:12 +02:00
Bastien Boutonnet
f99efbf72e remove references to temp and backup relations 2019-04-26 20:04:21 +02:00
Jacob Beck
e90b60eecd fix dbt debug connections 2019-04-26 11:14:24 -06:00
Jacob Beck
1205e15be2 remove non-destructive mode 2019-04-26 10:14:27 -06:00
Jacob Beck
32f39f35f6 Merge pull request #1417 from fishtown-analytics/feature/use-pytest
nosetest -> pytest
2019-04-26 08:15:21 -06:00
Bastien Boutonnet
9591b86430 (PR fdbk) rm extra macro 2019-04-26 12:43:11 +02:00
Jacob Beck
b54c6023eb on windows, run via "python -m pytest" 2019-04-25 22:13:12 -06:00
Jacob Beck
00ba5d36b9 nosetest -> pytest 2019-04-25 21:51:43 -06:00
Jacob Beck
89eeaf1390 Merge branch 'dev/wilt-chamberlain' into feature/stub-adapter-in-parsing 2019-04-25 21:48:02 -06:00
Jacob Beck
3f18b93980 Merge pull request #1398 from fishtown-analytics/feature/archive-no-create-existing-schema
archives: do not create existing schema (#758)
2019-04-25 21:42:58 -06:00
Jacob Beck
96cb056ec9 Merge pull request #1361 from fishtown-analytics/feature/archive-blocks-as-regex
Feature/archive blocks
2019-04-25 21:42:20 -06:00
Jacob Beck
1042f1ac8b fix some merged-in flake8 failures 2019-04-25 14:58:29 -06:00
Jacob Beck
dd232594a5 Merge branch 'dev/wilt-chamberlain' into feature/archive-blocks-as-regex 2019-04-25 14:48:49 -06:00
Jacob Beck
5762e5fdfb Merge pull request #1406 from fishtown-analytics/fix/run-operation-return-codes
Fix run operation return codes (#1377)
2019-04-25 08:19:11 -06:00
Jacob Beck
0f1c154a1a create a decorator for stubbing out methods at parse time
Includes some unit tests
Update integration tests to handle the fact that sometimes we now fail at runtime
2019-04-24 14:36:16 -06:00
Jacob Beck
ad1fcbe8b2 Merge pull request #1412 from fishtown-analytics/fix/insensitive-list-schemas
use ilike instead of = for database when listing schemas (#1411)
2019-04-24 12:02:04 -06:00
Jacob Beck
877440b1e6 use ilike instead of = for database when listing schemas 2019-04-24 10:50:14 -06:00
Jacob Beck
ca02a58519 PR feedback: Add a clear_transaction call 2019-04-23 20:25:18 -06:00
Jacob Beck
2834f2d8b6 update test.env.sample 2019-04-23 19:39:44 -06:00
Jacob Beck
cc4f285765 if the schema exists, do not try to create it 2019-04-23 14:59:50 -06:00
Jacob Beck
2efae5a9c3 give bigquery list_schemas/check_schema_exist macros 2019-04-23 14:59:50 -06:00
Jacob Beck
416cc72498 Implmement check_cols
Contracts: some anyOf shenanigans to add support for check_cols
Macros: split apart archive selection, probably too much copy+paste
Legacy: Archive configs now include a "timestamp" strategy when parsed from dbt_project.yml
Add integration tests
fix aliases test
Unquote columns in archives
handle null columns
attr -> use_profile
2019-04-23 14:34:24 -06:00
Jacob Beck
d66584f35c Update jsonschema and go from Draft 4 to Draft 7 2019-04-23 14:34:24 -06:00
Jacob Beck
2b80d7ad8d move column-related things into adapters where they belong 2019-04-23 14:34:24 -06:00
Jacob Beck
be3445b78a get archive blocks working
tests
fix event tracking test
Fix print statements
make archives not inherit configs from models
archive now uses the name/alias properly for everything instead of target_table
skip non-archive blocks in archive parsing instead of raising
make archives ref-able
 - test for archive ref, test for archive selects
raise a more useful message on incorrect archive targets
add "--models" and "--exclude" arguments to archives
 - pass them through to selection
 - change get_fqn to take a full node object, have archives use that so selection behaves well
 - added tests

Improve error handling on invalid archive configs

Added a special archive-only node that has extra config restrictions
add tests for invalid archive config
2019-04-23 14:33:44 -06:00
Jacob Beck
ab63042dfa archive-paths support, wire up the block parser
raise on non-archive during parsing
break archive materialization
2019-04-23 14:33:12 -06:00
Jacob Beck
af8622e8ff flake8, pep8, unit tests 2019-04-23 14:32:23 -06:00
Jacob Beck
53d083ec58 fix this for real in a way that will make me not break it again 2019-04-23 14:30:59 -06:00
Jacob Beck
32f74b60ef Merge pull request #1408 from fishtown-analytics/fix/remove-sql_where-from-subquery
Remove sql_where
2019-04-23 07:59:35 -06:00
Jacob Beck
0885be1dc0 Remove sql_where, removing an unnecessary subquery in the process 2019-04-22 11:39:54 -06:00
Jacob Beck
8b58b208ca add quote policy to Relation.create calls 2019-04-22 11:29:07 -06:00
Jacob Beck
3188aeaac4 More tests
Add an already_exists call to a test that requires an alternative database
Make the alternative database on snowlfake be one that must be quoted
2019-04-22 11:29:07 -06:00
Bastien Boutonnet
e83edd30de fixme/todo regarding non destructive flag 2019-04-22 16:02:03 +02:00
Bastien Boutonnet
04333699a0 remove testing logging messages 2019-04-22 15:49:59 +02:00
Bastien Boutonnet
95c9f76e32 remove snowflake__ direct call in incremental 2019-04-22 11:10:02 +02:00
Bastien Boutonnet
2830b6a899 make default create or replace macro to allow snowflake adapter pick it up 2019-04-22 11:06:58 +02:00
Bastien Boutonnet
54c02ef4b4 some logging and temp call workaround 2019-04-22 01:32:07 +02:00
Bastien Boutonnet
dacce7c864 fix insert cols call and temp workaround call of snowflake 2019-04-22 01:27:25 +02:00
Jacob Beck
08c5f9aed8 no automatic transactions 2019-04-21 16:43:52 -06:00
Bastien Boutonnet
fb26ce5c24 Merge branch 'snowflake_create_or_replace' of github.com:bastienboutonnet/dbt into snowflake_create_or_replace 2019-04-20 19:32:40 +01:00
Bastien Boutonnet
91d869e61a revert test 2019-04-20 19:29:01 +01:00
Bastien Boutonnet
d168bdd0c8 Merge branch 'snowflake_create_or_replace' of github.com:bastienboutonnet/dbt into snowflake_create_or_replace 2019-04-20 19:25:41 +01:00
Bastien Boutonnet
6a104c1938 test 2019-04-20 19:25:16 +01:00
Bastien Boutonnet
2d5525e887 add some logging 2019-04-20 19:09:46 +01:00
Bastien Boutonnet
a35ad186e3 implement insert when no unique key and full refresh solution 2019-04-20 18:13:37 +01:00
Jacob Beck
dd469adf29 Merge pull request #1407 from fishtown-analytics/feature/wrap-models-in-jinja-tags
add a flag to wrap models/tests in jinja blocks
2019-04-18 11:48:22 -06:00
Jacob Beck
4ffc5cbe6a PR feedback 2019-04-18 11:17:26 -06:00
Jacob Beck
f3449dcfcb add a flag to wrap models/tests in jinja blocks, to see what happens 2019-04-18 11:17:26 -06:00
Jacob Beck
4e8c7b9216 add a test and a comment about an odd edge case in do-block parsing 2019-04-18 09:27:52 -06:00
Jacob Beck
473078986c PR feedback: run_unsafe -> _run_unsafe 2019-04-18 09:05:23 -06:00
Jacob Beck
5b74c58a43 add tests 2019-04-17 11:26:33 -06:00
Jacob Beck
a72a4e1fcb Acquire a connection before executing the macro, and commit after 2019-04-17 11:26:33 -06:00
Jacob Beck
13dd72029f run-operation fixes
Make dbt run-operation actually function at all with the RPC changes
On exceptions that occur outside of actual macro execution, catch them and return failure appropriately
2019-04-17 11:26:32 -06:00
Jacob Beck
fc4fc5762b add a pretty janky data test to expose quoting issues 2019-04-16 13:51:34 -06:00
Jacob Beck
d515903172 make PostgresAdapter.verify_database handle names that resolve to the same thing 2019-04-16 13:51:33 -06:00
Jacob Beck
97a6a51bec Quote databases when we list them
Fix a copy+paste error that broke database quoting configuration
2019-04-16 13:51:33 -06:00
Bastien Boutonnet
9222c79043 implement create or replace in table mater 2019-04-16 17:53:52 +02:00
Bastien Boutonnet
38254a8695 make create or replace snowflake macro 2019-04-16 17:53:31 +02:00
Jacob Beck
9b1aede911 Fix a number of bugs
After we find the start of a comment block, advance immediately
 - this is so we do not mistake "{#}" as both open and close of comment
support do/set statements (no {% enddo %}/{% endset %})
fix some edge-case bugs around quoting
fix a bug around materialization parsing
2019-04-16 09:52:27 -06:00
Jacob Beck
ac71888236 add tests
test for comment blocks where the first character of the comment is "}"
do/set tests
an even trickier test case
2019-04-16 09:45:28 -06:00
Jacob Beck
3f31b52daf Merge pull request #1388 from fishtown-analytics/fix/bigquery-missing-model-names
pass the model name along in get_relations
2019-04-11 07:28:22 -06:00
Connor McArthur
e3230aad55 Merge pull request #1391 from fishtown-analytics/add-logging-to-dbt-clean
Add logging to dbt clean
2019-04-08 09:31:20 -05:00
emilielimaburke
ac40aa9b02 fix merge conflict 2019-04-08 09:27:08 -05:00
Emilie Lima Schario
fa480e61a1 update docs with steps 2019-04-08 09:25:14 -05:00
Jacob Beck
c19644882b add unit tests 2019-04-05 10:22:54 -06:00
Jacob Beck
e29eccd741 pass the model name along in get_relations 2019-04-04 17:50:42 -06:00
Jacob Beck
4dd80567e1 Merge pull request #1380 from fishtown-analytics/feature/rpc-ps
RPC server task management
2019-04-03 11:46:37 -06:00
Jacob Beck
2654c79548 PR feedback 2019-04-03 10:01:36 -06:00
Jacob Beck
3b357340fd skip the timing assert on python 2.x 2019-04-02 15:15:43 -06:00
Jacob Beck
6c8e74bac9 tests, fight with (test-only?) deadlocks
when adding more threads stops helping, add more sleeps
2019-04-02 15:15:37 -06:00
Jacob Beck
182714b6b8 handle ctrl+c during parsing, etc 2019-04-02 15:14:28 -06:00
Jacob Beck
8410be848f fix the methods list 2019-04-02 15:14:28 -06:00
Jacob Beck
3f9b9962c3 add "ps" and "kill" commands, and track tasks in flight
proper cancel support
Refactor rpc server logic a bit
fix an issue in query cancelation where we would cancel ourselves
fix exception handling misbehavior
2019-04-02 15:14:28 -06:00
Jacob Beck
ec1f4bc33d fix bad add_query calls
also fix unit tests
2019-04-02 15:14:28 -06:00
Jacob Beck
f2a0d36b34 when a dbt RuntimeException is raised inside the exception handler, re-raise it instead of wrapping it 2019-04-02 15:14:28 -06:00
Jacob Beck
fc2b86df4f rearrange some path handling in the rpc server task 2019-04-02 15:14:28 -06:00
Jacob Beck
0cd0792b65 Merge pull request #1378 from fishtown-analytics/ci/add-build-tag
build on all dev branches, build on any branch named starting with "pr/"
2019-04-02 14:27:27 -06:00
Jacob Beck
122ee5ab7d Merge pull request #1381 from fishtown-analytics/fix/report-compiled-node-on-error
Fix/report compiled node on error
2019-04-02 14:24:32 -06:00
Darren Haken
8270ef71b2 Merge branch 'dev/wilt-chamberlain' of github.com:fishtown-analytics/dbt into dev/stephen-girard 2019-04-01 14:31:33 +01:00
Darren Haken
d59a13079f Merge remote-tracking branch 'upstream/dev/stephen-girard' into dev/stephen-girard 2019-04-01 14:30:59 +01:00
Jacob Beck
ed59bd22f3 actually override macros...
fix macro overrides
fix tests that masked the broken macro overrides
2019-03-29 09:03:54 -06:00
Jacob Beck
8d32c870fc attach nodes to exceptions and pass compiled sql into the error messages 2019-03-29 08:19:30 -06:00
Jacob Beck
bea2d4fb34 Merge pull request #1373 from fishtown-analytics/fix/rpc-ephemeral-clean
Support ephemeral nodes (#1368)
2019-03-27 13:02:29 -06:00
Jacob Beck
fcb97bf78a Merge pull request #1363 from roverdotcom/feature/alias-macro
Adding a generate_alias_name macro
2019-03-27 08:19:34 -06:00
Jacob Beck
161a78dc23 build on all dev branches or branches that start with "pr/" 2019-03-26 15:52:12 -06:00
Brandyn Lee
4b7bddb481 generate_macro update 2019-03-26 13:32:56 -07:00
Brandyn Lee
0879b1b38b A more verbose function 2019-03-26 13:29:08 -07:00
Brandyn Lee
8bf81a581a Fix integration tests 2019-03-26 13:29:08 -07:00
Brandyn Lee
759da58648 Fix unit test, node.name 2019-03-26 13:29:08 -07:00
Brandyn Lee
ec4a4fe7df Add second integration test and pass parsed_node through macro 2019-03-26 13:29:08 -07:00
Brandyn Lee
4dedd62aea Minimal testing 2019-03-26 13:29:08 -07:00
Brandyn Lee
cf4030ed94 Get unit testing working 2019-03-26 13:29:08 -07:00
Brandyn Lee
35df887307 Get get_customer_alias working. 2019-03-26 13:29:08 -07:00
Brandyn Lee
d41adaa51b Copying all the customer schema code 2019-03-26 13:29:08 -07:00
Jacob Beck
9373a45870 add integration test 2019-03-26 07:28:37 -06:00
Jacob Beck
a2db88c9c3 attach nodes to results during processing, but not for serialization 2019-03-26 07:25:35 -06:00
Jacob Beck
a26d7bf9e8 pass along the compiled manifest instead of pitching it away 2019-03-26 07:25:34 -06:00
Jacob Beck
4225047b06 Merge pull request #1375 from fishtown-analytics/feature/inline-macros-models
Support macros in execute/compile tasks (#1372)
2019-03-26 07:16:51 -06:00
Jacob Beck
0a9ed9977b include top-level raw/comment blocks 2019-03-25 17:30:49 -06:00
Jacob Beck
fc1b4ce88e add all non-macro toplevel blocks to the sql so things like "if" work 2019-03-25 15:30:21 -06:00
Jacob Beck
6295c96852 get macros from the body of the sql data instead of a separate parameter 2019-03-25 15:30:21 -06:00
Jacob Beck
73418b5c16 make the block lexer include raw toplevel data 2019-03-25 15:30:21 -06:00
Jacob Beck
98d530f0b1 Jinja block parsing/lexing implemented 2019-03-25 14:20:24 -06:00
Drew Banin
e45ed0ed8c Merge pull request #1366 from emilieschario/fix_profile_link_issue_1344
Update broken profiles.yml link
2019-03-24 12:07:58 -04:00
emilielimaburke
fc04e2db89 updates link 2019-03-24 11:59:20 -04:00
Drew Banin
cfaacc5a76 Merge pull request #1364 from fishtown-analytics/fix/redshift-test-env-password
fix sample test env password
2019-03-23 14:43:24 -04:00
Drew Banin
b91c3edb16 fix sample test env password 2019-03-21 19:29:03 -04:00
Connor McArthur
0a503a0bed try to resolve merge 2019-03-21 15:13:11 -04:00
Connor McArthur
015e4d66b2 resolve connection naming conflict 2019-03-21 15:12:00 -04:00
Connor McArthur
da4c135e23 Merge pull request #1359 from fishtown-analytics/dev/stephen-girard
0.13.0 (Stephen Girard)
2019-03-21 13:26:58 -04:00
Drew Banin
588851ac1c Update CHANGELOG.md 2019-03-21 11:16:29 -04:00
Drew Banin
2b7d7061f9 Update CHANGELOG.md 2019-03-21 11:15:03 -04:00
Connor McArthur
24bc3b6d76 Bump version: 0.13.0rc1 → 0.13.0 2019-03-21 11:05:28 -04:00
Connor McArthur
cd52a152f6 update RELEASE instructions, add script to build all the source distributions 2019-03-21 11:04:51 -04:00
Connor McArthur
3ecf8be873 add RC bugfixes 2019-03-21 10:50:03 -04:00
Connor McArthur
f38466db11 Merge pull request #1360 from fishtown-analytics/0.13.0-changelog
0.13.0 changelog
2019-03-21 10:40:34 -04:00
Drew Banin
3b8d5c0609 Merge pull request #1356 from fishtown-analytics/fix/concurrent-transaction-fresness
fix for error-ed out transactions on redshift
2019-03-20 16:49:14 -04:00
Drew Banin
60539aaa56 fix for errored out transactions on redshift 2019-03-20 14:55:54 -04:00
Drew Banin
6d53e67670 Update parsed.py
pep8
2019-03-19 15:37:13 -04:00
Drew Banin
9771e63247 Merge branch 'dev/stephen-girard' into dev/stephen-girard 2019-03-19 15:15:02 -04:00
Jacob Beck
02c9bcabe0 Merge pull request #1355 from fishtown-analytics/fix/out-of-order-execution-on-model-select
Fix out of order execution on model select (#1354)
2019-03-19 07:34:52 -06:00
Jacob Beck
69c8a09d43 Use the transitive closure to calculate the graph
Don't maintain the links manually while removing nodes
Just take the transitive closure and remove all nodes
2019-03-18 16:49:46 -06:00
Jacob Beck
9ae229a0d5 ugh, forgot to remove this I guess 2019-03-18 15:51:26 -06:00
Jacob Beck
38921fad17 tests 2019-03-18 15:48:22 -06:00
Jacob Beck
633858a218 When building the graph underlying the graph queue, preserve transitive dependencies while removing the skipped nodes 2019-03-18 15:08:51 -06:00
Drew Banin
70262b38f8 Merge pull request #1353 from fishtown-analytics/fix/issue-1352
possibly reckless fix for #1352
2019-03-18 09:30:46 -04:00
Jacob Beck
f96dedf3a9 redshift can just change this on you apparently 2019-03-16 12:56:10 -04:00
Drew Banin
1ce0493488 possibly reckless fix for #1352 2019-03-15 15:08:48 -04:00
Jacob Beck
027a0d2ee6 Merge pull request #1341 from fishtown-analytics/feature/rpc-improve-dbt-exceptions
RPC: Error handling improvements
2019-03-12 16:17:50 -06:00
Jacob Beck
9c8e08811b redshift can just change this on you apparently 2019-03-12 15:09:07 -06:00
Jacob Beck
c1c09f3342 Merge pull request #1348 from fishtown-analytics/feature/rpc-with-macros
RPC: macros
2019-03-12 14:00:30 -06:00
Drew Banin
05b82a22bc Merge pull request #1349 from fishtown-analytics/fix/new-logo
Use new logo
2019-03-11 21:33:45 -04:00
Drew Banin
067aa2ced0 replace logo 2019-03-11 21:32:40 -04:00
Drew Banin
f18733fd09 Add files via upload 2019-03-11 21:31:41 -04:00
Drew Banin
a981f657ec Merge pull request #1347 from fishtown-analytics/fix/warn-error-flag
re-add --warn-error flag
2019-03-11 20:48:15 -04:00
Jacob Beck
81426ae800 add optional "macros" parameter to dbt rpc calls 2019-03-11 18:25:17 -06:00
Drew Banin
4771452590 Merge pull request #1346 from fishtown-analytics/fix/on-run-hooks-in-tests
[Stephen Girard] fix for on-run- hooks running in tests
2019-03-11 20:07:46 -04:00
Drew Banin
10bfaf0e4b Update CHANGELOG.md 2019-03-11 19:56:57 -04:00
Drew Banin
9e25ec2f07 Update CHANGELOG.md 2019-03-11 19:55:57 -04:00
Drew Banin
90fb908376 re-add --warn-error flag 2019-03-11 19:42:14 -04:00
Drew Banin
a08c0753e7 fix for on-run- hooks running in tests 2019-03-11 13:01:22 -04:00
Jacob Beck
fc22cb2bf0 when encoding json, handle dates and times like datetimes 2019-03-08 13:28:44 -07:00
Jacob Beck
fbaae2e493 fix Python 2.7 2019-03-08 12:02:51 -07:00
Jacob Beck
c86390e139 use notice logging for "Found x models, ...", change a couple other levels 2019-03-08 10:54:50 -07:00
Jacob Beck
d890642c28 add NOTICE level logging, make log messages richer types 2019-03-08 10:54:10 -07:00
Jacob Beck
6620a3cd90 wrap all context-raised exceptions in node info
Fixes "called by <Unknown>"
2019-03-08 10:15:16 -07:00
Jacob Beck
7e181280b3 PR feedback: QueueMessageType class, remove extra assignments 2019-03-08 08:19:50 -07:00
Jacob Beck
53499e6b14 Merge branch 'dev/stephen-girard' into dev/wilt-chamberlain 2019-03-08 08:06:18 -07:00
Jacob Beck
3f948ae501 Error handling improvements
All dbt errors now have proper error codes/messages
The raised message at runtime ends up in result.error.data.message
The raised message type at runtime ends up in result.error.data.typename
result.error.message is a plaintext name for result.error.code
dbt.exceptions.Exception.data() becomes result.error.data
Collect dbt logs and make them available to requests/responses
2019-03-08 07:25:21 -07:00
Jacob Beck
2090887a07 Merge pull request #1336 from fishtown-analytics/feature/one-connection-per-thread
per-thread connections
2019-03-08 06:21:53 -07:00
Jacob Beck
a7bfae061c Merge pull request #1342 from fishtown-analytics/fix/temp-relations-from-modelname
Prefix temp relations with model name instead of alias (#1321)
2019-03-08 06:17:52 -07:00
Jacob Beck
fb1926a571 use model name instead of alias for temp tables to ensure uniqueness 2019-03-07 17:08:40 -07:00
Jacob Beck
c215158d67 PR feedback, clean up associated connection holding 2019-03-06 19:47:12 -07:00
Connor McArthur
74152562fe Bump version: 0.13.0a2 → 0.13.0rc1 2019-03-06 17:44:57 -05:00
Jacob Beck
e2af871a5a per-thread connections
parsing now always opens a connection, instead of waiting to need it
remove model_name/available_raw/etc
2019-03-06 12:10:22 -07:00
Jacob Beck
2ad116649a Merge branch 'dev/stephen-girard' into dev/wilt-chamberlain 2019-03-06 07:18:06 -07:00
Jacob Beck
03aa086e0b Merge pull request #1338 from fishtown-analytics/fix/jeb-snowflake-source-quoting
Fix snowflake source quoting / information_schema uses
2019-03-06 06:59:13 -07:00
Jacob Beck
a335857695 PR feedback 2019-03-05 21:47:37 -07:00
Jacob Beck
95a88b9d5d PR feedback 2019-03-05 18:12:19 -07:00
Jacob Beck
2501783d62 fix macro kwargs 2019-03-05 18:02:13 -07:00
Jacob Beck
7367f0ffbd Merge remote-tracking branch 'origin/snowflake-unquote-db-by-default-2' into fix/jeb-snowflake-source-quoting 2019-03-05 17:17:46 -07:00
Jacob Beck
088442e9c1 test fixes 2019-03-05 17:12:02 -07:00
Jacob Beck
ec14c6b2dc initial work, unit tests 2019-03-05 17:11:29 -07:00
Drew Banin
7eb033e71a fix incorrect schema filtering logic 2019-03-05 17:54:29 -05:00
Drew Banin
1a700c1212 Merge pull request #1330 from fishtown-analytics/fix/handle-unexpected-snapshot-values
[Stephen Girard] handle unexpected max_loaded_at types
2019-03-05 17:22:16 -05:00
Drew Banin
78fbde0e1f Merge branch 'dev/stephen-girard' of github.com:fishtown-analytics/dbt into fix/handle-unexpected-snapshot-values 2019-03-05 14:43:50 -05:00
Darren Haken
a30cc5e41e Add persist_docs to dbt's contract 2019-03-05 15:15:30 +00:00
Darren Haken
804a495d82 Fix BQ integration tests by adding persist_docs: {} 2019-03-05 14:51:00 +00:00
Jacob Beck
0a4eea4388 Merge pull request #1301 from fishtown-analytics/feature/rpc-tasks
RPC server (#1274)
2019-03-05 06:35:03 -07:00
Jacob Beck
8471ce8d46 Merge branch 'dev/wilt-chamberlain' into feature/rpc-tasks 2019-03-04 21:07:56 -07:00
Jacob Beck
f9b1cf6c1c Merge pull request #1314 from fishtown-analytics/azure-pipelines-2
Set up CI with Azure Pipelines
2019-03-04 21:06:04 -07:00
Drew Banin
22a2887df2 add missing import 2019-03-04 22:50:23 -05:00
Jacob Beck
02e88a31df Merge branch 'dev/wilt-chamberlain' into feature/rpc-tasks 2019-03-04 19:55:51 -07:00
Jacob Beck
98d5bc1285 PR feedback 2019-03-04 19:50:24 -07:00
Jacob Beck
436815f313 Merge branch 'dev/stephen-girard' into azure-pipelines-2 2019-03-04 19:50:16 -07:00
Drew Banin
328ce82bae fix unit tests 2019-03-04 20:43:37 -05:00
Drew Banin
d39a048e6e pr feedback 2019-03-04 20:39:31 -05:00
Drew Banin
67b56488d3 0.13.0 fixes around database quoting and rendering
- do not quote snowflake database identifiers by default
- do not find relations in source schemas in list_relations
- do not render database names in stdout if a custom database is not specified
2019-03-04 09:09:29 -05:00
Drew Banin
07397edd47 handle unexpected loaded_at field types 2019-03-03 17:06:34 -05:00
Darren Haken
1e3bdc9c06 Fix unit tests by adding persist_docs: {} 2019-03-02 17:36:28 +00:00
Darren Haken
12bfeaa0d3 Merge remote-tracking branch 'upstream/dev/stephen-girard' into dev/stephen-girard 2019-03-02 17:19:36 +00:00
Blake Blackwell
56801f9095 Adding changes based on Drew's recommendations 2019-03-02 05:28:09 -06:00
Drew Banin
54b0b38900 Merge pull request #1328 from fishtown-analytics/feature/operations-actually
Feature/operations actually
2019-03-01 11:27:42 -05:00
Drew Banin
e4660969cf Merge pull request #1324 from fishtown-analytics/fix/snowflake-rename
Fix for alter table ... rename statements on Snowflake
2019-02-28 14:59:57 -05:00
Drew Banin
1090a1612a add run-operation subtask 2019-02-28 11:16:14 -05:00
Drew Banin
f4baba8cc1 Merge pull request #1327 from fishtown-analytics/fix/relocate-event-json-schemas
relocate events dir back to root of repo
2019-02-28 10:12:44 -05:00
Darren Haken
28fa237f87 Add tests for the BQ description in the relations 2019-02-28 14:18:44 +00:00
Drew Banin
f19f0e8193 (fixes #1319) relocate events dir back to root of repo 2019-02-27 22:46:01 -05:00
Drew Banin
72d6ee2446 fix tests 2019-02-27 13:24:32 -05:00
Drew Banin
f8dfe48653 (fixes #1313) Show sources in resource count list during compilation 2019-02-26 13:18:30 -05:00
Drew Banin
9c9c0d991a (fixes #1316) Fix for alter table rename on Snowflake tables, added tests 2019-02-26 13:08:28 -05:00
Jacob Beck
8d2cb5fdf1 more newlines 2019-02-22 15:03:22 -07:00
Jacob Beck
1486796973 on windows, host on "localhost" instead of "database" 2019-02-22 14:07:59 -07:00
Jacob Beck
1300f8f49f Set up CI with Azure Pipelines
Add yml file
chunk up windows tests in tox.ini
add missing mock.patch calls
fix path issues in docs tests
better error messaging on mystery exceptions
ENOEXEC handling on Windows
Handle pipelines insisting on cloning with crlf line endings
set up postgres service for the postgres tests
remove appveyor
2019-02-22 13:44:37 -07:00
Jacob Beck
29e9c63e94 the RPC tests fail on windows, just skip them 2019-02-21 16:59:05 -07:00
Jacob Beck
4bda6769c5 fix an error handling bug I introduced 2019-02-21 15:29:10 -07:00
Jacob Beck
dc5c59b40b PR feedback
argument parsing fixes
change the table to a list of columns + list of rows
2019-02-20 15:06:05 -07:00
Jacob Beck
a90ef2504e PR feedback
Fix python 2.7
remove TODO
remove useless file I added by accident
close Pipe members
Give RPC its own logger, include remote addrs
2019-02-20 07:45:52 -07:00
Jacob Beck
0f3967e87d add tests, put them in sources to re-use all the source work 2019-02-19 14:41:48 -07:00
Jacob Beck
1a0df174c9 Implement the RPC server
- make tasks all have a "from_args" that handles initializing correct config type, etc
- make it possible to process a single node's refs at a time
- Make remote run/compile tasks + rpc server task, wire them up
- add ref() and source() support, and vestigial doc() support
- refactor results a bit to support new result behavior
- don't write to the filesystem on requests
- handle uniqueness issues
2019-02-19 10:43:27 -07:00
Darren Haken
a1b5375e50 Add AttributionError to except block 2019-02-19 15:59:12 +00:00
Darren Haken
101fd139c7 Add try catch for the updating config value.
This resolves the issue that `{{ config(persist_docs=true) }}`
would not raise a useful exception.
2019-02-19 15:41:51 +00:00
Darren Haken
25d5fb1655 Add persist_docs to project level settings.
Change `table_options` to have better error handling.
2019-02-19 12:18:09 +00:00
Darren Haken
e672042306 Merge remote-tracking branch 'upstream/dev/stephen-girard' into dev/stephen-girard 2019-02-19 11:46:04 +00:00
Connor McArthur
42ec3f9f06 add base setup.py to .bumpversion.cfg, bump the version 2019-02-18 16:04:24 -05:00
Connor McArthur
a4fd148a80 Bump version: 0.13.0a1 → 0.13.0a2 2019-02-18 16:02:37 -05:00
Drew Banin
e9927fb09c Merge pull request #1303 from fishtown-analytics/fix/0.13.0-tweaks
Fix/0.13.0 tweaks
2019-02-18 15:57:45 -05:00
Drew Banin
da31c9a708 pep8 2019-02-18 12:37:16 -05:00
Drew Banin
1be8cb8e91 add logging for registry requests 2019-02-18 12:34:20 -05:00
Drew Banin
a6ae79faf4 show db name in model result output 2019-02-18 12:30:19 -05:00
Drew Banin
61c345955e Merge pull request #1302 from fishtown-analytics/fix/source-timestamps
fix for invalid timestamps returned by source freshness cmd
2019-02-18 12:27:01 -05:00
Drew Banin
ebce6da788 fix tests 2019-02-18 10:00:15 -05:00
Blake Blackwell
9772c1caeb Adding incremental logic to Snowflake plugins 2019-02-17 19:28:46 -06:00
Darren Haken
5661855dcc Merge remote-tracking branch 'upstream/dev/stephen-girard' into dev/stephen-girard 2019-02-16 15:22:02 +00:00
Darren Haken
11319171be Add description for BQ tables and view relations.
Also make changes to table_options macro based on testing against a
real project.
2019-02-16 15:21:43 +00:00
Drew Banin
3134b59637 fix for invalid timestamps returned by source freshness cmd 2019-02-15 20:23:07 -05:00
Connor McArthur
dfb87dce38 fix long_description field in all the setup.pys 2019-02-14 15:46:23 -05:00
Connor McArthur
ce105d2350 Merge pull request #1299 from fishtown-analytics/rm-presto
remove presto adapter
2019-02-14 15:22:19 -05:00
Connor McArthur
e039397ab1 add setup.py for dbt 2019-02-14 14:34:43 -05:00
Connor McArthur
b54aadf968 rm presto adapter unit test 2019-02-14 14:30:55 -05:00
Connor McArthur
e9cf074b45 move presto docker stuff 2019-02-14 14:21:41 -05:00
Connor McArthur
c417c2011b remove presto from requirements.txt 2019-02-14 14:19:58 -05:00
Connor McArthur
2c3c3c9a84 remove presto adapter 2019-02-14 14:18:30 -05:00
Drew Banin
f546390221 Update CHANGELOG.md 2019-02-14 10:00:16 -05:00
Drew Banin
40034e056f Merge pull request #1298 from fishtown-analytics/feature/sources-in-docs-site
bump docs site index.html to use sources
2019-02-14 09:38:36 -05:00
Drew Banin
47e9896d54 bump docs site index.html to use sources 2019-02-14 09:38:03 -05:00
Jacob Beck
cda365f22a Merge pull request #1297 from fishtown-analytics/fix/widen-boto3-restrictions
fix: Widen botocore/boto3 requirements (#1234)
2019-02-14 07:18:59 -07:00
Jacob Beck
36e1252824 Widen botocore/boto3 requirements
Remove botocore/boto3 restrictions on the snowflake plugin
Increase minimum snowflake-connector-python to 1.6.12
 - 1.6.12 is the most recent boto3/botocore requirements change
Increase the upper bounds on boto3/botocre to match the redshift plugin
 - they match the upper bounds of snowflake-connector-python 1.6.12
2019-02-13 15:38:21 -07:00
Drew Banin
cf873d0fc5 Merge pull request #1293 from fishtown-analytics/fix/incremental-from-view
Fix for models which transition from views to incremental
2019-02-13 17:09:55 -05:00
Jacob Beck
026c50deb3 Merge pull request #1272 from fishtown-analytics/feature/source-freshness
Feature: source freshness (#1240)
2019-02-13 14:40:34 -07:00
Jacob Beck
c5138eb30f Merge pull request #1295 from fishtown-analytics/fix/dbt-seed-show
fix dbt seed --show (#1288)
2019-02-13 13:03:00 -07:00
Jacob Beck
0bd59998c0 Merge branch 'dev/stephen-girard' into feature/source-freshness 2019-02-13 12:14:57 -07:00
Jacob Beck
7cd336081f Merge branch 'dev/stephen-girard' into feature/source-freshness 2019-02-13 11:21:31 -07:00
Jacob Beck
47cc931b0f Merge pull request #1291 from fishtown-analytics/feature/enhanced-model-selection
Enhanced model selection syntax (#1156)
2019-02-13 11:08:19 -07:00
Jacob Beck
5462216bb3 fix dbt seed --show
Fix the invalid dict-stype lookup on the result of dbt seed --show
Add a --show to a test to make sure it doesn't crash dbt
2019-02-13 10:49:54 -07:00
Drew Banin
fe86615625 Merge pull request #1286 from fishtown-analytics/fix/error-url-v1-schema
fix error message url
2019-02-13 11:42:41 -05:00
Drew Banin
9a74abf4cc Merge pull request #1252 from fishtown-analytics/feature/snowflake-transient-tables
Support transient tables on Snowflake
2019-02-13 11:42:18 -05:00
Drew Banin
2847f690f1 Merge branch 'dev/stephen-girard' into fix/incremental-from-view 2019-02-13 11:27:25 -05:00
Drew Banin
2c94e9e650 (fixes #1292) Check for relation type in is_incremental()
If the target relation is a non-table (probably a view) then dbt
should return False from the is_incremental() macro. The
materialization will drop this relation before running the model
code as a `create table as` statement, so the incremental filter
would likely be invalid.
2019-02-13 11:26:40 -05:00
Jacob Beck
95ab2ab443 fix Snowflake tests 2019-02-13 09:05:07 -07:00
Jacob Beck
3dcfa2c475 Merge branch 'dev/stephen-girard' into feature/enhanced-model-selection 2019-02-13 08:59:03 -07:00
Jacob Beck
7bab31543e Merge pull request #1280 from fishtown-analytics/feature/werror
Add flag for raising errors on warnings (#1243)
2019-02-13 08:55:14 -07:00
Jacob Beck
08c4c2a8b5 PR feedback
print source name on pass/warn/error lines
distinguish 'error' vs 'error stale'
2019-02-13 08:44:23 -07:00
Jacob Beck
9fcad69bf4 tests 2019-02-13 08:17:19 -07:00
Jacob Beck
b406a536a9 initial selector work 2019-02-13 08:17:13 -07:00
Jacob Beck
5e8ab9ce4a Remove RunManager
Move some of RunManager into tasks
Move compile_node work into compilation
Move manifest work into the GraphLoader
Move the rest into the runners

Implement freshness calculations for sources

command: 'dbt source snapshot-freshness'
support for 4 adapters (no presto)
Integration tests
break up main.py's argument parsing
Pass the manifest along to freshness calculation

Results support for freshness

New freshness result contracts
Fix source result printing
Result contract cleanup
safe_run supports alternate result types
Fix tests to support changes in results

PR feedback:

- snowflake macro changed to always return utc
- no cte in collect_freshness
- remove extra optional arg
- fix the has_freshness check to examine if there is anything in freshness
- support error_after without warn_after and vice-versa
- snowflake: convert_timestamp -> convert_timezone

Update sources to be Relations

 - update contracts
 - add create_from_source
 - add create_from_source calls
 - fix tests

PR feedback

create_from_source forces quotes
default source schema/table from source/table names
snowflake quoting nonsense
also fix output: pass -> PASS
make seeding test 017 take 1m instead of 3m by using csv instead of sql

- source tweaks for the docs site
2019-02-13 10:07:37 -05:00
Darren Haken
562d47f12a Make changes as per review from @drewbanin 2019-02-12 15:21:29 +00:00
Drew Banin
ab6d4d7de5 use frozenset for adapter specific configs, add bq configs 2019-02-11 13:04:58 -05:00
Drew Banin
4b9ad21e9e fix error message url 2019-02-10 10:34:50 -05:00
Darren Haken
f4c233aeba #1031 Add macro to add table description from schema.yml for BQ 2019-02-10 13:38:40 +00:00
Jacob Beck
90497b1e47 give deprecation its own integration test 2019-02-08 08:22:12 -07:00
Jacob Beck
9b9319cbd0 fix bigquery
Remove unnecessary is_incremental() from test
Fix strict mode type check
2019-02-08 08:00:16 -07:00
Jacob Beck
fe948d6805 Add flag for raising errors on warnings
fix strict mode
fix some strict mode asserts
add internal WARN_ERROR support and a warn_on_error function
make everything that says "warning" go through warn_or_error
add --warn-error flag to main.py
remove sql_where from tests
replace drop-existing with full-refresh in tests
(re-)add integration tests for malformed schema/source tests and strict mode
2019-02-08 08:00:09 -07:00
Jacob Beck
b17d70679f Merge pull request #1277 from fishtown-analytics/fix/backwards-compatible-adapter-functions
0.13.0 backwards compatibility (#1273)
2019-02-08 06:54:36 -07:00
Jacob Beck
5290451a65 force upgrade pip on appveyor to see if that helps, idk 2019-02-07 19:51:20 -07:00
Jacob Beck
faadb34aff backwards compatibility work
create deprecation warning decorator for deprecated available methods
make already_exists just take schema/name again and direct users to get_relation
remove test that cannot fail (the deprecation does not exist!)
add a little deprecation warning check to test_simple_copy
2019-02-05 09:49:10 -07:00
Connor McArthur
314b4530d8 Merge pull request #1242 from fishtown-analytics/add-node-level-timing-info
add node level timing info to run_results.json and to tracking
2019-02-01 16:45:51 -05:00
Connor McArthur
843d342137 fix event tracking tests 2019-02-01 14:34:28 -05:00
Connor McArthur
f0981964f3 upgrade run_model iglu schema to 1-0-1 2019-02-01 13:59:53 -05:00
Jacob Beck
da409549d4 Merge pull request #1262 from fishtown-analytics/feature/source-table-selection
Add source selector support (#1256)
2019-01-30 08:22:25 -07:00
Connor McArthur
dc7ad2afc7 Merge pull request #1264 from fishtown-analytics/limit-exception-context
limit exception context scope
2019-01-29 13:33:00 -05:00
Connor McArthur
7e8ea51c1a remove timezone from datetime properties 2019-01-29 11:30:08 -05:00
Connor McArthur
343afc2374 typo: switch datetime in for pytz 2019-01-29 11:18:04 -05:00
Jacob Beck
c2a0a2092a source: now allows full-source selection, corresponding tests 2019-01-29 08:52:01 -07:00
Connor McArthur
f74a252b95 whitelist things from pytz, datetime 2019-01-29 10:50:18 -05:00
Connor McArthur
f5cfadae67 fix context exports for dbt.exceptions 2019-01-29 10:39:58 -05:00
Connor McArthur
7c1ecaf2b8 limit exception context scope 2019-01-29 09:44:58 -05:00
Jacob Beck
ea5edf20ba Add source selector support 2019-01-28 16:53:42 -07:00
Jacob Beck
c5f8cc7816 Merge pull request #1258 from fishtown-analytics/fix/dont-create-unused-schemas
Only create schemas for selected nodes (#1239)
2019-01-28 08:46:03 -07:00
Jacob Beck
60d75d26f0 Merge pull request #1260 from fishtown-analytics/fix/dbt-deps-foreign-langauges
Fix dbt deps on non-english shells (#1222)
2019-01-28 08:16:20 -07:00
Jacob Beck
f6402d3390 Merge pull request #1254 from fishtown-analytics/feature/source-tables
Add 'sources' to dbt (#814)
2019-01-28 08:05:22 -07:00
Jacob Beck
7b23a1b9a8 Make loader actually optional
Default to the empty string in ParsedSourceDefinitions
2019-01-25 07:37:09 -07:00
Jacob Beck
7714d12f7c fix "git clone" on windows
env replaces, it does not update, and windows really needs its env
2019-01-24 16:56:38 -07:00
Jacob Beck
fc813e40eb fix an old merge conflict 2019-01-24 15:36:50 -07:00
Jacob Beck
96578c3d2f add support for "env" parameter to system.run_cmd, make every git command that parses output use it 2019-01-24 15:36:50 -07:00
Jacob Beck
f47be0808f PR feedback
Add a test for model -> source
Cleaned up extra TODOs
Fixed missing variable in source_target_not_found
Changed a ref_target_not_found -> source_target_not_found
Better error messaging on invalid schema.yml files
Made loader optional
Make get_model_name_or_none accept just about anything
2019-01-24 15:25:35 -07:00
Drew Banin
b6d1e15a9f Merge pull request #1251 from fishtown-analytics/fix/redshift-iam-auth-0.13.0
fix for optional adapter configs with aliases
2019-01-24 10:44:47 -05:00
Jacob Beck
7d332aaa35 fix snowflake check_schema_exists macro 2019-01-24 06:20:26 -07:00
Jacob Beck
9ff8705cd7 fix an old merge conflict 2019-01-23 16:35:03 -07:00
Jacob Beck
76669995f6 Only create schemas for selected models (and the default schema)
- pass selected uids along to the before_run method for schema creation
 - only schemas used by selected nodes are created
 - fix some big issues with check_schema_exists (such as: it does not work)
 - integration test changes to test this
2019-01-23 16:35:03 -07:00
Jacob Beck
1079e9bfaf test enhancements, make failure to find sources clearer 2019-01-23 14:11:03 -07:00
Jacob Beck
67d85316ac make the test a bit sillier 2019-01-23 13:47:34 -07:00
Jacob Beck
7d41f4e22c contracts test, squash a nodes iteration bug 2019-01-23 13:37:32 -07:00
Jacob Beck
cdeb0d1423 Merge pull request #1246 from fishtown-analytics/feature/presto-notimplemented-error
presto notimplemented errors (#1228)
2019-01-23 11:18:36 -07:00
Jacob Beck
477699a102 Merge pull request #1245 from fishtown-analytics/feature/presto-adapter
Presto Adapter (#1106, #1229, #1230)
2019-01-23 11:18:17 -07:00
Jacob Beck
63047d01ab add a very basic integration test for sources 2019-01-23 09:48:01 -07:00
Jacob Beck
5e53e64df2 Add source configuration feature
- newly supported 'sources' section in schema.yml
   - like models - define tests, columns, column tests, descriptions
   - reference them via "source()" function (complements "ref()")
   - new 'sources' field on nodes (like 'refs')
 - many many contract and test updates to support it
 - fixed a long-standing bug with referencing tests by namespace
 - fix linecache on python 2.7
 - rendering now forces strings into their native format to avoid "u" prefixes in tests
 - fixup anything that iterates over nodes in the graph to accept ParsedSourceDefinitions
2019-01-23 09:48:01 -07:00
Connor McArthur
89207155fd update RELEASE process 2019-01-21 13:59:37 -05:00
Drew Banin
b7d9eecf86 Merge pull request #1232 from alexyer/feature/snowflake-ssh-login
Add support for Snowflake Key Pair Authentication
2019-01-21 11:29:18 -05:00
Drew Banin
8212994018 push adapter-specific configs to plugins, add transient config 2019-01-19 23:37:03 -05:00
Drew Banin
c283cb0ff4 fix for optional configs with aliases 2019-01-19 15:30:59 -05:00
Drew Banin
a34ab9a268 Merge pull request #1250 from fishtown-analytics/s-sinter-dbt-cloud-g
Update README.md
2019-01-19 15:08:06 -05:00
Drew Banin
2a2a2b26ef Update README.md 2019-01-19 15:07:24 -05:00
Jacob Beck
49fe2c3bb3 add NotImplemented archive implementation 2019-01-17 09:57:18 -07:00
Jacob Beck
170942c8be add presto tests for archives + incremental failing 2019-01-17 09:57:06 -07:00
Alexander Yermakov
438b3529ae Add support for Snowflake Key Pair Authentication
This PR adds support for Snowflake Key Pair Authentication.

Unit tests verify everything's getting passed through correctly.
2019-01-17 11:08:11 +02:00
Connor McArthur
2bff901860 pep8 2019-01-16 21:29:25 -05:00
Connor McArthur
d45fff3c5a move comment with relevant code 2019-01-16 21:24:12 -05:00
Connor McArthur
8843a22854 undo changes to tox.ini 2019-01-16 21:22:54 -05:00
Connor McArthur
d9ba73af44 Merge branch 'dev/stephen-girard' of github.com:fishtown-analytics/dbt into add-node-level-timing-info 2019-01-16 21:21:10 -05:00
Jacob Beck
491d5935cf make cancellation work, pretty ugly and no clue how to test it 2019-01-16 16:47:48 -07:00
Jacob Beck
85389afb3e Presto testo 2019-01-16 16:47:48 -07:00
Jacob Beck
16d75249c5 Implement the presto adapter
- seeds, views, tables, and ephemeral models all implemented
 - ConnectionManager methods and credentials
 - Adapter.date_function
 - macros
 - give presto a small chunk size for seeds
 - manually cascade drop_schema
 - stub out some stuff that does not exist in presto
 - stub out incremental materializations for now (delete is not useful)
 - stub out query cancellation for now
2019-01-16 16:47:48 -07:00
Jacob Beck
27842f4cff ran the create script 2019-01-16 16:47:48 -07:00
Jacob Beck
016afd4b2c more work on the create script 2019-01-16 16:47:48 -07:00
Jacob Beck
cdb0cbdca7 Some dbt-core fixes/changes to support the new adapter
- fix sql docstring, which is just wrong.
 - extract the default seed materialization to one that takes a chunk size param
 - add NUMBERS constant for all number types dbt should support inserting
 - update Column definition to allow for "varchar"
2019-01-16 16:47:43 -07:00
Jacob Beck
da2d7ea8c0 Create a docker-compose container system for presto testing 2019-01-16 16:46:56 -07:00
Jacob Beck
f6278d590a initial work on an adapter building script 2019-01-16 16:46:56 -07:00
Jacob Beck
3b0d14bd5d Merge pull request #1202 from fishtown-analytics/feature/dynamic-database-config
dynamic database config (#1183, #1204)
2019-01-16 16:46:33 -07:00
Connor McArthur
66d1f2099b Merge branch 'dev/stephen-girard' of github.com:fishtown-analytics/dbt into add-node-level-timing-info 2019-01-16 16:45:31 -05:00
Jacob Beck
1ae32c12ab Merge pull request #1226 from fishtown-analytics/fix/catch-everything
On unexpected errors in safe_run, do not raise (#1223)
2019-01-16 14:44:22 -07:00
Connor McArthur
c626de76ff fix test_events 2019-01-16 16:38:54 -05:00
Jacob Beck
c80792d713 Merge pull request #1207 from fishtown-analytics/feature/macros-for-everything
Convert all embedded adapter SQL into macros (#1204)
2019-01-16 14:33:02 -07:00
Connor McArthur
a16958e35d fix test_docs_generate integration test 2019-01-16 15:47:49 -05:00
Connor McArthur
3e4c75e41b add timing as a namedproperty of RunModelResult 2019-01-16 15:22:15 -05:00
Jacob Beck
1596174a36 give root grant, split up test 2019-01-16 12:49:39 -07:00
Jacob Beck
f4084f069a PR feedback from #1202
Postgres/Redshift permissions issues:
 - use pg_views/pg_tables to list relations
 - use pg_namespace to list schemas and check schema existence
Catalog:
 - Redshift: fix error, add database check
 - Snowflake: Use the information_schema_name macro
Snowflake/default: Quote the database properly in information_schema_name
2019-01-16 12:36:06 -07:00
Connor McArthur
aceee680c8 add thread id to run results 2019-01-16 12:40:08 -05:00
Connor McArthur
48c47bf11e clean up, fix tests 2019-01-16 12:21:38 -05:00
Connor McArthur
8783c013e5 add node level timing info to run_results.json and to tracking 2019-01-15 20:41:08 -05:00
Jacob Beck
70069f53b1 Move SQL previously embedded into adapters into macros
Adapters now store an internal manifest that only has the dbt internal projects
Adapters use that manifest if none is provided to execute_manifest
The internal manifest is lazy-loaded to avoid recursion issues
Moved declared plugin paths down one level
Connection management changes to accomadate calling macro -> adapter -> macro
Split up precision and scale when describing number columns so agate doesn't eat commas
Manifest building now happens in the RunManager instead of the compiler

Now macros:
  create/drop schema
  get_columns_in_relation
  alter column type
  rename/drop/truncate
  list_schemas/check_schema_exists
  list_relations_without_caching
2019-01-15 08:08:45 -07:00
Jacob Beck
bf665e1c14 Merge branch 'dev/stephen-girard' into feature/dynamic-database-config 2019-01-15 07:58:35 -07:00
Connor McArthur
e359a69b18 Merge pull request #1224 from convoyinc/adriank/add_snowflake_sso_support
Add support for Snowflake SSO authentication round 2
2019-01-09 15:53:49 -05:00
Adrian Kreuziger
c01caefac9 Add support for Snowflake SSO authentication 2019-01-08 16:40:30 -08:00
Jacob Beck
2653201fe1 do not re-raise 2019-01-08 14:53:48 -07:00
Jacob Beck
dadab35aee PR feedback
bigquery: naming/parameter sanity cleanup
postgres: never allow databases that aren't the default
postgres: simplify cache buliding since we know we'll only ever have one database
everything: parameter name change for execute_macro
everything: cache related bugfixes to casing
internal only: cross db/cross schema rename support in the cache
  - none of the adapters support it, but unit tests expose the behavior
tests: much more comprehensive cache tests
2019-01-08 11:36:00 -07:00
Jacob Beck
c218af8512 Point at the 0.13 branch of dbt-utils 2019-01-08 11:36:00 -07:00
Jacob Beck
874ead9751 Make config() accept database, add adapter-specifc aliasing
Add concept of aliasing for credentials/relations

All databases use database, schema, and identifier internally now:
 - Postgres/Redshift have 'dbname' aliased to database and pass to
    password
 - Bigquery has 'project' aliased to database and 'dataset' aliased to
    schema
 - Set default database include policy to True everywhere

config() calls accept aliases instead of canonical names

Remove unused functions and change others to accept Relations (see core/CHANGELOG.md)

Add catalog, etc support for multiple databases
2019-01-08 11:36:00 -07:00
Jacob Beck
1e5308db31 Merge remote-tracking branch 'origin/0.12.latest' into dev/stephen-girard 2019-01-08 11:32:20 -07:00
Connor McArthur
83c8381c19 Bump version: 0.12.2rc1 → 0.12.2 2019-01-07 14:14:41 -05:00
Connor McArthur
b5f5117555 Merge pull request #1220 from fishtown-analytics/dev/grace-kelly
Release: Grace Kelly (0.12.2)
2019-01-07 14:13:50 -05:00
Drew Banin
b0a4f5c981 Merge pull request #1216 from mikekaminsky/docs/dev-installation
Updates contributing doc with new local installation instructions
2019-01-07 13:25:14 -05:00
Drew Banin
1da50abe2f Merge pull request #1200 from fishtown-analytics/changelog/grace-kelly
Update CHANGELOG.md for Grace Kelly
2019-01-07 11:06:02 -05:00
Drew Banin
6d69ff0bda Update CHANGELOG.md
add links to docs
2019-01-07 11:04:39 -05:00
Jacob Beck
7179d135fa Merge pull request #1218 from fishtown-analytics/fix/lets-not-hang-on-errors
move cleanup into the executing thread (#1214)
2019-01-07 08:38:41 -07:00
Jacob Beck
3e4523e1ef On errors inside the thread, set a raise_next_tick flag
Threads that raise exceptions bypass the callback, which makes dbt hang.
Now threads don't raise during the callback, instead they set a flag.
The RunManager will check the flag during queue processing and raise if set.
Fix compilation failures so they raise properly.
2019-01-07 07:58:23 -07:00
Michael Kaminsky
69a65dd63f Updates contributing doc with new local installation instructions 2019-01-05 20:45:42 -06:00
Drew Banin
3c25a9b40d Merge pull request #1210 from fishtown-analytics/fix/dbt-init-command
do not try to remove remote after dbt init
2019-01-04 14:56:28 -05:00
Drew Banin
f43d9b5e88 pr feedback 2019-01-04 13:14:11 -05:00
Drew Banin
5826bc80bc (fixes #1209) do not try to remove remote after dbt init 2019-01-03 22:27:51 -05:00
Drew Banin
6563b03299 Merge pull request #1208 from fishtown-analytics/fix/repo-file-placement
move markdown files back into place
2019-01-03 18:13:53 -05:00
Drew Banin
406ff55c7d move markdown files back into place 2019-01-03 18:03:46 -05:00
Adrian
8882bbe617 Merge pull request #1 from fishtown-analytics/dev/stephen-girard
Merge upstream
2019-01-03 13:09:05 -08:00
Drew Banin
769a886b93 Merge pull request #1206 from fishtown-analytics/fix/windows-unicode-output-for-dbt-debug
Replace unicode characters with ascii strings
2019-01-03 13:27:38 -05:00
Jacob Beck
f2fc002f5c Merge pull request #1145 from fishtown-analytics/dbt-core
dbt core/plugin architecture (#1070, #1069)
2019-01-02 16:59:29 -07:00
Jacob Beck
836998c9e9 Merge branch 'dev/stephen-girard' into dbt-core 2019-01-02 11:30:31 -07:00
Drew Banin
f90a5b14ad (fixes #1201) Replace unicode characters with ascii strings 2019-01-02 10:51:50 -05:00
Drew Banin
6004bdf012 Merge pull request #1191 from brianhartsock/archive_character_columns
Ensure character columns are treated as string types.
2018-12-21 13:22:26 -05:00
Drew Banin
d7610a7c55 Merge pull request #1192 from patrickgoss/fix/postgres-dependency-query
Postgresql dependency query speedup
2018-12-21 13:21:01 -05:00
Drew Banin
2ecc1e06cf Merge pull request #1198 from vijaykiran/dev/grace-kelly
Print dbt version before every task is run
2018-12-21 13:16:08 -05:00
Drew Banin
dcc017d681 Merge pull request #1199 from vijaykiran/update-contributing-doc
Update environment setup in CONTRIBUTING.md
2018-12-21 13:14:36 -05:00
Drew Banin
ea401f6556 Update CHANGELOG.md 2018-12-21 13:07:19 -05:00
Vijay Kiran
09fbe288d8 Update environment setup in CONTRIBUTING.md
- Document command to activate the virtualenv
- Fix minor typo
2018-12-21 19:02:57 +01:00
Vijay Kiran
7786175d32 Print dbt version before every task is run
resolves  fishtown-analytics/dbt#1134
2018-12-21 17:58:35 +01:00
Jacob Beck
f558516f40 remove extra script 2018-12-20 08:41:04 -07:00
Jacob Beck
d8c46d94df fix the issue on bigquery where tests executed with a connection name of "master" 2018-12-20 08:40:13 -07:00
pgoss
f64e335735 split the relationship CTE in order to speed the dependency query on large dbs 2018-12-19 12:32:32 -05:00
Brian Hartsock
b263ba7df2 Ensure character columns are treated as string types. 2018-12-19 10:31:11 -05:00
Jacob Beck
d2a68d92a3 Merge pull request #1176 from fishtown-analytics/fix/remove-operations
Remove operations [#1117]
2018-12-18 12:23:46 -07:00
Jacob Beck
4cbff8e1a1 update changelog 2018-12-18 08:28:08 -07:00
Jacob Beck
33ffafc7d6 run_operation -> execute_macro 2018-12-18 08:01:59 -07:00
Jacob Beck
4780c4bb18 Split dbt into core and plugins 2018-12-14 11:17:41 -07:00
Jacob Beck
c61561aab2 Merge pull request #1178 from fishtown-analytics/feature/package-dbt-version-compatibility
package dbt version compatibility (#581)
2018-12-14 10:15:19 -08:00
Jacob Beck
9466862560 add require-dbt-version config item and --no-version-check flag, make dependency errors fail the run 2018-12-14 10:33:05 -07:00
Jacob Beck
931dd4e301 Merge branch 'dev/grace-kelly' into dev/stephen-girard 2018-12-14 10:16:40 -07:00
Jacob Beck
e52475cac7 Merge pull request #1186 from fishtown-analytics/fix/tracking
make tracking use the profile directory, and suppress errors (#1180)
2018-12-14 08:18:35 -08:00
Claire Carroll
afa9fc051e Merge pull request #1189 from fishtown-analytics/add-m-flag-for-tests
Add -m argument for dbt test
2018-12-14 11:15:51 -05:00
Claire Carroll
3cbf49cba7 Add m flag for tests 2018-12-13 16:46:06 -05:00
Jacob Beck
5deb7e8c2d everything about tracking is hard 2018-12-13 14:04:53 -07:00
Jacob Beck
2003222691 Merge branch 'dev/grace-kelly' into dev/stephen-girard 2018-12-13 12:58:44 -07:00
Jacob Beck
08913bf96b Merge pull request #1188 from fishtown-analytics/ipdb-dev-requirement
Add ipdb to dev requirements so it shows up in test docker containers
2018-12-13 11:48:09 -08:00
Jacob Beck
15c047077a Add ipdb to dev requirements so it shows up in test docker containers for debugging 2018-12-13 10:28:39 -07:00
Jacob Beck
260bcfd532 Merge branch 'dev/grace-kelly' into dev/stephen-girard 2018-12-13 09:07:16 -07:00
Claire Carroll
3deb295d29 Merge pull request #1147 from fishtown-analytics/cleanup-repo
Cleanup repo and update readme
2018-12-13 09:50:04 -05:00
Claire Carroll
f56ac542bc Merge branch 'dev/grace-kelly' into cleanup-repo 2018-12-13 09:46:25 -05:00
Claire Carroll
ebd6d3ef19 Update readme and cleanup repo 2018-12-13 09:43:38 -05:00
Jacob Beck
808ed75858 make tracking use the profile directory, and suppress errors 2018-12-12 16:01:42 -07:00
Connor McArthur
d2c704884e Bump version: 0.12.2a1 → 0.12.2rc1 2018-12-07 14:10:57 -05:00
Jacob Beck
91a5b1ce52 Merge branch 'dev/grace-kelly' into dev/stephen-girard 2018-12-05 12:14:54 -07:00
Jacob Beck
bec30efec5 Merge pull request #1174 from fishtown-analytics/feature/latest-version-from-pypi
use pypi for the latest version instead of git [#1122]
2018-12-05 11:56:44 -07:00
Drew Banin
2cd24cfa9e Merge pull request #1129 from fishtown-analytics/feature/better-incremental-models
(fixes #744) deprecate sql_where and provide an alternative
2018-12-05 12:52:45 -05:00
Jacob Beck
963fb84cb7 Merge pull request #1171 from fishtown-analytics/feature/make-debug-great
Improve dbt debug [#1061]
2018-12-05 10:52:03 -07:00
Connor McArthur
65729c4acc Bump version: 0.12.1 → 0.12.2a1 2018-12-05 12:15:50 -05:00
Jacob Beck
9b8e8ff17a more pr feedback 2018-12-05 10:12:57 -07:00
Jacob Beck
8ea9c68be0 PR feedback 2018-12-05 10:00:30 -07:00
Jacob Beck
282774cbdf Merge branch 'dev/grace-kelly' into dev/stephen-girard 2018-12-05 09:49:07 -07:00
Jacob Beck
eb504ae866 use pypi for the latest version instead of git 2018-12-05 09:29:30 -07:00
Jacob Beck
0f1520c392 re-add call to path_info 2018-12-05 09:23:11 -07:00
Drew Banin
009eaa3a59 pep8 2018-12-05 11:06:04 -05:00
Drew Banin
80232ff8e8 (fixes #744) deprecate sql_where and provide an alternative 2018-12-05 10:44:20 -05:00
Jacob Beck
9938af1580 pr feedback 2018-12-05 08:33:16 -07:00
Jacob Beck
6935a4a2e4 pr feedback: add pretty colors 2018-12-05 08:16:41 -07:00
Jacob Beck
eef5024354 Merge branch 'dev/grace-kelly' into feature/make-debug-great 2018-12-05 08:12:31 -07:00
Jacob Beck
bb6b469768 Merge pull request #1157 from fishtown-analytics/feature/graph-iteration
Handle concurrent job ordering via a graph iterator (#813)
2018-12-05 08:10:11 -07:00
Jacob Beck
d4c2dfedb2 dbt debug 2018-12-05 07:34:55 -07:00
Jacob Beck
3434ad9ca0 Bypass project loading in init, we do it anyway 2018-12-05 07:34:55 -07:00
Jacob Beck
937219dd91 add connection_info method to credentials for dbt debug 2018-12-05 07:34:55 -07:00
Jacob Beck
5bdd1ebdbc Merge pull request #1159 from fishtown-analytics/fix/use-root-generate-schema-name
get the default schema name generator from the root package [#801]
2018-12-05 07:32:52 -07:00
Jacob Beck
17f3f24652 Merge pull request #1168 from fishtown-analytics/fix/snowflake-drops-wrong-backup-type
drop the correct relation type on snowflake when building tables [#1103]
2018-12-05 07:32:25 -07:00
Jacob Beck
d80b37854a Merge pull request #1169 from fishtown-analytics/feature/log-warnings-on-missing-test-refs
log warnings on missing test refs [#968]
2018-12-05 07:32:02 -07:00
Jacob Beck
cbfa21ce45 PR feedback 2018-12-04 15:04:10 -07:00
Jacob Beck
3665e65986 reorganize some things to make us better detect disabled vs missing nodes 2018-12-04 11:16:46 -07:00
Jacob Beck
0daca0276b log about missing test ref targets at warning instead of debug level, include filepath 2018-12-04 09:33:24 -07:00
Jacob Beck
8769118471 drop the correct relation type on snowflake when building tables 2018-12-04 09:09:32 -07:00
Jacob Beck
863dbd2f4d Merge branch 'dev/grace-kelly' into fix/use-root-generate-schema-name 2018-12-03 16:36:46 -07:00
Jacob Beck
eb00b1a1b9 Merge pull request #1166 from fishtown-analytics/fix/trim-schema-whitespace
Trim schema whitespace (#1074)
2018-12-03 16:35:27 -07:00
Jacob Beck
953ba9b8eb Merge pull request #1163 from fishtown-analytics/fix/cdecimal-json-encoding
handle cdecimal.Decimal during serialization [#1155]
2018-12-03 16:29:53 -07:00
Jacob Beck
aa9d43a3fc Merge pull request #1164 from fishtown-analytics/fix/analysis-parsing-inside-raw
fix analysis to only compile sql once like model nodes [#1152]
2018-12-03 16:29:31 -07:00
Jacob Beck
9d5cbf7e51 strip the schema when there is extra whitespace 2018-12-03 14:35:32 -07:00
Jacob Beck
1744f21084 Refactor nodes into objects, make generate_schema_name use its own context
Allows us to cache the get_schema behavior without re-building it for every node
Allows us to special-case the generate_schema_name macro
2018-12-03 14:10:59 -07:00
Jacob Beck
adf05bd11d get the default schema name generator from the root package 2018-12-03 14:09:57 -07:00
Jacob Beck
3d205c3597 fix analysis to only compile sql once like model nodes, fix test 2018-12-03 13:06:52 -07:00
Jacob Beck
9f9b861769 handle cdecimal.Decimal during serialization as well as decimal.Decimal 2018-12-03 07:48:51 -07:00
Jacob Beck
6025d3d843 Merge pull request #1161 from fishtown-analytics/feature/m-is-for-models
add "-m" shorthand for models (#1160)
2018-11-30 15:33:12 -07:00
Jacob Beck
3cf03f3018 add "-m" shorthand for models 2018-11-30 15:25:29 -07:00
Jacob Beck
1c0caf9a81 Merge branch 'dev/grace-kelly' into dev/stephen-girard 2018-11-29 14:24:04 -07:00
Jacob Beck
4dc79f655f pr feedback, add many docstrings 2018-11-29 08:04:23 -07:00
Jacob Beck
5a06d57d7e use the manifest for node information and block/timeout arguments to get()
Assorted cleanup/test fixes
2018-11-28 15:14:45 -07:00
Jacob Beck
84ba7f57d0 fix many bugs, move RunBuilder back into the RunManager 2018-11-28 14:14:00 -07:00
Connor McArthur
8af30611f3 Merge pull request #1133 from fishtown-analytics/bigquery-smarter-delete-dataset
Bigquery: smarter delete_dataset
2018-11-28 09:40:14 -05:00
Jacob Beck
6e27476faa Merge pull request #1105 from fishtown-analytics/fix/hub-packaging
Fix the hub packaging so that it at least mostly works
2018-11-28 07:39:19 -07:00
Jacob Beck
acddb3b939 RunManager now operates on queues instead of lists 2018-11-27 15:26:16 -07:00
Jacob Beck
b6193be1ef add initial work on a graph-based priority queue 2018-11-27 15:26:16 -07:00
Jacob Beck
e7b1a093a3 Refactor the RunManager to build its runners as it iterates over the nodes instead of in advance 2018-11-27 15:26:16 -07:00
Jacob Beck
5be8c7f85f Merge pull request #1150 from fishtown-analytics/feature/config-muliple-calls
allow muliple config() calls (#558)
2018-11-26 12:16:06 -07:00
Jacob Beck
b751ed6c6a Merge pull request #1146 from fishtown-analytics/feature/hook-config-aliases
add pre_hook/post_hook aliases to config (#1124)
2018-11-26 11:55:22 -07:00
Jacob Beck
d16ca86782 Merge pull request #1151 from fishtown-analytics/feature/bq-profile-location
Add 'location' to google bigquery profile (#969)
2018-11-26 11:54:20 -07:00
Jacob Beck
b92d6692ce test that location gets passed along 2018-11-23 13:09:12 -07:00
Jacob Beck
dab2ff402f add a location field to the bigquery profile and pass it along to the google library if it is provided 2018-11-23 11:04:46 -07:00
Jacob Beck
51252b06b9 Add a test for overwrite/append 2018-11-23 10:12:24 -07:00
Jacob Beck
1fd84ad9d5 add unit tests 2018-11-23 10:12:24 -07:00
Jacob Beck
c4d6b2ed0f Delete dead code, move SourceConfig into the parser, allow multiple calls 2018-11-23 10:12:24 -07:00
Jacob Beck
71a239825a Merge branch 'dev/grace-kelly' into feature/hook-config-aliases 2018-11-23 08:11:31 -07:00
Jacob Beck
f72e0a8ddf Merge pull request #1148 from fishtown-analytics/feature/jinja-do-statement
Jinja expression statements (#1113)
2018-11-23 06:38:18 -07:00
Jacob Beck
069bc3a905 add do-syntax 2018-11-21 14:53:13 -07:00
Jacob Beck
0307d78236 PR feedback, tests around it 2018-11-21 12:29:19 -07:00
Jacob Beck
e543dc4278 add pre_hook/post_hook kwargs to config, add tests 2018-11-21 11:54:45 -07:00
Jacob Beck
029ef1795f Merge pull request #1020 from fishtown-analytics/profiler
dbt builtin profiler
2018-11-20 07:57:44 -07:00
Jacob Beck
12433fdba4 Merge branch 'dev/grace-kelly' into profiler 2018-11-20 07:08:49 -07:00
Jacob Beck
0a66adf707 Merge pull request #1142 from fishtown-analytics/feature/profiler-with-single-threaded
single-threaded mode
2018-11-19 19:06:30 -07:00
Jacob Beck
b5aab26c38 Merge pull request #1144 from fishtown-analytics/fix/run-repeatability-issues
Fix run repeatability/caching issues (#1138, #1139, #1140)
2018-11-19 19:04:44 -07:00
Drew Banin
416173a03c Merge pull request #1128 from fishtown-analytics/cache-macro-only-manifest
cache the macro manifest
2018-11-19 20:47:53 -05:00
Jacob Beck
e82361c893 lowercase the cache's view of schema/identifier, fix drop 2018-11-19 09:51:27 -07:00
Jacob Beck
7d3bf03404 fix casing expectations in unit tests 2018-11-19 09:51:27 -07:00
Jacob Beck
eb50b8319b Add integration tests with interesting model/case/quoting settings 2018-11-19 09:51:27 -07:00
Jacob Beck
cfd2d60575 stop dropping renames... 2018-11-19 09:51:27 -07:00
Jacob Beck
d4c3fb8261 fix schema cache casing bugs 2018-11-19 09:51:27 -07:00
Jacob Beck
7940b71ffe on dbt invocation, reset the adapters (to reset the cache) 2018-11-19 09:51:27 -07:00
Jacob Beck
6dd04b1a43 set cache logger propagation during invocation time instead of at log init time 2018-11-19 09:51:25 -07:00
Jacob Beck
399a6854c5 add a special flag to make dbt use map instead of the pool 2018-11-19 09:21:59 -07:00
Drew Banin
8eded7081c PR feedback (move comment) 2018-11-19 10:38:12 -05:00
Drew Banin
3bdebba18d Merge pull request #1123 from fishtown-analytics/fix/local-packages
fix for deps that depend on other local deps
2018-11-19 10:17:56 -05:00
Jacob Beck
8aab340a2a Merge branch 'dev/grace-kelly' into profiler 2018-11-19 08:13:34 -07:00
Drew Banin
0138228309 Merge branch '0.12.latest' into dev/grace-kelly 2018-11-15 16:05:58 -05:00
Connor McArthur
3912028318 bigquery: use delete_contents option in delete_dataset, remove unused drop_tables_in_schema 2018-11-15 10:45:04 -05:00
Connor McArthur
59cea11ef5 Bump version: 0.12.0 → 0.12.1 2018-11-15 09:28:22 -05:00
Connor McArthur
d9c12abd2d Merge pull request #1131 from fishtown-analytics/dev/0.12.1
dbt 0.12.1
2018-11-15 09:27:34 -05:00
Drew Banin
4b981caa53 first cut at caching macro manifest 2018-11-14 21:33:16 -05:00
Jacob Beck
735ff8831d Merge pull request #1110 from fishtown-analytics/fix/dbt-deps-issues
Fix dbt deps issues (#778 #994 #895)
2018-11-14 13:31:28 -07:00
Jacob Beck
6529c3edd3 Merge pull request #1109 from fishtown-analytics/feature/adapter-refactor
Feature/adapter refactor (#962, #963, #965, #1035, #1090)
2018-11-14 11:32:05 -07:00
Jacob Beck
8840996a30 user feedback: log download directory in dbt deps 2018-11-14 11:30:17 -07:00
Jacob Beck
d35e549dbf Handle cross-drive windows permissions issues by undoing git's readonly settings 2018-11-14 10:37:23 -07:00
Drew Banin
7195f07b3d fix for local deps that depend on other local deps 2018-11-13 21:55:25 -05:00
Jacob Beck
351542257e Merge branch 'dev/grace-kelly' into fix/dbt-deps-issues 2018-11-13 11:32:42 -07:00
Jacob Beck
8927aa8e02 clean up TODOs 2018-11-13 09:54:10 -07:00
Jacob Beck
717d1ed995 Merge pull request #1107 from fishtown-analytics/no-postgres-for-non-postgres
On non-postgres tests, don't require the postgres container (#841)
2018-11-13 08:02:28 -07:00
Jacob Beck
3773843094 in deps, when "git" is not in the path, link users to help docs 2018-11-13 08:02:08 -07:00
Jacob Beck
9bee0190d2 Lazy load adapters
Move adapter package loading to runtime, after configs have been mostly parsed
Parse/validate credentials after determining what the type is
profile/config contracts ignore credentials
2018-11-12 11:30:28 -07:00
Jacob Beck
60c4619862 Make adapters optional 2018-11-12 11:30:28 -07:00
Jacob Beck
9ffbb3ad02 Split out connection managers 2018-11-12 11:30:28 -07:00
Jacob Beck
350b81db99 Class hierarchy, deprecate and remove deprecated methods, abstract method definitions 2018-11-12 11:30:28 -07:00
Jacob Beck
412b165dc9 fix issue where we incorrectly logged stack traces 2018-11-12 11:29:45 -07:00
Jacob Beck
531d7c687e use environment variables or per-run temp directories to assign the temporary downloads directory 2018-11-12 11:29:45 -07:00
Jacob Beck
e866caa900 add support for cross-drive moves 2018-11-12 11:29:45 -07:00
Jacob Beck
ec466067b2 Tests, fixup ci 2018-11-12 11:29:45 -07:00
Jacob Beck
59b6f78c71 Raise an exception on rc!=0 in run_cmd, raise more specific exceptions about what went wrong on error 2018-11-12 11:29:45 -07:00
Jacob Beck
7757c85d4f add some helpful exceptions about command running 2018-11-12 11:29:45 -07:00
Jacob Beck
3077eecb97 Avoid connecting to postgres on each run 2018-11-12 11:29:11 -07:00
Jacob Beck
e6fc0f6724 make 2.7 exceptions have the same output as 3.x 2018-11-12 11:27:43 -07:00
Jacob Beck
42f817abf5 contracts and very basic tests for hub packages 2018-11-12 11:27:43 -07:00
Connor McArthur
3b0c9f8b48 merged dev/gb 2018-10-29 10:55:33 -04:00
Connor McArthur
738304f438 dbt builtin profiler 2018-09-20 08:44:42 -04:00
1097 changed files with 82168 additions and 24297 deletions

View File

@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.12.0
current_version = 0.18.1
parse = (?P<major>\d+)
\.(?P<minor>\d+)
\.(?P<patch>\d+)
@@ -22,5 +22,23 @@ first_value = 1
[bumpversion:file:setup.py]
[bumpversion:file:dbt/version.py]
[bumpversion:file:core/setup.py]
[bumpversion:file:core/dbt/version.py]
[bumpversion:file:plugins/postgres/setup.py]
[bumpversion:file:plugins/redshift/setup.py]
[bumpversion:file:plugins/snowflake/setup.py]
[bumpversion:file:plugins/bigquery/setup.py]
[bumpversion:file:plugins/postgres/dbt/adapters/postgres/__version__.py]
[bumpversion:file:plugins/redshift/dbt/adapters/redshift/__version__.py]
[bumpversion:file:plugins/snowflake/dbt/adapters/snowflake/__version__.py]
[bumpversion:file:plugins/bigquery/dbt/adapters/bigquery/__version__.py]

View File

@@ -1,114 +1,125 @@
version: 2
version: 2.1
jobs:
unit:
docker: &py36
- image: python:3.6
docker: &test_only
- image: fishtownanalytics/test-container:9
environment:
DBT_INVOCATION_ENV: circle
steps:
- checkout
- run: tox -e flake8,mypy,unit-py36,unit-py38
build-wheels:
docker: *test_only
steps:
- checkout
- run:
name: Build wheels
command: |
python3.8 -m venv "${PYTHON_ENV}"
export PYTHON_BIN="${PYTHON_ENV}/bin/python"
$PYTHON_BIN -m pip install -U pip setuptools
$PYTHON_BIN -m pip install -r requirements.txt
$PYTHON_BIN -m pip install -r dev_requirements.txt
/bin/bash ./scripts/build-wheels.sh
$PYTHON_BIN ./scripts/collect-dbt-contexts.py > ./dist/context_metadata.json
$PYTHON_BIN ./scripts/collect-artifact-schema.py > ./dist/artifact_schemas.json
environment:
PYTHON_ENV: /home/tox/build_venv/
- store_artifacts:
path: ./dist
destination: dist
integration-postgres-py36:
docker: &test_and_postgres
- image: fishtownanalytics/test-container:9
environment:
DBT_INVOCATION_ENV: circle
- image: postgres
name: database
environment: &pgenv
POSTGRES_USER: "root"
POSTGRES_PASSWORD: "password"
POSTGRES_DB: "dbt"
steps:
- checkout
- run: apt-get update && apt-get install -y python-dev python3-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run: &setupdb
name: Setup postgres
command: bash test/setup_db.sh
environment:
PGHOST: 127.0.0.1
PGHOST: database
PGUSER: root
PGPASSWORD: password
PGDATABASE: postgres
- run: tox -e pep8,unit-py27,unit-py36
integration-postgres-py36:
docker: *py36
steps:
- checkout
- run: apt-get update && apt-get install -y python3-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run: *setupdb
- run:
name: Run tests
command: tox -e integration-postgres-py36
- store_artifacts:
path: ./logs
integration-snowflake-py36:
docker: *py36
docker: *test_only
steps:
- checkout
- run: apt-get update && apt-get install -y python3-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run:
name: Run tests
command: tox -e integration-snowflake-py36
no_output_timeout: 1h
- store_artifacts:
path: ./logs
integration-redshift-py36:
docker: *py36
docker: *test_only
steps:
- checkout
- run: apt-get update && apt-get install -y python3-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run:
name: Run tests
command: tox -e integration-redshift-py36
- store_artifacts:
path: ./logs
integration-bigquery-py36:
docker: *py36
docker: *test_only
steps:
- checkout
- run: apt-get update && apt-get install -y python3-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run:
name: Run tests
command: tox -e integration-bigquery-py36
integration-postgres-py27:
docker: &py27
- image: python:2.7
- image: postgres
environment: *pgenv
- store_artifacts:
path: ./logs
integration-postgres-py38:
docker: *test_and_postgres
steps:
- checkout
- run: apt-get update && apt-get install -y python-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run: *setupdb
- run:
name: Run tests
command: tox -e integration-postgres-py27
integration-snowflake-py27:
docker: *py27
command: tox -e integration-postgres-py38
- store_artifacts:
path: ./logs
integration-snowflake-py38:
docker: *test_only
steps:
- checkout
- run: apt-get update && apt-get install -y python-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run:
name: Run tests
command: tox -e integration-snowflake-py27
command: tox -e integration-snowflake-py38
no_output_timeout: 1h
integration-redshift-py27:
docker: *py27
- store_artifacts:
path: ./logs
integration-redshift-py38:
docker: *test_only
steps:
- checkout
- run: apt-get update && apt-get install -y python-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run:
name: Run tests
command: tox -e integration-redshift-py27
integration-bigquery-py27:
docker: *py27
command: tox -e integration-redshift-py38
- store_artifacts:
path: ./logs
integration-bigquery-py38:
docker: *test_only
steps:
- checkout
- run: apt-get update && apt-get install -y python-dev postgresql
- run: echo 127.0.0.1 database | tee -a /etc/hosts
- run: pip install virtualenvwrapper tox
- run:
name: Run tests
command: tox -e integration-bigquery-py27
command: tox -e integration-bigquery-py38
- store_artifacts:
path: ./logs
workflows:
version: 2
@@ -118,18 +129,6 @@ workflows:
- integration-postgres-py36:
requires:
- unit
- integration-postgres-py27:
requires:
- unit
- integration-redshift-py27:
requires:
- integration-postgres-py27
- integration-bigquery-py27:
requires:
- integration-postgres-py27
- integration-snowflake-py27:
requires:
- integration-postgres-py27
- integration-redshift-py36:
requires:
- integration-postgres-py36
@@ -139,3 +138,26 @@ workflows:
- integration-snowflake-py36:
requires:
- integration-postgres-py36
- integration-postgres-py38:
requires:
- unit
- integration-redshift-py38:
requires:
- integration-postgres-py38
- integration-bigquery-py38:
requires:
- integration-postgres-py38
- integration-snowflake-py38:
requires:
- integration-postgres-py38
- build-wheels:
requires:
- unit
- integration-postgres-py36
- integration-redshift-py36
- integration-bigquery-py36
- integration-snowflake-py36
- integration-postgres-py38
- integration-redshift-py38
- integration-bigquery-py38
- integration-snowflake-py38

View File

@@ -1,3 +0,0 @@
[report]
include =
dbt/*

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
*
!docker/requirements/*.txt
!dist

View File

@@ -1,34 +0,0 @@
Please make sure to fill out either the issue template or the feature template and delete the other one!
## Issue
### Issue description
In general terms, please describe the issue. What command did you run?
### Results
What happened? What did you expect to happen?
### System information
The output of `dbt --version`:
```
<output goes here>
```
The operating system you're running on:
The python version you're using (probably the output of `python --version`)
### Steps to reproduce
In as much detail as possible, please provide steps to reproduce the issue. Sample data that triggers the issue, example models, etc are all very helpful here.
## Feature
### Feature description
Please describe the feature you would like dbt to have. Please provide any details, relevant documentation links, StackOverflow links, etc here.
### Who will this benefit?
What kind of use case will this feature be useful for? Please be specific and provide examples, this will help us prioritize properly.

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,41 @@
---
name: Bug report
about: Report a bug or an issue you've found with dbt
title: ''
labels: bug, triage
assignees: ''
---
### Describe the bug
A clear and concise description of what the bug is. What command did you run? What happened?
### Steps To Reproduce
In as much detail as possible, please provide steps to reproduce the issue. Sample data that triggers the issue, example model code, etc is all very helpful here.
### Expected behavior
A clear and concise description of what you expected to happen.
### Screenshots and log output
If applicable, add screenshots or log output to help explain your problem.
### System information
**Which database are you using dbt with?**
- [ ] postgres
- [ ] redshift
- [ ] bigquery
- [ ] snowflake
- [ ] other (specify: ____________)
**The output of `dbt --version`:**
```
<output goes here>
```
**The operating system you're using:**
**The output of `python --version`:**
### Additional context
Add any other context about the problem here.

View File

@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest an idea for dbt
title: ''
labels: enhancement, triage
assignees: ''
---
### Describe the feature
A clear and concise description of what you want to happen.
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Is this feature database-specific? Which database(s) is/are relevant? Please include any other relevant context here.
### Who will this benefit?
What kind of use case will this feature be useful for? Please be specific and provide examples, this will help us prioritize properly.
### Are you interested in contributing this feature?
Let us know if you want to write some code, and how we can help.

22
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,22 @@
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.
Example:
resolves #1234
-->
### Description
<!--- Describe the Pull Request here -->
### Checklist
- [ ] I have signed the [CLA](https://docs.getdbt.com/docs/contributor-license-agreements)
- [ ] 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
- [ ] I have updated the `CHANGELOG.md` and added information about my change to the "dbt next" section.

17
.gitignore vendored
View File

@@ -9,6 +9,7 @@ __pycache__/
# Distribution / packaging
.Python
env/
dbt_env/
build/
develop-eggs/
dist/
@@ -23,6 +24,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
*.mypy_cache/
logs/
# PyInstaller
@@ -41,12 +43,16 @@ htmlcov/
.coverage
.coverage.*
.cache
.env
nosetests.xml
coverage.xml
*,cover
.hypothesis/
test.env
# Mypy
.mypy_cache/
# Translations
*.mo
*.pot
@@ -76,3 +82,14 @@ target/
# Vim
*.sw*
# pycharm
.idea/
# AWS credentials
.aws/
.DS_Store
# vscode
.vscode/

File diff suppressed because it is too large Load Diff

View File

@@ -1,320 +1,222 @@
# Getting started with dbt
# Contributing to dbt
1. [About this document](#about-this-document)
2. [Proposing a change](#proposing-a-change)
3. [Getting the code](#getting-the-code)
4. [Setting up an environment](#setting-up-an-environment)
5. [Running dbt in development](#running-dbt-in-development)
6. [Testing](#testing)
7. [Submitting a Pull Request](#submitting-a-pull-request)
## About this document
This is intended as a developer's guide to modifying and using dbt. It is not intended as a guide for end users of dbt (though if it is helpful, that's great!) and assumes a certain level of familiarity with Python concepts such as virtualenvs, `pip`, module/filesystem layouts, etc. It also assumes you are using macOS or Linux and are comfortable with the command line.
This document is a guide intended for folks interested in contributing to dbt. Below, we document the process by which members of the community should create issues and submit pull requests (PRs) in this repository. It is not intended as a guide for using dbt, and it assumes a certain level of familiarity with Python concepts such as virtualenvs, `pip`, python modules, filesystems, and so on. This guide assumes you are using macOS or Linux and are comfortable with the command line.
If you're new to python development or contributing to open-source software, we encourage you to read this document from start to finish. If you get stuck, drop us a line in the #development channel on [slack](community.getdbt.com).
### Signing the CLA
Please note that all contributors to dbt must sign the [Contributor License Agreement](https://docs.getdbt.com/docs/contributor-license-agreements) to have their Pull Request merged into the dbt codebase. If you are unable to sign the CLA, then the dbt maintainers will unfortunately be unable to merge your Pull Request. You are, however, welcome to open issues and comment on existing ones.
## Proposing a change
dbt is Apache 2.0-licensed open source software. dbt is what it is today because community members like you have opened issues, provided feedback, and contributed to the knowledge loop for the entire communtiy. Whether you are a seasoned open source contributor or a first-time committer, we welcome and encourage you to contribute code, documentation, ideas, or problem statements to this project.
### Defining the problem
If you have an idea for a new feature or if you've discovered a bug in dbt, the first step is to open an issue. Please check the list of [open issues](https://github.com/fishtown-analytics/dbt/issues) before creating a new one. If you find a relevant issue, please add a comment to the open issue instead of creating a new one. There are hundreds of open issues in this repository and it can be hard to know where to look for a relevant open issue. **The dbt maintainers are always happy to point contributors in the right direction**, so please err on the side of documenting your idea in a new issue if you are unsure where a problem statement belongs.
**Note:** All community-contributed Pull Requests _must_ be associated with an open issue. If you submit a Pull Request that does not pertain to an open issue, you will be asked to create an issue describing the problem before the Pull Request can be reviewed.
### Discussing the idea
After you open an issue, a dbt maintainer will follow up by commenting on your issue (usually within 1-3 days) to explore your idea further and advise on how to implement the suggested changes. In many cases, community members will chime in with their own thoughts on the problem statement. If you as the issue creator are interested in submitting a Pull Request to address the issue, you should indicate this in the body of the issue. The dbt maintainers are _always_ happy to help contributors with the implementation of fixes and features, so please also indicate if there's anything you're unsure about or could use guidance around in the issue.
### Submitting a change
If an issue is appropriately well scoped and describes a beneficial change to the dbt codebase, then anyone may submit a Pull Request to implement the functionality described in the issue. See the sections below on how to do this.
The dbt maintainers will add a `good first issue` label if an issue is suitable for a first-time contributor. This label often means that the required code change is small, limited to one database adapter, or a net-new addition that does not impact existing functionality. You can see the list of currently open issues on the [Contribute](https://github.com/fishtown-analytics/dbt/contribute) page.
Here's a good workflow:
- Comment on the open issue, expressing your interest in contributing the required code change
- Outline your planned implementation. If you want help getting started, ask!
- Follow the steps outlined below to develop locally. Once you have opened a PR, one of the dbt maintainers will work with you to review your code.
- Add a test! Tests are crucial for both fixes and new features alike. We want to make sure that code works as intended, and that it avoids any bugs previously encountered. Currently, the best resource for understanding dbt's [unit](test/unit) and [integration](test/integration) tests is the tests themselves. One of the maintainers can help by pointing out relevant examples.
In some cases, the right resolution to an open issue might be tangential to the dbt codebase. The right path forward might be a documentation update or a change that can be made in user-space. In other cases, the issue might describe functionality that the dbt maintainers are unwilling or unable to incorporate into the dbt codebase. When it is determined that an open issue describes functionality that will not translate to a code change in the dbt repository, the issue will be tagged with the `wontfix` label (see below) and closed.
### Using issue labels
The dbt maintainers use labels to categorize open issues. Some labels indicate the databases impacted by the issue, while others describe the domain in the dbt codebase germane to the discussion. While most of these labels are self-explanatory (eg. `snowflake` or `bigquery`), there are others that are worth describing.
| tag | description |
| --- | ----------- |
| [triage](https://github.com/fishtown-analytics/dbt/labels/triage) | This is a new issue which has not yet been reviewed by a dbt maintainer. This label is removed when a maintainer reviews and responds to the issue. |
| [bug](https://github.com/fishtown-analytics/dbt/labels/bug) | This issue represents a defect or regression in dbt |
| [enhancement](https://github.com/fishtown-analytics/dbt/labels/enhancement) | This issue represents net-new functionality in dbt |
| [good first issue](https://github.com/fishtown-analytics/dbt/labels/good%20first%20issue) | This issue does not require deep knowledge of the dbt codebase to implement. This issue is appropriate for a first-time contributor. |
| [help wanted](https://github.com/fishtown-analytics/dbt/labels/help%20wanted) / [discussion](https://github.com/fishtown-analytics/dbt/labels/discussion) | Conversation around this issue in ongoing, and there isn't yet a clear path forward. Input from community members is most welcome. |
| [duplicate](https://github.com/fishtown-analytics/dbt/issues/duplicate) | This issue is functionally identical to another open issue. The dbt maintainers will close this issue and encourage community members to focus conversation on the other one. |
| [snoozed](https://github.com/fishtown-analytics/dbt/labels/snoozed) | This issue describes a good idea, but one which will probably not be addressed in a six-month time horizon. The dbt maintainers will revist these issues periodically and re-prioritize them accordingly. |
| [stale](https://github.com/fishtown-analytics/dbt/labels/stale) | This is an old issue which has not recently been updated. Stale issues will periodically be closed by dbt maintainers, but they can be re-opened if the discussion is restarted. |
| [wontfix](https://github.com/fishtown-analytics/dbt/labels/wontfix) | This issue does not require a code change in the dbt repository, or the maintainers are unwilling/unable to merge a Pull Request which implements the behavior described in the issue. |
## Getting the code
### Installing git
You will need `git` in order to download and modify the dbt source code. On macOS, the best way to download git is to just install [Xcode](https://developer.apple.com/support/xcode/).
### External contributors
If you are not a member of the `fishtown-analytics` GitHub organization, you can contribute to dbt by forking the dbt repository. For a detailed overview on forking, check out the [GitHub docs on forking](https://help.github.com/en/articles/fork-a-repo). In short, you will need to:
1. fork the dbt repository
2. clone your fork locally
3. check out a new branch for your proposed changes
4. push changes to your fork
5. open a pull request against `fishtown-analytics/dbt` from your forked repository
### Core contributors
If you are a member of the `fishtown-analytics` GitHub organization, you will have push access to the dbt repo. Rather than
forking dbt to make your changes, just clone the repository, check out a new branch, and push directly to that branch.
## Setting up an environment
Before you can develop dbt effectively, you should set up the following:
### pyenv
We strongly recommend setting up [pyenv](https://github.com/pyenv/pyenv) and its [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) plugin. This setup will make it much easier for you to manage multiple Python projects in the medium to long term.
### python
By default, `pyenv` has only one python version installed and it's the `system` python - the one that comes with your OS. You don't want that. Instead, use `pyenv install 3.6.5` to install a more recent version. dbt supports up to Python 3.6 at the time of writing (and will soon support Python 3.7)
To get a full (very long!) list of versions available, you can do `pyenv install -l` and look for the versions defined by numbers alone - the others are variants of Python and outside the scope of this document.
### docker and docker-compose
Docker and docker-compose are both used in testing. For macOS, the easiest thing to do is to go [here](https://store.docker.com/editions/community/docker-ce-desktop-mac) and download it. You'll need to make an account. On Linux, if you can use one of the packages [here](https://docs.docker.com/install/#server). We recommend installing from docker.com instead of from your package manager. On Linux you also have to install docker-compose separately, follow [these instructions](https://docs.docker.com/compose/install/#install-compose).
### git
You will also need `git` in order to get dbt and contribute code. On macOS, the best way to get that is to just install Xcode.
### GitHub
You will need a GitHub account fully configured with SSH to contribute to dbt. GitHub has [an excellent guide on how to set up SSH](https://help.github.com/articles/connecting-to-github-with-ssh/) -- we strongly recommend you follow their guide if you are unfamiliar with SSH.
### Getting dbt
Now clone dbt to wherever you'd like. For example:
```
mkdir -p ~/git/
cd ~/git
git clone git@github.com:fishtown-analytics/dbt.git
```
But it really does not matter where you put it as long as you remember it.
### Setting up your virtualenv
Set up a fresh virtualenv with pyenv-virtualenv for dbt:
```
pyenv virtualenv 3.6.5 dbt36
cd ~/git/dbt
pyenv local dbt36
```
This makes a new virtualenv based on python 3.6.5 named `dbt36`, and tells pyenv that when you're in the `dbt` directory it should automatically use that virtualenv.
### Installing postgres locally
For testing, and later in the examples in this document, you may want to have `psql` available so you can poke around in the database and see what happened. We recommend that you use [homebrew](https://brew.sh/) for that on macOS, and your package manager on Linux. You can install any version of the postgres client that you'd like. So on macOS, with homebrew setup:
```
brew install postgresql
```
## Testing
Getting the dbt integration tests set up in your local environment will be very helpful as you start to make changes to your local version of dbt. The section that follows outlines some helpful tips for setting up the test environment.
There are some tools that will be helpful to you in developing locally. While this is the list relevant for dbt development, many of these tools are used commonly across open-source python projects.
### Tools
A short list of tools used in dbt testing that will be helpful to your understanding:
- [virtualenv](https://virtualenv.pypa.io/en/stable/) to manage dependencies and stuff
- [virtualenv](https://virtualenv.pypa.io/en/stable/) to manage dependencies
- [tox](https://tox.readthedocs.io/en/latest/) to manage virtualenvs across python versions
- [nosetests](http://nose.readthedocs.io/en/latest) to discover/run tests
- [pytest](https://docs.pytest.org/en/latest/) to discover/run tests
- [make](https://users.cs.duke.edu/~ola/courses/programming/Makefiles/Makefiles.html) - but don't worry too much, nobody _really_ understands how make works and our Makefile is super simple
- [pep8](https://pep8.readthedocs.io/en/release-1.7.x/) for code linting
- [CircleCI](https://circleci.com/product/) and [Appveyor](https://www.appveyor.com/docs/)
- [flake8](https://gitlab.com/pycqa/flake8) for code linting
- [CircleCI](https://circleci.com/product/) and [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/)
If you're unfamiliar with any or all of these, that's fine! You really do not have to have a deep understanding of any of these to get by.
A deep understanding of these tools in not required to effectively contribute to dbt, but we recommend checking out the attached documentation if you're interested in learning more about them.
Our test environment goes like this:
#### virtual environments
- CircleCI and Appveyor run `tox`
- `make test` runs `docker-compose`
- `docker-compose` runs `tox`
- `tox` sets up virtualenvs for each distinct set of tests and runs `nosetests`
- `nosetests` finds all the appropriate tests and runs them
We strongly recommend using virtual environments when developing code in dbt. We recommend creating this virtualenv
in the root of the dbt repository. To create a new virtualenv, run:
```
python3 -m venv env
source env/bin/activate
```
This will create and activate a new Python virtual environment.
#### docker and docker-compose
Docker and docker-compose are both used in testing. For macOS, the easiest thing to do is to [download docker for mac](https://store.docker.com/editions/community/docker-ce-desktop-mac). You'll need to make an account. On Linux, you can use one of the packages [here](https://docs.docker.com/install/#server). We recommend installing from docker.com instead of from your package manager. On Linux you also have to install docker-compose separately, following [these instructions](https://docs.docker.com/compose/install/#install-compose).
#### postgres (optional)
For testing, and later in the examples in this document, you may want to have `psql` available so you can poke around in the database and see what happened. We recommend that you use [homebrew](https://brew.sh/) for that on macOS, and your package manager on Linux. You can install any version of the postgres client that you'd like. On macOS, with homebrew setup, you can run:
```
brew install postgresql
```
## Running dbt in development
### Installation
First make sure that you set up your `virtualenv` as described in section _Setting up an environment_. Next, install dbt (and its dependencies) with:
```
pip install -r editable_requirements.txt
```
When dbt is installed from source in this way, any changes you make to the dbt source code will be reflected immediately in your next `dbt` run.
### Running dbt
With your virtualenv activated, the `dbt` script should point back to the source code you've cloned on your machine. You can verify this by running `which dbt`. This command should show you a path to an executable in your virtualenv.
Configure your [profile](https://docs.getdbt.com/docs/configure-your-profile) as necessary to connect to your target databases. It may be a good idea to add a new profile pointing to a local postgres instance, or a specific test sandbox within your data warehouse if appropriate.
## Testing
Getting the dbt integration tests set up in your local environment will be very helpful as you start to make changes to your local version of dbt. The section that follows outlines some helpful tips for setting up the test environment.
### Running tests via Docker
The basics should work with basically no further setup. In the terminal, `cd` to the directory where you cloned dbt. So, for example, if you cloned dbt to `~/git/dbt`:
dbt's unit and integration tests run in Docker. Because dbt works with a number of different databases, you will need to supply credentials for one or more of these databases in your test environment. Most organizations don't have access to each of a BigQuery, Redshift, Snowflake, and Postgres database, so it's likely that you will be unable to run every integration test locally. Fortunately, Fishtown Analytics provides a CI environment with access to sandboxed Redshift, Snowflake, BigQuery, and Postgres databases. See the section on [_Submitting a Pull Request_](#submitting-a-pull-request) below for more information on this CI setup.
```
cd ~/git/dbt
```
Then you'll want to make a test.env file. Fortunately, there's a sample which is fine for our purposes:
### Specifying your test credentials
dbt uses test credentials specified in a `test.env` file in the root of the repository. This `test.env` file is git-ignored, but please be _extra_ careful to never check in credentials or other sensitive information when developing against dbt. To create your `test.env` file, copy the provided sample file, then supply your relevant credentials:
```
cp test.env.sample test.env
atom test.env # supply your credentials
```
If you want to test snowflake/bigquery/redshift locally you'll need to get credentials and add them to this file. But, to start, you can just focus on postgres tests. They have the best coverage, are the fastest, and are the easiest to set up.
We recommend starting with dbt's Postgres tests. These tests cover most of the functionality in dbt, are the fastest to run, and are the easiest to set up. dbt's test suite runs Postgres in a Docker container, so no setup should be required to run these tests.
To run the unit tests, use `make test-unit` - it will run the unit tests on python 2.7 and 3.6, and a pep8 linter.
If you additionally want to test Snowflake, Bigquery, or Redshift, locally you'll need to get credentials and add them to the `test.env` file. In general, it's most important to have successful unit and Postgres tests. Once you open a PR, dbt will automatically run integration tests for the other three core database adapters. Of course, if you are a BigQuery user, contributing a BigQuery-only feature, it's important to run BigQuery tests as well.
To run the postgres+python 3.6 integration tests, you'll have to do one extra step of setting up the database:
### Test commands
dbt's unit tests and Python linter can be run with:
```
make test-unit
```
To run the Postgres + Python 3.6 integration tests, you'll have to do one extra step of setting up the test database:
```
docker-compose up -d database
PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres bash test/setup_db.sh
```
And then to actually run them, you can do `make test-quick`.
If you want to see what exactly is getting run on these commands, look at the `Makefile`. Note that the commands start with an `@` which you can ignore, just makefile magic. If you want to see what the involved `tox` commands are using, look at the corresponding `tox.ini` section - hopefully it's pretty self-explanatory.
### Running tests in CI
When a contributor to dbt pushes code, GitHub will trigger a series of CI builds on CircleCI and Appveyor (Windows) to test all of dbt's code. The CI builds trigger all the integration tests, not just postgres+python3.6.
The Snowflake tests take a very long time to run (about an hour), so don't just sit around waiting, it'll be a while!
If you open a PR as a non-contributor, these tests won't run automatically. Someone from the dbt team will reach out to you and get them running after reviewing your code.
## Running dbt locally
Sometimes, you're going to have to pretend to be an end user to reproduce bugs and stuff. So that means manually setting up some stuff that the test harness takes care of for you.
### installation
First, from the `dbt` directory, install dbt in 'editable' mode. There are a couple ways to do it, but I'm in the habit of `pip install -e .`, which tells pip to install the package in the current directory in "editable" mode. What's cool about this mode is any changes you make to the current dbt directory will be reflected immediately in your next `dbt` run.
### Profile
Now you'll also need a 'dbt profile' so dbt can tell how to connect to your database. By default, this file belongs at `~/.dbt/profiles.yml`, so `mkdir ~/.dbt` and then open your favorite text editor and write out something like this to `~/.dbt/profiles.yml`:
To run a quick test for Python3 integration tests on Postgres, you can run:
```
config:
send_anonymous_usage_stats: False
use_colors: True
talk:
outputs:
default:
type: postgres
threads: 4
host: localhost
port: 5432
user: root
pass: password
dbname: postgres
schema: dbt_postgres
target: default
make test-quick
```
There's a sample you can look at in the `dbt` folder (`sample.profiles.yml`) but it's got a lot of extra and as a developer, you really probably only want to test against your local postgres container. The basic idea is that there are multiple 'profiles' (`talk`, in this case) and within those each profile has one or more 'targets' (`default`, in this case), and each profile has a default target. You can specify what profile you want to use with the `--profile` flag, and which target with the `--target` flag. If you want to be really snazzy, dbt project files actually specify their target, and if you match up your dbt project `profile` key with your `profiles.yml` profile names you don't have to use `--profile` (and if you like your profile's default target, no need for `--target` either).
## Example
There is a very simple project that is a very nice example of dbt's capabilities, you can get it from github:
To run tests for a specific database, invoke `tox` directly with the required flags:
```
cd ~/src/fishtown
git clone git@github.com:fishtown-analytics/talk.git
git checkout use-postgres
# Run Postgres py36 tests
docker-compose run test tox -e integration-postgres-py36 -- -x
# Run Snowflake py36 tests
docker-compose run test tox -e integration-snowflake-py36 -- -x
# Run BigQuery py36 tests
docker-compose run test tox -e integration-bigquery-py36 -- -x
# Run Redshift py36 tests
docker-compose run test tox -e integration-redshift-py36 -- -x
```
The `use-postgres` branch configures the project to use your local postgres (instead of the default, Snowflake). You should poke around in this project a bit, particularly the `models` directory.
Before doing anything, let's check the database out:
To run a specific test by itself:
```
> PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres psql
psql (10.4)
Type "help" for help.
postgres=# \dn
List of schemas
Name | Owner
--------+----------
public | postgres
(1 row)
postgres=# \q
docker-compose run test tox -e explicit-py36 -- -s -x -m profile_{adapter} {path_to_test_file_or_folder}
```
E.g.
```
docker-compose run test tox -e explicit-py36 -- -s -x -m profile_snowflake test/integration/001_simple_copy_test
```
`\dn` lists schemas in postgres. You can see that we just have the default "public" schema, so we haven't done anything yet.
See the `Makefile` contents for more some other examples of ways to run `tox`.
## Submitting a Pull Request
If you compile your model with `dbt compile` you should see something like this:
Fishtown Analytics provides a sandboxed Redshift, Snowflake, and BigQuery database for use in a CI environment. When pull requests are submitted to the `fishtown-analytics/dbt` repo, GitHub will trigger automated tests in CircleCI and Azure Pipelines.
```
> dbt compile
Found 2 models, 0 tests, 0 archives, 0 analyses, 59 macros, 1 operations, 1 seed files
A dbt maintainer will review your PR. They may suggest code revision for style or clarity, or request that you add unit or integration test(s). These are good things! We believe that, with a little bit of help, anyone can contribute high-quality code.
09:49:57 | Concurrency: 2 threads (target='default')
09:49:57 |
09:49:57 | Done.
```
So what does that mean? Well:
- `2 models` refers to the contents of the `models` directory
- `59 macros` are the builtin global macros defind by dbt itself
- `1 operations` is the catalog generation operation that runs by default
- `1 seed files` refers to the seed data in `data/moby_dick.csv`
It will create two new folders: One named `dbt_modules`, which is empty for this case, and one named `target`, which has a few things in it:
- A folder named `compiled`, created by dbt looking at your models and your database schema and filling in references (so `models/moby_dick_base.sql` becomes `target/compiled/talk/moby_dick_base.sql` by replacing the `from {{ ref('moby_dick') }}` with `from "dbt_postgres".moby_dick`)
- A file named `graph.gpickle`, which is your project's dependency/reference graph as understood by the `networkx` library.
- A file named `catalog.json`, which is the data dbt has collected about your project (macros used, models/seeds used, and parent/child reference maps)
Next, load the seed file into the database with `dbt seed`, it'll look like this:
```
> dbt seed
Found 2 models, 0 tests, 0 archives, 0 analyses, 59 macros, 1 operations, 1 seed files
10:40:46 | Concurrency: 2 threads (target='default')
10:40:46 |
10:40:46 | 1 of 1 START seed file dbt_postgres.moby_dick........................ [RUN]
10:40:47 | 1 of 1 OK loaded seed file dbt_postgres.moby_dick.................... [INSERT 17774 in 0.44s]
10:40:47 |
10:40:47 | Finished running 1 seeds in 0.65s.
Completed successfully
```
If you go into postgres now, you can see that you have a new schema ('dbt_postgres'), a new table in that schema ('moby_dick'), and a bunch of stuff in that table:
```
> PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres psql
psql (10.4)
Type "help" for help.
postgres=# \dn
List of schemas
Name | Owner
--------------+----------
dbt_postgres | root
public | postgres
(2 rows)
postgres=# \dt dbt_postgres.*
List of relations
Schema | Name | Type | Owner
--------------+-----------+-------+-------
dbt_postgres | moby_dick | table | root
(1 row)
postgres=# select count(*) from dbt_postgres.moby_dick;
count
-------
17774
(1 row)
postgres=# \q
```
If you run `dbt run` now, you'll see something like this:
```
> dbt run
Found 2 models, 0 tests, 0 archives, 0 analyses, 59 macros, 1 operations, 1 seed files
10:19:41 | Concurrency: 2 threads (target='default')
10:19:41 |
10:19:41 | 1 of 2 START view model dbt_postgres.moby_dick_base.................. [RUN]
10:19:41 | 1 of 2 OK created view model dbt_postgres.moby_dick_base............. [CREATE VIEW in 0.05s]
10:19:41 | 2 of 2 START table model dbt_postgres.word_count..................... [RUN]
10:19:42 | 2 of 2 OK created table model dbt_postgres.word_count................ [SELECT 27390 in 0.19s]
10:19:42 |
10:19:42 | Finished running 1 view models, 1 table models in 0.53s.
Completed successfully
Done. PASS=2 ERROR=0 SKIP=0 TOTAL=2
```
So, some of the same information and then you can see that dbt created a view (`moby_dick_base`) and a table (`word_count`). If you go into postgres, you'll be able to see that!
If you want to inspect the result, you can do so via psql:
```
> PGHOST=localhost PGUSER=root PGPASSWORD=password PGDATABASE=postgres psql
psql (10.4)
Type "help" for help.
postgres=# \dt dbt_postgres.*
List of relations
Schema | Name | Type | Owner
--------------+------------+-------+-------
dbt_postgres | moby_dick | table | root
dbt_postgres | word_count | table | root
(2 rows)
postgres=# select * from dbt_postgres.word_count order by ct desc limit 10;
word | ct
------+-------
the | 13394
| 12077
of | 6368
and | 5846
to | 4382
a | 4377
in | 3767
that | 2753
his | 2406
I | 1826
(10 rows)
```
It's pretty much what you'd expect - the most common words are "the", "of", "and", etc. (The empty string probably should not be there, but this is just a toy example!)
So what happened here? First, `dbt seed` loaded the data in the csv file into postgres. Then `dbt compile` built out a sort of plan for how everything is linked together by looking up references and macros and the current state of the database. And finally, `dbt run` ran the compiled SQL to generate the word_count table.
Once all tests are passing and your PR has been approved, a dbt maintainer will merge your changes into the active development branch. And that's it! Happy developing :tada:

View File

@@ -1,14 +1,51 @@
FROM python:3.6
FROM ubuntu:18.04
RUN apt-get update
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get install -y python-pip netcat
RUN apt-get install -y python-dev python3-dev
RUN apt-get update && \
apt-get dist-upgrade -y && \
apt-get install -y --no-install-recommends \
netcat postgresql curl git ssh software-properties-common \
make build-essential ca-certificates libpq-dev \
libsasl2-dev libsasl2-2 libsasl2-modules-gssapi-mit libyaml-dev \
&& \
add-apt-repository ppa:deadsnakes/ppa && \
apt-get install -y \
python python-dev python-pip \
python3.6 python3.6-dev python3-pip python3.6-venv \
python3.7 python3.7-dev python3.7-venv \
python3.8 python3.8-dev python3.8-venv \
python3.9 python3.9-dev python3.9-venv && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN pip install pip --upgrade
RUN pip install virtualenv
RUN pip install virtualenvwrapper
RUN pip install tox
ARG DOCKERIZE_VERSION=v0.6.1
RUN curl -LO https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \
tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \
rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
WORKDIR /usr/src/app
RUN cd /usr/src/app
RUN pip3 install -U "tox==3.14.4" wheel "six>=1.14.0,<1.15.0" "virtualenv==20.0.3" setuptools
# tox fails if the 'python' interpreter (python2) doesn't have `tox` installed
RUN pip install -U "tox==3.14.4" "six>=1.14.0,<1.15.0" "virtualenv==20.0.3" setuptools
# These args are passed in via docker-compose, which reads then from the .env file.
# On Linux, run `make .env` to create the .env file for the current user.
# On MacOS and Windows, these can stay unset.
ARG USER_ID
ARG GROUP_ID
RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
groupadd -g ${GROUP_ID} dbt_test_user && \
useradd -m -l -u ${USER_ID} -g ${GROUP_ID} dbt_test_user; \
else \
useradd -mU -l dbt_test_user; \
fi
RUN mkdir /usr/app && chown dbt_test_user /usr/app
RUN mkdir /home/tox && chown dbt_test_user /home/tox
WORKDIR /usr/app
VOLUME /usr/app
USER dbt_test_user
ENV PYTHONIOENCODING=utf-8
ENV LANG C.UTF-8

View File

@@ -5,25 +5,38 @@ changed_tests := `git status --porcelain | grep '^\(M\| M\|A\| A\)' | awk '{ pri
install:
pip install -e .
test:
test: .env
@echo "Full test run starting..."
@time docker-compose run test tox
test-unit:
test-unit: .env
@echo "Unit test run starting..."
@time docker-compose run test tox -e unit-py27,unit-py36,pep8
@time docker-compose run test tox -e unit-py36,flake8
test-integration:
test-integration: .env
@echo "Integration test run starting..."
@time docker-compose run test tox -e integration-postgres-py27,integration-postgres-py36,integration-redshift-py27,integration-redshift-py36,integration-snowflake-py27,integration-snowflake-py36,integration-bigquery-py27,integration-bigquery-py36
@time docker-compose run test tox -e integration-postgres-py36,integration-redshift-py36,integration-snowflake-py36,integration-bigquery-py36
test-quick:
test-quick: .env
@echo "Integration test run starting..."
@time docker-compose run test tox -e integration-postgres-py36 -- -x
# This rule creates a file named .env that is used by docker-compose for passing
# the USER_ID and GROUP_ID arguments to the Docker image.
.env:
@touch .env
ifneq ($(OS),Windows_NT)
ifneq ($(shell uname -s), Darwin)
@echo USER_ID=$(shell id -u) > .env
@echo GROUP_ID=$(shell id -g) >> .env
endif
endif
@time docker-compose build
clean:
rm -f .coverage
rm -rf .eggs/
rm -f .env
rm -rf .tox/
rm -rf build/
rm -rf dbt.egg-info/

View File

@@ -1,49 +1,58 @@
# dbt
<p align="center">
<img src="/etc/dbt-logo-full.svg" alt="dbt logo" width="500"/>
</p>
<p align="center">
<a href="https://codeclimate.com/github/fishtown-analytics/dbt">
<img src="https://codeclimate.com/github/fishtown-analytics/dbt/badges/gpa.svg" alt="Code Climate"/>
</a>
<a href="https://circleci.com/gh/fishtown-analytics/dbt/tree/master">
<img src="https://circleci.com/gh/fishtown-analytics/dbt/tree/master.svg?style=svg" alt="CircleCI" />
</a>
<a href="https://ci.appveyor.com/project/DrewBanin/dbt/branch/development">
<img src="https://ci.appveyor.com/api/projects/status/v01rwd3q91jnwp9m/branch/development?svg=true" alt="AppVeyor" />
</a>
<a href="https://community.getdbt.com">
<img src="https://community.getdbt.com/badge.svg" alt="Slack" />
</a>
</p>
dbt (data build tool) helps analysts write reliable, modular code using a workflow that closely mirrors software development.
**[dbt](https://www.getdbt.com/)** (data build tool) enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.
A dbt project primarily consists of "models". These models are SQL `select` statements that filter, aggregate, and otherwise transform data to facilitate analytics. Analysts use dbt to [aggregate pageviews into sessions](https://github.com/fishtown-analytics/snowplow), calculate [ad spend ROI](https://github.com/fishtown-analytics/facebook-ads), or report on [email campaign performance](https://github.com/fishtown-analytics/mailchimp).
dbt is the T in ELT. Organize, cleanse, denormalize, filter, rename, and pre-aggregate the raw data in your warehouse so that it's ready for analysis.
These models frequently build on top of one another. Fortunately, dbt makes it easy to [manage relationships](https://docs.getdbt.com/reference#ref) between models, [test](https://docs.getdbt.com/docs/testing) your assumptions, and [visualize](https://graph.sinterdata.com/) your projects.
![dbt architecture](https://github.com/fishtown-analytics/dbt/blob/master/etc/dbt-arch.png?raw=true)
Still reading? Check out the [docs](https://docs.getdbt.com/docs/overview) for more information.
dbt can be used to [aggregate pageviews into sessions](https://github.com/fishtown-analytics/snowplow), calculate [ad spend ROI](https://github.com/fishtown-analytics/facebook-ads), or report on [email campaign performance](https://github.com/fishtown-analytics/mailchimp).
![dbt dag](/etc/dag.png?raw=true)
## Understanding dbt
---
### Getting Started
Analysts using dbt can transform their data by simply writing select statements, while dbt handles turning these statements into tables and views in a data warehouse.
- [What is dbt]?
- Read the [dbt viewpoint]
- [Installation]
- Join the [chat][slack-url] on Slack for live questions and support.
These select statements, or "models", form a dbt project. Models frequently build on top of one another dbt makes it easy to [manage relationships](https://docs.getdbt.com/docs/ref) between models, and [visualize these relationships](https://docs.getdbt.com/docs/documentation), as well as assure the quality of your transformations through [testing](https://docs.getdbt.com/docs/testing).
---
### The dbt ecosystem
- Visualize your dbt graph [here](https://graph.sinterdata.com/)
- Run your dbt projects on a schedule [here](http://sinterdata.com/)
![dbt dag](https://github.com/fishtown-analytics/dbt/blob/master/etc/dbt-dag.png?raw=true)
---
## Getting started
[![Code Climate](https://codeclimate.com/github/fishtown-analytics/dbt/badges/gpa.svg)](https://codeclimate.com/github/fishtown-analytics/dbt) [![Slack](https://slack.getdbt.com/badge.svg)](https://slack.getdbt.com)
- [Install dbt](https://docs.getdbt.com/docs/installation)
- Read the [documentation](https://docs.getdbt.com/).
- Productionize your dbt project with [dbt Cloud](https://www.getdbt.com)
### Testing
## Find out more
| service | development | master |
| --- | --- | --- |
| CircleCI| [![CircleCI](https://circleci.com/gh/fishtown-analytics/dbt/tree/development.svg?style=svg)](https://circleci.com/gh/fishtown-analytics/dbt/tree/development) | [![CircleCI](https://circleci.com/gh/fishtown-analytics/dbt/tree/master.svg?style=svg)](https://circleci.com/gh/fishtown-analytics/dbt/tree/master) |
| AppVeyor | [![AppVeyor](https://ci.appveyor.com/api/projects/status/v01rwd3q91jnwp9m/branch/development?svg=true)](https://ci.appveyor.com/project/DrewBanin/dbt/branch/development) | [![AppVeyor](https://ci.appveyor.com/api/projects/status/v01rwd3q91jnwp9m/branch/master?svg=true)](https://ci.appveyor.com/project/DrewBanin/dbt/branch/master) |
- Check out the [Introduction to dbt](https://docs.getdbt.com/docs/introduction/).
- Read the [dbt Viewpoint](https://docs.getdbt.com/docs/about/viewpoint/).
[Coverage](https://circleci.com/api/v1/project/fishtown-analytics/dbt/latest/artifacts/0/$CIRCLE_ARTIFACTS/htmlcov/index.html?branch=development)
## Join thousands of analysts in the dbt community
- Join the [chat](http://community.getdbt.com/) on Slack.
- Find community posts on [dbt Discourse](https://discourse.getdbt.com).
## Reporting bugs and contributing code
- Want to report a bug or request a feature? Let us know on [Slack](http://community.getdbt.com/), or open [an issue](https://github.com/fishtown-analytics/dbt/issues/new).
- Want to help us build dbt? Check out the [Contributing Getting Started Guide](/CONTRIBUTING.md)
## Code of Conduct
Everyone interacting in the dbt project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [PyPA Code of Conduct].
[PyPA Code of Conduct]: https://www.pypa.io/en/latest/code-of-conduct/
[slack-url]: https://slack.getdbt.com/
[Installation]: https://docs.getdbt.com/docs/installation
[What is dbt]: https://docs.getdbt.com/docs/overview
[dbt viewpoint]: https://docs.getdbt.com/docs/viewpoint
Everyone interacting in the dbt project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [dbt Code of Conduct](https://community.getdbt.com/code-of-conduct).

View File

@@ -11,19 +11,22 @@ dbt has three types of branches:
#### Git & PyPI
1. Update CHANGELOG.md with the most recent changes
2. If this is a release candidate, you want to create it off of your release branch. If it's an actual release, you must first merge to master. Open a Pull Request in Github to merge it.
2. If this is a release candidate, you want to create it off of your release branch. If it's an actual release, you must first merge to a master branch. Open a Pull Request in Github to merge it into the appropriate trunk (`X.X.latest`)
3. Bump the version using `bumpversion`:
- Dry run first by running `bumpversion --new-version <desired-version> <part>` and checking the diff. If it looks correct, clean up the chanages and move on:
- Alpha releases: `bumpversion --commit --tag --new-version 0.10.2a1 num`
- Patch releases: `bumpversion --commit --tag --new-version 0.10.2 patch`
- Minor releases: `bumpversion --commit --tag --new-version 0.11.0 minor`
- Major releases: `bumpversion --commit --tag --new-version 1.0.0 major`
4. Deploy to pypi
- `python setup.py sdist upload -r pypi`
5. Deploy to homebrew (see below)
6. Deploy to conda-forge (see below)
7. Git release notes (points to changelog)
8. Post to slack (point to changelog)
- Alpha releases: `bumpversion --commit --no-tag --new-version 0.10.2a1 num`
- Patch releases: `bumpversion --commit --no-tag --new-version 0.10.2 patch`
- Minor releases: `bumpversion --commit --no-tag --new-version 0.11.0 minor`
- Major releases: `bumpversion --commit --no-tag --new-version 1.0.0 major`
4. (If this is a not a release candidate) Merge to `x.x.latest` and (optionally) `master`.
5. Update the default branch to the next dev release branch.
6. Build source distributions for all packages by running `./scripts/build-sdists.sh`. Note that this will clean out your `dist/` folder, so if you have important stuff in there, don't run it!!!
7. Deploy to pypi
- `twine upload dist/*`
8. Deploy to homebrew (see below)
9. Deploy to conda-forge (see below)
10. Git release notes (points to changelog)
11. Post to slack (point to changelog)
After releasing a new version, it's important to merge the changes back into the other outstanding release branches. This avoids merge conflicts moving forward.

View File

@@ -1,63 +0,0 @@
version: 1.0.{build}-{branch}
environment:
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
# /E:ON and /V:ON options are not enabled in the batch script intepreter
# See: http://stackoverflow.com/a/13751649/163740
CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
TOX_ENV: "pywin"
matrix:
- PYTHON: "C:\\Python35"
PYTHON_VERSION: "3.5.2"
PYTHON_ARCH: "32"
#- PYTHON: "C:\\Python35"
# PYTHON_VERSION: "3.5.2"
# PYTHON_ARCH: "32"
PGUSER: postgres
PGPASSWORD: Password12!
services:
- postgresql94
hosts:
database: 127.0.0.1
init:
- PATH=C:\Program Files\PostgreSQL\9.4\bin\;%PATH%
- ps: Set-Content "c:\program files\postgresql\9.4\data\pg_hba.conf" "host all all ::1/128 trust"
- ps: Add-Content "c:\program files\postgresql\9.4\data\pg_hba.conf" "host all all 127.0.0.1/32 trust"
install:
# Download setup scripts and unzip
- ps: "wget https://github.com/cloudify-cosmo/appveyor-utils/archive/master.zip -OutFile ./master.zip"
- "7z e master.zip */appveyor/* -oappveyor"
# Install Python (from the official .msi of http://python.org) and pip when
# not already installed.
- "powershell ./appveyor/install.ps1"
# Prepend newly installed Python to the PATH of this build (this cannot be
# done from inside the powershell script as it would require to restart
# the parent CMD process).
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
# Check that we have the expected version and architecture for Python
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
build: false # Not a C# project, build stuff at the test step instead.
before_test:
- "%CMD_IN_ENV% pip install psycopg2==2.6.2"
- "%CMD_IN_ENV% pip install tox"
test_script:
- "bash test/setup_db.sh"
# this is generally a bad idea TODO
- git config --system http.sslverify false
- "%CMD_IN_ENV% tox -e %TOX_ENV%"

154
azure-pipelines.yml Normal file
View File

@@ -0,0 +1,154 @@
# Python package
# Create and test a Python package on multiple Python versions.
# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
trigger:
branches:
include:
- master
- dev/*
- pr/*
jobs:
- job: UnitTest
pool:
vmImage: 'vs2017-win2016'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
architecture: 'x64'
- script: python -m pip install --upgrade pip && pip install tox
displayName: 'Install dependencies'
- script: python -m tox -e pywin-unit
displayName: Run unit tests
- job: PostgresIntegrationTest
pool:
vmImage: 'vs2017-win2016'
dependsOn: UnitTest
steps:
- pwsh: |
$serviceName = Get-Service -Name postgresql*
Set-Service -InputObject $serviceName -StartupType Automatic
Start-Service -InputObject $serviceName
& $env:PGBIN\createdb.exe -U postgres dbt
& $env:PGBIN\psql.exe -U postgres -c "CREATE ROLE root WITH PASSWORD 'password';"
& $env:PGBIN\psql.exe -U postgres -c "ALTER ROLE root WITH LOGIN;"
& $env:PGBIN\psql.exe -U postgres -c "GRANT CREATE, CONNECT ON DATABASE dbt TO root WITH GRANT OPTION;"
& $env:PGBIN\psql.exe -U postgres -c "CREATE ROLE noaccess WITH PASSWORD 'password' NOSUPERUSER;"
& $env:PGBIN\psql.exe -U postgres -c "ALTER ROLE noaccess WITH LOGIN;"
& $env:PGBIN\psql.exe -U postgres -c "GRANT CONNECT ON DATABASE dbt TO noaccess;"
displayName: Install postgresql and set up database
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
architecture: 'x64'
- script: python -m pip install --upgrade pip && pip install tox
displayName: 'Install dependencies'
- script: python -m tox -e pywin-postgres
displayName: Run integration tests
# These three are all similar except secure environment variables, which MUST be passed along to their tasks,
# but there's probably a better way to do this!
- job: SnowflakeIntegrationTest
pool:
vmImage: 'vs2017-win2016'
dependsOn: PostgresIntegrationTest
condition: succeeded()
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
architecture: 'x64'
- script: python -m pip install --upgrade pip && pip install tox
displayName: 'Install dependencies'
- script: python -m tox -e pywin-snowflake
env:
SNOWFLAKE_TEST_ACCOUNT: $(SNOWFLAKE_TEST_ACCOUNT)
SNOWFLAKE_TEST_PASSWORD: $(SNOWFLAKE_TEST_PASSWORD)
SNOWFLAKE_TEST_USER: $(SNOWFLAKE_TEST_USER)
SNOWFLAKE_TEST_WAREHOUSE: $(SNOWFLAKE_TEST_WAREHOUSE)
SNOWFLAKE_TEST_OAUTH_REFRESH_TOKEN: $(SNOWFLAKE_TEST_OAUTH_REFRESH_TOKEN)
SNOWFLAKE_TEST_OAUTH_CLIENT_ID: $(SNOWFLAKE_TEST_OAUTH_CLIENT_ID)
SNOWFLAKE_TEST_OAUTH_CLIENT_SECRET: $(SNOWFLAKE_TEST_OAUTH_CLIENT_SECRET)
displayName: Run integration tests
- job: BigQueryIntegrationTest
pool:
vmImage: 'vs2017-win2016'
dependsOn: PostgresIntegrationTest
condition: succeeded()
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
architecture: 'x64'
- script: python -m pip install --upgrade pip && pip install tox
displayName: 'Install dependencies'
- script: python -m tox -e pywin-bigquery
env:
BIGQUERY_SERVICE_ACCOUNT_JSON: $(BIGQUERY_SERVICE_ACCOUNT_JSON)
displayName: Run integration tests
- job: RedshiftIntegrationTest
pool:
vmImage: 'vs2017-win2016'
dependsOn: PostgresIntegrationTest
condition: succeeded()
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
architecture: 'x64'
- script: python -m pip install --upgrade pip && pip install tox
displayName: 'Install dependencies'
- script: python -m tox -e pywin-redshift
env:
REDSHIFT_TEST_DBNAME: $(REDSHIFT_TEST_DBNAME)
REDSHIFT_TEST_PASS: $(REDSHIFT_TEST_PASS)
REDSHIFT_TEST_USER: $(REDSHIFT_TEST_USER)
REDSHIFT_TEST_PORT: $(REDSHIFT_TEST_PORT)
REDSHIFT_TEST_HOST: $(REDSHIFT_TEST_HOST)
displayName: Run integration tests
- job: BuildWheel
pool:
vmImage: 'vs2017-win2016'
dependsOn:
- UnitTest
- PostgresIntegrationTest
- RedshiftIntegrationTest
- SnowflakeIntegrationTest
- BigQueryIntegrationTest
condition: succeeded()
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
architecture: 'x64'
- script: python -m pip install --upgrade pip setuptools && python -m pip install -r requirements.txt && python -m pip install -r dev_requirements.txt
displayName: Install dependencies
- task: ShellScript@2
inputs:
scriptPath: scripts/build-wheels.sh
- task: CopyFiles@2
inputs:
contents: 'dist\?(*.whl|*.tar.gz)'
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: dists

View File

@@ -0,0 +1,14 @@
# these are all just exports, #noqa them so flake8 will be happy
# TODO: Should we still include this in the `adapters` namespace?
from dbt.contracts.connection import Credentials # noqa
from dbt.adapters.base.meta import available # noqa
from dbt.adapters.base.connections import BaseConnectionManager # noqa
from dbt.adapters.base.relation import ( # noqa
BaseRelation,
RelationType,
SchemaSearchMap,
)
from dbt.adapters.base.column import Column # noqa
from dbt.adapters.base.impl import AdapterConfig, BaseAdapter # noqa
from dbt.adapters.base.plugin import AdapterPlugin # noqa

View File

@@ -0,0 +1,155 @@
from dataclasses import dataclass
import re
from hologram import JsonSchemaMixin
from dbt.exceptions import RuntimeException
from typing import Dict, ClassVar, Any, Optional
@dataclass
class Column(JsonSchemaMixin):
TYPE_LABELS: ClassVar[Dict[str, str]] = {
'STRING': 'TEXT',
'TIMESTAMP': 'TIMESTAMP',
'FLOAT': 'FLOAT',
'INTEGER': 'INT'
}
column: str
dtype: str
char_size: Optional[int] = None
numeric_precision: Optional[Any] = None
numeric_scale: Optional[Any] = None
@classmethod
def translate_type(cls, dtype: str) -> str:
return cls.TYPE_LABELS.get(dtype.upper(), dtype)
@classmethod
def create(cls, name, label_or_dtype: str) -> 'Column':
column_type = cls.translate_type(label_or_dtype)
return cls(name, column_type)
@property
def name(self) -> str:
return self.column
@property
def quoted(self) -> str:
return '"{}"'.format(self.column)
@property
def data_type(self) -> str:
if self.is_string():
return Column.string_type(self.string_size())
elif self.is_numeric():
return Column.numeric_type(self.dtype, self.numeric_precision,
self.numeric_scale)
else:
return self.dtype
def is_string(self) -> bool:
return self.dtype.lower() in ['text', 'character varying', 'character',
'varchar']
def is_number(self):
return any([self.is_integer(), self.is_numeric(), self.is_float()])
def is_float(self):
return self.dtype.lower() in [
# floats
'real', 'float4', 'float', 'double precision', 'float8'
]
def is_integer(self) -> bool:
return self.dtype.lower() in [
# real types
'smallint', 'integer', 'bigint',
'smallserial', 'serial', 'bigserial',
# aliases
'int2', 'int4', 'int8',
'serial2', 'serial4', 'serial8',
]
def is_numeric(self) -> bool:
return self.dtype.lower() in ['numeric', 'decimal']
def string_size(self) -> int:
if not self.is_string():
raise RuntimeException("Called string_size() on non-string field!")
if self.dtype == 'text' or self.char_size is None:
# char_size should never be None. Handle it reasonably just in case
return 256
else:
return int(self.char_size)
def can_expand_to(self, other_column: 'Column') -> bool:
"""returns True if this column can be expanded to the size of the
other column"""
if not self.is_string() or not other_column.is_string():
return False
return other_column.string_size() > self.string_size()
def literal(self, value: Any) -> str:
return "{}::{}".format(value, self.data_type)
@classmethod
def string_type(cls, size: int) -> str:
return "character varying({})".format(size)
@classmethod
def numeric_type(cls, dtype: str, precision: Any, scale: Any) -> str:
# This could be decimal(...), numeric(...), number(...)
# Just use whatever was fed in here -- don't try to get too clever
if precision is None or scale is None:
return dtype
else:
return "{}({},{})".format(dtype, precision, scale)
def __repr__(self) -> str:
return "<Column {} ({})>".format(self.name, self.data_type)
@classmethod
def from_description(cls, name: str, raw_data_type: str) -> 'Column':
match = re.match(r'([^(]+)(\([^)]+\))?', raw_data_type)
if match is None:
raise RuntimeException(
f'Could not interpret data type "{raw_data_type}"'
)
data_type, size_info = match.groups()
char_size = None
numeric_precision = None
numeric_scale = None
if size_info is not None:
# strip out the parentheses
size_info = size_info[1:-1]
parts = size_info.split(',')
if len(parts) == 1:
try:
char_size = int(parts[0])
except ValueError:
raise RuntimeException(
f'Could not interpret data_type "{raw_data_type}": '
f'could not convert "{parts[0]}" to an integer'
)
elif len(parts) == 2:
try:
numeric_precision = int(parts[0])
except ValueError:
raise RuntimeException(
f'Could not interpret data_type "{raw_data_type}": '
f'could not convert "{parts[0]}" to an integer'
)
try:
numeric_scale = int(parts[1])
except ValueError:
raise RuntimeException(
f'Could not interpret data_type "{raw_data_type}": '
f'could not convert "{parts[1]}" to an integer'
)
return cls(
name, data_type, char_size, numeric_precision, numeric_scale
)

View File

@@ -0,0 +1,305 @@
import abc
import os
# multiprocessing.RLock is a function returning this type
from multiprocessing.synchronize import RLock
from threading import get_ident
from typing import (
Dict, Tuple, Hashable, Optional, ContextManager, List
)
import agate
import dbt.exceptions
from dbt.contracts.connection import (
Connection, Identifier, ConnectionState, AdapterRequiredConfig, LazyHandle
)
from dbt.contracts.graph.manifest import Manifest
from dbt.adapters.base.query_headers import (
MacroQueryStringSetter,
)
from dbt.logger import GLOBAL_LOGGER as logger
from dbt import flags
class BaseConnectionManager(metaclass=abc.ABCMeta):
"""Methods to implement:
- exception_handler
- cancel_open
- open
- begin
- commit
- clear_transaction
- execute
You must also set the 'TYPE' class attribute with a class-unique constant
string.
"""
TYPE: str = NotImplemented
def __init__(self, profile: AdapterRequiredConfig):
self.profile = profile
self.thread_connections: Dict[Hashable, Connection] = {}
self.lock: RLock = flags.MP_CONTEXT.RLock()
self.query_header: Optional[MacroQueryStringSetter] = None
def set_query_header(self, manifest: Manifest) -> None:
self.query_header = MacroQueryStringSetter(self.profile, manifest)
@staticmethod
def get_thread_identifier() -> Hashable:
# note that get_ident() may be re-used, but we should never experience
# that within a single process
return (os.getpid(), get_ident())
def get_thread_connection(self) -> Connection:
key = self.get_thread_identifier()
with self.lock:
if key not in self.thread_connections:
raise dbt.exceptions.InvalidConnectionException(
key, list(self.thread_connections)
)
return self.thread_connections[key]
def set_thread_connection(self, conn: Connection) -> None:
key = self.get_thread_identifier()
if key in self.thread_connections:
raise dbt.exceptions.InternalException(
'In set_thread_connection, existing connection exists for {}'
)
self.thread_connections[key] = conn
def get_if_exists(self) -> Optional[Connection]:
key = self.get_thread_identifier()
with self.lock:
return self.thread_connections.get(key)
def clear_thread_connection(self) -> None:
key = self.get_thread_identifier()
with self.lock:
if key in self.thread_connections:
del self.thread_connections[key]
def clear_transaction(self) -> None:
"""Clear any existing transactions."""
conn = self.get_thread_connection()
if conn is not None:
if conn.transaction_open:
self._rollback(conn)
self.begin()
self.commit()
def rollback_if_open(self) -> None:
conn = self.get_if_exists()
if conn is not None and conn.handle and conn.transaction_open:
self._rollback(conn)
@abc.abstractmethod
def exception_handler(self, sql: str) -> ContextManager:
"""Create a context manager that handles exceptions caused by database
interactions.
:param str sql: The SQL string that the block inside the context
manager is executing.
:return: A context manager that handles exceptions raised by the
underlying database.
"""
raise dbt.exceptions.NotImplementedException(
'`exception_handler` is not implemented for this adapter!')
def set_connection_name(self, name: Optional[str] = None) -> Connection:
conn_name: str
if name is None:
# if a name isn't specified, we'll re-use a single handle
# named 'master'
conn_name = 'master'
else:
if not isinstance(name, str):
raise dbt.exceptions.CompilerException(
f'For connection name, got {name} - not a string!'
)
assert isinstance(name, str)
conn_name = name
conn = self.get_if_exists()
if conn is None:
conn = Connection(
type=Identifier(self.TYPE),
name=None,
state=ConnectionState.INIT,
transaction_open=False,
handle=None,
credentials=self.profile.credentials
)
self.set_thread_connection(conn)
if conn.name == conn_name and conn.state == 'open':
return conn
logger.debug(
'Acquiring new {} connection "{}".'.format(self.TYPE, conn_name))
if conn.state == 'open':
logger.debug(
'Re-using an available connection from the pool (formerly {}).'
.format(conn.name)
)
else:
conn.handle = LazyHandle(self.open)
conn.name = conn_name
return conn
@abc.abstractmethod
def cancel_open(self) -> Optional[List[str]]:
"""Cancel all open connections on the adapter. (passable)"""
raise dbt.exceptions.NotImplementedException(
'`cancel_open` is not implemented for this adapter!'
)
@abc.abstractclassmethod
def open(cls, connection: Connection) -> Connection:
"""Open the given connection on the adapter and return it.
This may mutate the given connection (in particular, its state and its
handle).
This should be thread-safe, or hold the lock if necessary. The given
connection should not be in either in_use or available.
"""
raise dbt.exceptions.NotImplementedException(
'`open` is not implemented for this adapter!'
)
def release(self) -> None:
with self.lock:
conn = self.get_if_exists()
if conn is None:
return
try:
# always close the connection. close() calls _rollback() if there
# is an open transaction
self.close(conn)
except Exception:
# if rollback or close failed, remove our busted connection
self.clear_thread_connection()
raise
def cleanup_all(self) -> None:
with self.lock:
for connection in self.thread_connections.values():
if connection.state not in {'closed', 'init'}:
logger.debug("Connection '{}' was left open."
.format(connection.name))
else:
logger.debug("Connection '{}' was properly closed."
.format(connection.name))
self.close(connection)
# garbage collect these connections
self.thread_connections.clear()
@abc.abstractmethod
def begin(self) -> None:
"""Begin a transaction. (passable)"""
raise dbt.exceptions.NotImplementedException(
'`begin` is not implemented for this adapter!'
)
@abc.abstractmethod
def commit(self) -> None:
"""Commit a transaction. (passable)"""
raise dbt.exceptions.NotImplementedException(
'`commit` is not implemented for this adapter!'
)
@classmethod
def _rollback_handle(cls, connection: Connection) -> None:
"""Perform the actual rollback operation."""
try:
connection.handle.rollback()
except Exception:
logger.debug(
'Failed to rollback {}'.format(connection.name),
exc_info=True
)
@classmethod
def _close_handle(cls, connection: Connection) -> None:
"""Perform the actual close operation."""
# On windows, sometimes connection handles don't have a close() attr.
if hasattr(connection.handle, 'close'):
logger.debug(f'On {connection.name}: Close')
connection.handle.close()
else:
logger.debug(f'On {connection.name}: No close available on handle')
@classmethod
def _rollback(cls, connection: Connection) -> None:
"""Roll back the given connection."""
if flags.STRICT_MODE:
if not isinstance(connection, Connection):
raise dbt.exceptions.CompilerException(
f'In _rollback, got {connection} - not a Connection!'
)
if connection.transaction_open is False:
raise dbt.exceptions.InternalException(
f'Tried to rollback transaction on connection '
f'"{connection.name}", but it does not have one open!'
)
logger.debug(f'On {connection.name}: ROLLBACK')
cls._rollback_handle(connection)
connection.transaction_open = False
@classmethod
def close(cls, connection: Connection) -> Connection:
if flags.STRICT_MODE:
if not isinstance(connection, Connection):
raise dbt.exceptions.CompilerException(
f'In close, got {connection} - not a Connection!'
)
# if the connection is in closed or init, there's nothing to do
if connection.state in {ConnectionState.CLOSED, ConnectionState.INIT}:
return connection
if connection.transaction_open and connection.handle:
logger.debug('On {}: ROLLBACK'.format(connection.name))
cls._rollback_handle(connection)
connection.transaction_open = False
cls._close_handle(connection)
connection.state = ConnectionState.CLOSED
return connection
def commit_if_has_connection(self) -> None:
"""If the named connection exists, commit the current transaction."""
connection = self.get_if_exists()
if connection:
self.commit()
def _add_query_comment(self, sql: str) -> str:
if self.query_header is None:
return sql
return self.query_header.add(sql)
@abc.abstractmethod
def execute(
self, sql: str, auto_begin: bool = False, fetch: bool = False
) -> Tuple[str, agate.Table]:
"""Execute the given SQL.
:param str sql: The sql to execute.
:param bool auto_begin: If set, and dbt is not currently inside a
transaction, automatically begin one.
:param bool fetch: If set, fetch results.
:return: A tuple of the status and the results (empty if fetch=False).
:rtype: Tuple[str, agate.Table]
"""
raise dbt.exceptions.NotImplementedException(
'`execute` is not implemented for this adapter!'
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
import abc
from functools import wraps
from typing import Callable, Optional, Any, FrozenSet, Dict, Set
from dbt.deprecations import warn, renamed_method
Decorator = Callable[[Any], Callable]
class _Available:
def __call__(self, func: Callable) -> Callable:
func._is_available_ = True # type: ignore
return func
def parse(self, parse_replacement: Callable) -> Decorator:
"""A decorator factory to indicate that a method on the adapter will be
exposed to the database wrapper, and will be stubbed out at parse time
with the given function.
@available.parse()
def my_method(self, a, b):
if something:
return None
return big_expensive_db_query()
@available.parse(lambda *args, **args: {})
def my_other_method(self, a, b):
x = {}
x.update(big_expensive_db_query())
return x
"""
def inner(func):
func._parse_replacement_ = parse_replacement
return self(func)
return inner
def deprecated(
self, supported_name: str, parse_replacement: Optional[Callable] = None
) -> Decorator:
"""A decorator that marks a function as available, but also prints a
deprecation warning. Use like
@available.deprecated('my_new_method')
def my_old_method(self, arg):
args = compatability_shim(arg)
return self.my_new_method(*args)
@available.deprecated('my_new_slow_method', lambda *a, **k: (0, ''))
def my_old_slow_method(self, arg):
args = compatibility_shim(arg)
return self.my_new_slow_method(*args)
To make `adapter.my_old_method` available but also print out a warning
on use directing users to `my_new_method`.
The optional parse_replacement, if provided, will provide a parse-time
replacement for the actual method (see `available.parse`).
"""
def wrapper(func):
func_name = func.__name__
renamed_method(func_name, supported_name)
@wraps(func)
def inner(*args, **kwargs):
warn('adapter:{}'.format(func_name))
return func(*args, **kwargs)
if parse_replacement:
available_function = self.parse(parse_replacement)
else:
available_function = self
return available_function(inner)
return wrapper
def parse_none(self, func: Callable) -> Callable:
wrapper = self.parse(lambda *a, **k: None)
return wrapper(func)
def parse_list(self, func: Callable) -> Callable:
wrapper = self.parse(lambda *a, **k: [])
return wrapper(func)
available = _Available()
class AdapterMeta(abc.ABCMeta):
_available_: FrozenSet[str]
_parse_replacements_: Dict[str, Callable]
def __new__(mcls, name, bases, namespace, **kwargs):
# 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,
# but having it doesn't hurt on the python side (and omitting it could
# hurt for obscure metaclass reasons, for all I know)
cls = abc.ABCMeta.__new__( # type: ignore
mcls, name, bases, namespace, **kwargs
)
# this is very much inspired by ABCMeta's own implementation
# dict mapping the method name to whether the model name should be
# injected into the arguments. All methods in here are exposed to the
# context.
available: Set[str] = set()
replacements: Dict[str, Any] = {}
# collect base class data first
for base in bases:
available.update(getattr(base, '_available_', set()))
replacements.update(getattr(base, '_parse_replacements_', set()))
# override with local data if it exists
for name, value in namespace.items():
if getattr(value, '_is_available_', False):
available.add(name)
parse_replacement = getattr(value, '_parse_replacement_', None)
if parse_replacement is not None:
replacements[name] = parse_replacement
cls._available_ = frozenset(available)
# should this be a namedtuple so it will be immutable like _available_?
cls._parse_replacements_ = replacements
return cls

View File

@@ -0,0 +1,42 @@
from typing import List, Optional, Type
from dbt.adapters.base import Credentials
from dbt.exceptions import CompilationException
from dbt.adapters.protocol import AdapterProtocol
def project_name_from_path(include_path: str) -> str:
# avoid an import cycle
from dbt.config.project import Project
partial = Project.partial_load(include_path)
if partial.project_name is None:
raise CompilationException(
f'Invalid project at {include_path}: name not set!'
)
return partial.project_name
class AdapterPlugin:
"""Defines the basic requirements for a dbt adapter plugin.
:param include_path: The path to this adapter plugin's root
:param dependencies: A list of adapter names that this adapter depends
upon.
"""
def __init__(
self,
adapter: Type[AdapterProtocol],
credentials: Type[Credentials],
include_path: str,
dependencies: Optional[List[str]] = None
):
self.adapter: Type[AdapterProtocol] = adapter
self.credentials: Type[Credentials] = credentials
self.include_path: str = include_path
self.project_name: str = project_name_from_path(include_path)
self.dependencies: List[str]
if dependencies is None:
self.dependencies = []
else:
self.dependencies = dependencies

View File

@@ -0,0 +1,101 @@
from threading import local
from typing import Optional, Callable, Dict, Any
from dbt.clients.jinja import QueryStringGenerator
from dbt.context.manifest import generate_query_header_context
from dbt.contracts.connection import AdapterRequiredConfig, QueryComment
from dbt.contracts.graph.compiled import CompileResultNode
from dbt.contracts.graph.manifest import Manifest
from dbt.exceptions import RuntimeException
class NodeWrapper:
def __init__(self, node):
self._inner_node = node
def __getattr__(self, name):
return getattr(self._inner_node, name, '')
class _QueryComment(local):
"""A thread-local class storing thread-specific state information for
connection management, namely:
- the current thread's query comment.
- a source_name indicating what set the current thread's query comment
"""
def __init__(self, initial):
self.query_comment: Optional[str] = initial
self.append = False
def add(self, sql: str) -> str:
if not self.query_comment:
return sql
if self.append:
# replace last ';' with '<comment>;'
sql = sql.rstrip()
if sql[-1] == ';':
sql = sql[:-1]
return '{}\n/* {} */;'.format(sql, self.query_comment.strip())
return '{}\n/* {} */'.format(sql, self.query_comment.strip())
return '/* {} */\n{}'.format(self.query_comment.strip(), sql)
def set(self, comment: Optional[str], append: bool):
if isinstance(comment, str) and '*/' in comment:
# tell the user "no" so they don't hurt themselves by writing
# garbage
raise RuntimeException(
f'query comment contains illegal value "*/": {comment}'
)
self.query_comment = comment
self.append = append
QueryStringFunc = Callable[[str, Optional[NodeWrapper]], str]
class MacroQueryStringSetter:
def __init__(self, config: AdapterRequiredConfig, manifest: Manifest):
self.manifest = manifest
self.config = config
comment_macro = self._get_comment_macro()
self.generator: QueryStringFunc = lambda name, model: ''
# if the comment value was None or the empty string, just skip it
if comment_macro:
assert isinstance(comment_macro, str)
macro = '\n'.join((
'{%- macro query_comment_macro(connection_name, node) -%}',
comment_macro,
'{% endmacro %}'
))
ctx = self._get_context()
self.generator = QueryStringGenerator(macro, ctx)
self.comment = _QueryComment(None)
self.reset()
def _get_comment_macro(self) -> Optional[str]:
return self.config.query_comment.comment
def _get_context(self) -> Dict[str, Any]:
return generate_query_header_context(self.config, self.manifest)
def add(self, sql: str) -> str:
return self.comment.add(sql)
def reset(self):
self.set('master', None)
def set(self, name: str, node: Optional[CompileResultNode]):
wrapped: Optional[NodeWrapper] = None
if node is not None:
wrapped = NodeWrapper(node)
comment_str = self.generator(name, wrapped)
append = False
if isinstance(self.config.query_comment, QueryComment):
append = self.config.query_comment.append
self.comment.set(comment_str, append)

View File

@@ -0,0 +1,455 @@
from collections.abc import Hashable
from dataclasses import dataclass
from typing import (
Optional, TypeVar, Any, Type, Dict, Union, Iterator, Tuple, Set
)
from dbt.contracts.graph.compiled import CompiledNode
from dbt.contracts.graph.parsed import ParsedSourceDefinition, ParsedNode
from dbt.contracts.relation import (
RelationType, ComponentName, HasQuoting, FakeAPIObject, Policy, Path
)
from dbt.exceptions import InternalException
from dbt.node_types import NodeType
from dbt.utils import filter_null_values, deep_merge, classproperty
import dbt.exceptions
Self = TypeVar('Self', bound='BaseRelation')
@dataclass(frozen=True, eq=False, repr=False)
class BaseRelation(FakeAPIObject, Hashable):
type: Optional[RelationType]
path: Path
quote_character: str = '"'
include_policy: Policy = Policy()
quote_policy: Policy = Policy()
dbt_created: bool = False
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()
else:
return self.path.get_part(field) == value
@classmethod
def _get_field_named(cls, field_name):
for field, _ in cls._get_fields():
if field.name == field_name:
return field
# this should be unreachable
raise ValueError(f'BaseRelation has no {field_name} field!')
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.to_dict() == other.to_dict()
@classmethod
def get_default_quote_policy(cls) -> Policy:
return cls._get_field_named('quote_policy').default
@classmethod
def get_default_include_policy(cls) -> Policy:
return cls._get_field_named('include_policy').default
def get(self, key, default=None):
"""Override `.get` to return a metadata object so we don't break
dbt_utils.
"""
if key == 'metadata':
return {
'type': self.__class__.__name__
}
return super().get(key, default)
def matches(
self,
database: Optional[str] = None,
schema: Optional[str] = None,
identifier: Optional[str] = None,
) -> bool:
search = filter_null_values({
ComponentName.Database: database,
ComponentName.Schema: schema,
ComponentName.Identifier: identifier
})
if not search:
# nothing was passed in
raise dbt.exceptions.RuntimeException(
"Tried to match relation, but no search path was passed!")
exact_match = True
approximate_match = True
for k, v in search.items():
if not self._is_exactish_match(k, v):
exact_match = False
if self.path.get_lowered_part(k) != v.lower():
approximate_match = False
if approximate_match and not exact_match:
target = self.create(
database=database, schema=schema, identifier=identifier
)
dbt.exceptions.approximate_relation_match(target, self)
return exact_match
def replace_path(self, **kwargs):
return self.replace(path=self.path.replace(**kwargs))
def quote(
self: Self,
database: Optional[bool] = None,
schema: Optional[bool] = None,
identifier: Optional[bool] = None,
) -> Self:
policy = filter_null_values({
ComponentName.Database: database,
ComponentName.Schema: schema,
ComponentName.Identifier: identifier
})
new_quote_policy = self.quote_policy.replace_dict(policy)
return self.replace(quote_policy=new_quote_policy)
def include(
self: Self,
database: Optional[bool] = None,
schema: Optional[bool] = None,
identifier: Optional[bool] = None,
) -> Self:
policy = filter_null_values({
ComponentName.Database: database,
ComponentName.Schema: schema,
ComponentName.Identifier: identifier
})
new_include_policy = self.include_policy.replace_dict(policy)
return self.replace(include_policy=new_include_policy)
def information_schema(self, view_name=None) -> 'InformationSchema':
# some of our data comes from jinja, where things can be `Undefined`.
if not isinstance(view_name, str):
view_name = None
# Kick the user-supplied schema out of the information schema relation
# Instead address this as <database>.information_schema by default
info_schema = InformationSchema.from_relation(self, view_name)
return info_schema.incorporate(path={"schema": None})
def information_schema_only(self) -> 'InformationSchema':
return self.information_schema()
def without_identifier(self) -> 'BaseRelation':
"""Return a form of this relation that only has the database and schema
set to included. To get the appropriately-quoted form the schema out of
the result (for use as part of a query), use `.render()`. To get the
raw database or schema name, use `.database` or `.schema`.
The hash of the returned object is the result of render().
"""
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):
path_part = self.path.get_part(key)
if path_part is not None and self.quote_policy.get_part(key):
path_part = self.quoted(path_part)
yield key, path_part
def render(self) -> str:
# if there is nothing set, this will return the empty string.
return '.'.join(
part for _, part in self._render_iterator()
if part is not None
)
def quoted(self, identifier):
return '{quote_char}{identifier}{quote_char}'.format(
quote_char=self.quote_character,
identifier=identifier,
)
@classmethod
def create_from_source(
cls: Type[Self], source: ParsedSourceDefinition, **kwargs: Any
) -> Self:
source_quoting = source.quoting.to_dict()
source_quoting.pop('column', None)
quote_policy = deep_merge(
cls.get_default_quote_policy().to_dict(),
source_quoting,
kwargs.get('quote_policy', {}),
)
return cls.create(
database=source.database,
schema=source.schema,
identifier=source.identifier,
quote_policy=quote_policy,
**kwargs
)
@staticmethod
def add_ephemeral_prefix(name: str):
return f'__dbt__CTE__{name}'
@classmethod
def create_ephemeral_from_node(
cls: Type[Self],
config: HasQuoting,
node: Union[ParsedNode, CompiledNode],
) -> Self:
# Note that ephemeral models are based on the name.
identifier = cls.add_ephemeral_prefix(node.name)
return cls.create(
type=cls.CTE,
identifier=identifier,
).quote(identifier=False)
@classmethod
def create_from_node(
cls: Type[Self],
config: HasQuoting,
node: Union[ParsedNode, CompiledNode],
quote_policy: Optional[Dict[str, bool]] = None,
**kwargs: Any,
) -> Self:
if quote_policy is None:
quote_policy = {}
quote_policy = dbt.utils.merge(config.quoting, quote_policy)
return cls.create(
database=node.database,
schema=node.schema,
identifier=node.alias,
quote_policy=quote_policy,
**kwargs)
@classmethod
def create_from(
cls: Type[Self],
config: HasQuoting,
node: Union[CompiledNode, ParsedNode, ParsedSourceDefinition],
**kwargs: Any,
) -> Self:
if node.resource_type == NodeType.Source:
if not isinstance(node, ParsedSourceDefinition):
raise InternalException(
'type mismatch, expected ParsedSourceDefinition but got {}'
.format(type(node))
)
return cls.create_from_source(node, **kwargs)
else:
if not isinstance(node, (ParsedNode, CompiledNode)):
raise InternalException(
'type mismatch, expected ParsedNode or CompiledNode but '
'got {}'.format(type(node))
)
return cls.create_from_node(config, node, **kwargs)
@classmethod
def create(
cls: Type[Self],
database: Optional[str] = None,
schema: Optional[str] = None,
identifier: Optional[str] = None,
type: Optional[RelationType] = None,
**kwargs,
) -> Self:
kwargs.update({
'path': {
'database': database,
'schema': schema,
'identifier': identifier,
},
'type': type,
})
return cls.from_dict(kwargs)
def __repr__(self) -> str:
return "<{} {}>".format(self.__class__.__name__, self.render())
def __hash__(self) -> int:
return hash(self.render())
def __str__(self) -> str:
return self.render()
@property
def database(self) -> Optional[str]:
return self.path.database
@property
def schema(self) -> Optional[str]:
return self.path.schema
@property
def identifier(self) -> Optional[str]:
return self.path.identifier
@property
def table(self) -> Optional[str]:
return self.path.identifier
# Here for compatibility with old Relation interface
@property
def name(self) -> Optional[str]:
return self.identifier
@property
def is_table(self) -> bool:
return self.type == RelationType.Table
@property
def is_cte(self) -> bool:
return self.type == RelationType.CTE
@property
def is_view(self) -> bool:
return self.type == RelationType.View
@classproperty
def Table(cls) -> str:
return str(RelationType.Table)
@classproperty
def CTE(cls) -> str:
return str(RelationType.CTE)
@classproperty
def View(cls) -> str:
return str(RelationType.View)
@classproperty
def External(cls) -> str:
return str(RelationType.External)
@classproperty
def get_relation_type(cls) -> Type[RelationType]:
return RelationType
Info = TypeVar('Info', bound='InformationSchema')
@dataclass(frozen=True, eq=False, repr=False)
class InformationSchema(BaseRelation):
information_schema_view: Optional[str] = None
def __post_init__(self):
if not isinstance(self.information_schema_view, (type(None), str)):
raise dbt.exceptions.CompilationException(
'Got an invalid name: {}'.format(self.information_schema_view)
)
@classmethod
def get_path(
cls, relation: BaseRelation, information_schema_view: Optional[str]
) -> Path:
return Path(
database=relation.database,
schema=relation.schema,
identifier='INFORMATION_SCHEMA',
)
@classmethod
def get_include_policy(
cls,
relation,
information_schema_view: Optional[str],
) -> Policy:
return relation.include_policy.replace(
database=relation.database is not None,
schema=False,
identifier=True,
)
@classmethod
def get_quote_policy(
cls,
relation,
information_schema_view: Optional[str],
) -> Policy:
return relation.quote_policy.replace(
identifier=False,
)
@classmethod
def from_relation(
cls: Type[Info],
relation: BaseRelation,
information_schema_view: Optional[str],
) -> Info:
include_policy = cls.get_include_policy(
relation, information_schema_view
)
quote_policy = cls.get_quote_policy(relation, information_schema_view)
path = cls.get_path(relation, information_schema_view)
return cls(
type=RelationType.View,
path=path,
include_policy=include_policy,
quote_policy=quote_policy,
information_schema_view=information_schema_view,
)
def _render_iterator(self):
for k, v in super()._render_iterator():
yield k, v
yield None, self.information_schema_view
class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
"""A utility class to keep track of what information_schema tables to
search for what schemas. The schema values are all lowercased to avoid
duplication.
"""
def add(self, relation: BaseRelation):
key = relation.information_schema_only()
if key not in self:
self[key] = set()
schema: Optional[str] = None
if relation.schema is not None:
schema = relation.schema.lower()
self[key].add(schema)
def search(
self
) -> Iterator[Tuple[InformationSchema, Optional[str]]]:
for information_schema_name, schemas in self.items():
for schema in schemas:
yield information_schema_name, schema
def flatten(self):
new = self.__class__()
# make sure we don't have duplicates
seen = {r.database.lower() for r in self if r.database}
if len(seen) > 1:
dbt.exceptions.raise_compiler_error(str(seen))
for information_schema_name, schema in self.search():
path = {
'database': information_schema_name.database,
'schema': schema
}
new.add(information_schema_name.incorporate(
path=path,
quote_policy={'database': False},
include_policy={'database': False},
))
return new

View File

@@ -1,47 +1,62 @@
from collections import namedtuple
import threading
from copy import deepcopy
import pprint
from typing import List, Iterable, Optional, Dict, Set, Tuple, Any
import threading
from dbt.logger import CACHE_LOGGER as logger
from dbt.utils import lowercase
import dbt.exceptions
_ReferenceKey = namedtuple('_ReferenceKey', 'schema identifier')
_ReferenceKey = namedtuple('_ReferenceKey', 'database schema identifier')
def dot_separated(key):
def _make_key(relation) -> _ReferenceKey:
"""Make _ReferenceKeys with lowercase values for the cache so we don't have
to keep track of quoting
"""
# databases and schemas can both be None
return _ReferenceKey(lowercase(relation.database),
lowercase(relation.schema),
lowercase(relation.identifier))
def dot_separated(key: _ReferenceKey) -> str:
"""Return the key in dot-separated string form.
:param key _ReferenceKey: The key to stringify.
:param _ReferenceKey key: The key to stringify.
"""
return '.'.join(key)
return '.'.join(map(str, key))
class _CachedRelation(object):
class _CachedRelation:
"""Nothing about _CachedRelation is guaranteed to be thread-safe!
:attr str schema: The schema of this relation.
:attr str identifier: The identifier of this relation.
:attr Dict[_ReferenceKey, _CachedRelation] referenced_by: The relations
that refer to this relation.
:attr DefaultRelation inner: The underlying dbt relation.
:attr BaseRelation inner: The underlying dbt relation.
"""
def __init__(self, inner):
self.referenced_by = {}
self.inner = inner
def __str__(self):
def __str__(self) -> str:
return (
'_CachedRelation(schema={}, identifier={}, inner={})'
).format(self.schema, self.identifier, self.inner)
'_CachedRelation(database={}, schema={}, identifier={}, inner={})'
).format(self.database, self.schema, self.identifier, self.inner)
@property
def schema(self):
return self.inner.schema
def database(self) -> Optional[str]:
return lowercase(self.inner.database)
@property
def identifier(self):
return self.inner.identifier
def schema(self) -> Optional[str]:
return lowercase(self.inner.schema)
@property
def identifier(self) -> Optional[str]:
return lowercase(self.inner.identifier)
def __copy__(self):
new = self.__class__(self.inner)
@@ -61,9 +76,9 @@ class _CachedRelation(object):
:return _ReferenceKey: A key for this relation.
"""
return _ReferenceKey(self.schema, self.identifier)
return _make_key(self)
def add_reference(self, referrer):
def add_reference(self, referrer: '_CachedRelation'):
"""Add a reference from referrer to self, indicating that if this node
were drop...cascaded, the referrer would be dropped as well.
@@ -98,7 +113,7 @@ class _CachedRelation(object):
Note that this will change the output of key(), all refs must be
updated!
:param _ReferenceKey new_relation: The new name to apply to the
:param _CachedRelation new_relation: The new name to apply to the
relation
"""
# Relations store this stuff inside their `path` dict. But they
@@ -107,10 +122,10 @@ class _CachedRelation(object):
# table_name is ever anything but the identifier (via .create())
self.inner = self.inner.incorporate(
path={
'schema': new_relation.schema,
'identifier': new_relation.identifier
'database': new_relation.inner.database,
'schema': new_relation.inner.schema,
'identifier': new_relation.inner.identifier
},
table_name=new_relation.identifier
)
def rename_key(self, old_key, new_key):
@@ -142,7 +157,13 @@ class _CachedRelation(object):
return [dot_separated(r) for r in self.referenced_by]
class RelationsCache(object):
def lazy_log(msg, func):
if logger.disabled:
return
logger.debug(msg.format(func()))
class RelationsCache:
"""A cache of the relations known to dbt. Keeps track of relationships
declared between tables and handles renames/drops as a real database would.
@@ -151,32 +172,56 @@ class RelationsCache(object):
The adapters also hold this lock while filling the cache.
:attr Set[str] schemas: The set of known/cached schemas, all lowercased.
"""
def __init__(self):
self.relations = {}
def __init__(self) -> None:
self.relations: Dict[_ReferenceKey, _CachedRelation] = {}
self.lock = threading.RLock()
self.schemas = set()
self.schemas: Set[Tuple[Optional[str], Optional[str]]] = set()
def add_schema(self, schema):
def add_schema(
self, database: Optional[str], schema: Optional[str],
) -> None:
"""Add a schema to the set of known schemas (case-insensitive)
:param str schema: The schema name to add.
:param database: The database name to add.
:param schema: The schema name to add.
"""
self.schemas.add(schema.lower())
self.schemas.add((lowercase(database), lowercase(schema)))
def update_schemas(self, schemas):
def drop_schema(
self, database: Optional[str], schema: Optional[str],
) -> None:
"""Drop the given schema and remove it from the set of known schemas.
Then remove all its contents (and their dependents, etc) as well.
"""
key = (lowercase(database), lowercase(schema))
if key not in self.schemas:
return
# avoid iterating over self.relations while removing things by
# collecting the list first.
with self.lock:
to_remove = self._list_relations_in_schema(database, schema)
self._remove_all(to_remove)
# handle a drop_schema race by using discard() over remove()
self.schemas.discard(key)
def update_schemas(self, schemas: Iterable[Tuple[Optional[str], str]]):
"""Add multiple schemas to the set of known schemas (case-insensitive)
:param Iterable[str] schemas: An iterable of the schema names to add.
:param schemas: An iterable of the schema names to add.
"""
self.schemas.update(s.lower() for s in schemas)
self.schemas.update((lowercase(d), s.lower()) for (d, s) in schemas)
def __contains__(self, schema):
def __contains__(self, schema_id: Tuple[Optional[str], str]):
"""A schema is 'in' the relations cache if it is in the set of cached
schemas.
:param str schema: The schema name to look up.
:param schema_id: The db name and schema name to look up.
"""
return schema in self.schemas
db, schema = schema_id
return (lowercase(db), schema.lower()) in self.schemas
def dump_graph(self):
"""Dump a key-only representation of the schema to a dictionary. Every
@@ -192,14 +237,14 @@ class RelationsCache(object):
for k, v in self.relations.items()
}
def _setdefault(self, relation):
def _setdefault(self, relation: _CachedRelation):
"""Add a relation to the cache, or return it if it already exists.
:param _CachedRelation relation: The relation to set or get.
:return _CachedRelation: The relation stored under the given relation's
key
"""
self.schemas.add(relation.schema)
self.add_schema(relation.database, relation.schema)
key = relation.key()
return self.relations.setdefault(key, relation)
@@ -214,6 +259,8 @@ class RelationsCache(object):
:raises InternalError: If either entry does not exist.
"""
referenced = self.relations.get(referenced_key)
if referenced is None:
return
if referenced is None:
dbt.exceptions.raise_cache_inconsistent(
'in add_link, referenced link key {} not in cache!'
@@ -227,11 +274,13 @@ class RelationsCache(object):
.format(dependent_key)
)
assert dependent is not None # we just raised!
referenced.add_reference(dependent)
def add_link(self, referenced, dependent):
"""Add a link between two relations to the database. Both the old and
new entries must already exist in the database.
"""Add a link between two relations to the database. If either relation
does not exist, it will be added as an "external" relation.
The dependent model refers _to_ the referenced model. So, given
arguments of (jake_test, bar, jake_test, foo):
@@ -239,50 +288,56 @@ class RelationsCache(object):
to bar, so "drop bar cascade" will drop foo and all of foo's
dependents.
:param DefaultRelation referenced: The referenced model.
:param DefaultRelation dependent: The dependent model.
:param BaseRelation referenced: The referenced model.
:param BaseRelation dependent: The dependent model.
:raises InternalError: If either entry does not exist.
"""
referenced = _ReferenceKey(
schema=referenced.schema,
identifier=referenced.name
)
if referenced.schema not in self:
ref_key = _make_key(referenced)
if (ref_key.database, ref_key.schema) not in self:
# if we have not cached the referenced schema at all, we must be
# referring to a table outside our control. There's no need to make
# a link - we will never drop the referenced relation during a run.
logger.debug(
'{dep!s} references {ref!s} but {ref.schema} is not in the '
'cache, skipping assumed external relation'
.format(dep=dependent, ref=referenced)
'{dep!s} references {ref!s} but {ref.database}.{ref.schema} '
'is not in the cache, skipping assumed external relation'
.format(dep=dependent, ref=ref_key)
)
return
dependent = _ReferenceKey(
schema=dependent.schema,
identifier=dependent.name
)
if ref_key not in self.relations:
# Insert a dummy "external" relation.
referenced = referenced.replace(
type=referenced.External
)
self.add(referenced)
dep_key = _make_key(dependent)
if dep_key not in self.relations:
# Insert a dummy "external" relation.
dependent = dependent.replace(
type=referenced.External
)
self.add(dependent)
logger.debug(
'adding link, {!s} references {!s}'.format(dependent, referenced)
'adding link, {!s} references {!s}'.format(dep_key, ref_key)
)
with self.lock:
self._add_link(referenced, dependent)
self._add_link(ref_key, dep_key)
def add(self, relation):
"""Add the relation inner to the cache, under the schema schema and
identifier identifier
:param DefaultRelation relation: The underlying relation.
:param BaseRelation relation: The underlying relation.
"""
cached = _CachedRelation(relation)
logger.debug('Adding relation: {!s}'.format(cached))
logger.debug('before adding: {}'.format(
pprint.pformat(self.dump_graph()))
)
lazy_log('before adding: {!s}', self.dump_graph)
with self.lock:
self._setdefault(cached)
logger.debug('after adding: {}'.format(
pprint.pformat(self.dump_graph()))
)
lazy_log('after adding: {!s}', self.dump_graph)
def _remove_refs(self, keys):
"""Removes all references to all entries in keys. This does not
@@ -324,42 +379,27 @@ class RelationsCache(object):
:param str schema: The schema of the relation to drop.
:param str identifier: The identifier of the relation to drop.
"""
dropped = _ReferenceKey(schema=relation.schema,
identifier=relation.identifier)
dropped = _make_key(relation)
logger.debug('Dropping relation: {!s}'.format(dropped))
with self.lock:
self._drop_cascade_relation(dropped)
def _rename_relation(self, old_key, new_key):
def _rename_relation(self, old_key, new_relation):
"""Rename a relation named old_key to new_key, updating references.
If the new key is already present, that is an error.
If the old key is absent, we only debug log and return, assuming it's a
temp table being renamed.
Return whether or not there was a key to rename.
:param _ReferenceKey old_key: The existing key, to rename from.
:param _ReferenceKey new_key: The new key, to rename to.
:raises InternalError: If the new key is already present.
:param _CachedRelation new_key: The new relation, to rename to.
"""
if old_key not in self.relations:
logger.debug(
'old key {} not found in self.relations, assuming temporary'
.format(old_key)
)
return
if new_key in self.relations:
dbt.exceptions.raise_cache_inconsistent(
'in rename, new key {} already in cache: {}'
.format(new_key, list(self.relations.keys()))
)
# On the database level, a rename updates all values that were
# previously referenced by old_name to be referenced by new_name.
# basically, the name changes but some underlying ID moves. Kind of
# like an object reference!
relation = self.relations.pop(old_key)
new_key = new_relation.key()
relation.rename(new_key)
# relaton has to rename its innards, so it needs the _CachedRelation.
relation.rename(new_relation)
# update all the relations that refer to it
for cached in self.relations.values():
if cached.is_referenced_by(old_key):
@@ -370,6 +410,37 @@ class RelationsCache(object):
cached.rename_key(old_key, new_key)
self.relations[new_key] = relation
# also fixup the schemas!
self.add_schema(new_key.database, new_key.schema)
return True
def _check_rename_constraints(self, old_key, new_key):
"""Check the rename constraints, and return whether or not the rename
can proceed.
If the new key is already present, that is an error.
If the old key is absent, we debug log and return False, assuming it's
a temp table being renamed.
:param _ReferenceKey old_key: The existing key, to rename from.
:param _ReferenceKey new_key: The new key, to rename to.
:return bool: If the old relation exists for renaming.
:raises InternalError: If the new key is already present.
"""
if new_key in self.relations:
dbt.exceptions.raise_cache_inconsistent(
'in rename, new key {} already in cache: {}'
.format(new_key, list(self.relations.keys()))
)
if old_key not in self.relations:
logger.debug(
'old key {} not found in self.relations, assuming temporary'
.format(old_key)
)
return False
return True
def rename(self, old, new):
"""Rename the old schema/identifier to the new schema/identifier and
@@ -379,42 +450,42 @@ class RelationsCache(object):
If the schema/identifier key is absent, we only debug log and return,
assuming it's a temp table being renamed.
:param DefaultRelation old: The existing relation name information.
:param DefaultRelation new: The new relation name information.
:param BaseRelation old: The existing relation name information.
:param BaseRelation new: The new relation name information.
:raises InternalError: If the new key is already present.
"""
old_key = _ReferenceKey(
schema=old.schema,
identifier=old.identifier
)
new_key = _ReferenceKey(
schema=new.schema,
identifier=new.identifier
)
old_key = _make_key(old)
new_key = _make_key(new)
logger.debug('Renaming relation {!s} to {!s}'.format(
old_key, new_key)
)
logger.debug('before rename: {}'.format(
pprint.pformat(self.dump_graph()))
)
with self.lock:
self._rename_relation(old_key, new_key)
logger.debug('after rename: {}'.format(
pprint.pformat(self.dump_graph()))
)
old_key, new_key
))
def get_relations(self, schema):
lazy_log('before rename: {!s}', self.dump_graph)
with self.lock:
if self._check_rename_constraints(old_key, new_key):
self._rename_relation(old_key, _CachedRelation(new))
else:
self._setdefault(_CachedRelation(new))
lazy_log('after rename: {!s}', self.dump_graph)
def get_relations(
self, database: Optional[str], schema: Optional[str]
) -> List[Any]:
"""Case-insensitively yield all relations matching the given schema.
:param str schema: The case-insensitive schema name to list from.
:return List[DefaultRelation]: The list of relations with the given
:return List[BaseRelation]: The list of relations with the given
schema
"""
schema = schema.lower()
database = lowercase(database)
schema = lowercase(schema)
with self.lock:
results = [
r.inner for r in self.relations.values()
if r.schema.lower() == schema
if (lowercase(r.schema) == schema and
lowercase(r.database) == database)
]
if None in results:
@@ -428,3 +499,25 @@ class RelationsCache(object):
with self.lock:
self.relations.clear()
self.schemas.clear()
def _list_relations_in_schema(
self, database: Optional[str], schema: Optional[str]
) -> List[_CachedRelation]:
"""Get the relations in a schema. Callers should hold the lock."""
key = (lowercase(database), lowercase(schema))
to_remove: List[_CachedRelation] = []
for cachekey, relation in self.relations.items():
if (cachekey.database, cachekey.schema) == key:
to_remove.append(relation)
return to_remove
def _remove_all(self, to_remove: List[_CachedRelation]):
"""Remove all the listed relations. Ignore relations that have been
cascaded out.
"""
for relation in to_remove:
# it may have been cascaded out already
drop_key = _make_key(relation)
if drop_key in self.relations:
self.drop(drop_key)

View File

@@ -0,0 +1,227 @@
import threading
from pathlib import Path
from importlib import import_module
from typing import Type, Dict, Any, List, Optional, Set
from dbt.exceptions import RuntimeException, InternalException
from dbt.include.global_project import (
PACKAGE_PATH as GLOBAL_PROJECT_PATH,
PROJECT_NAME as GLOBAL_PROJECT_NAME,
)
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.contracts.connection import Credentials, AdapterRequiredConfig
from dbt.adapters.protocol import (
AdapterProtocol,
AdapterConfig,
RelationProtocol,
)
from dbt.adapters.base.plugin import AdapterPlugin
Adapter = AdapterProtocol
class AdapterContainer:
def __init__(self):
self.lock = threading.Lock()
self.adapters: Dict[str, Adapter] = {}
self.plugins: Dict[str, AdapterPlugin] = {}
# map package names to their include paths
self.packages: Dict[str, Path] = {
GLOBAL_PROJECT_NAME: Path(GLOBAL_PROJECT_PATH),
}
def get_plugin_by_name(self, name: str) -> AdapterPlugin:
with self.lock:
if name in self.plugins:
return self.plugins[name]
names = ", ".join(self.plugins.keys())
message = f"Invalid adapter type {name}! Must be one of {names}"
raise RuntimeException(message)
def get_adapter_class_by_name(self, name: str) -> Type[Adapter]:
plugin = self.get_plugin_by_name(name)
return plugin.adapter
def get_relation_class_by_name(self, name: str) -> Type[RelationProtocol]:
adapter = self.get_adapter_class_by_name(name)
return adapter.Relation
def get_config_class_by_name(
self, name: str
) -> Type[AdapterConfig]:
adapter = self.get_adapter_class_by_name(name)
return adapter.AdapterSpecificConfigs
def load_plugin(self, name: str) -> Type[Credentials]:
# this doesn't need a lock: in the worst case we'll overwrite packages
# and adapter_type entries with the same value, as they're all
# singletons
try:
# mypy doesn't think modules have any attributes.
mod: Any = import_module('.' + name, 'dbt.adapters')
except ModuleNotFoundError as exc:
# if we failed to import the target module in particular, inform
# the user about it via a runtime error
if exc.name == 'dbt.adapters.' + name:
raise RuntimeException(f'Could not find adapter type {name}!')
logger.info(f'Error importing adapter: {exc}')
# otherwise, the error had to have come from some underlying
# library. Log the stack trace.
logger.debug('', exc_info=True)
raise
plugin: AdapterPlugin = mod.Plugin
plugin_type = plugin.adapter.type()
if plugin_type != name:
raise RuntimeException(
f'Expected to find adapter with type named {name}, got '
f'adapter with type {plugin_type}'
)
with self.lock:
# things do hold the lock to iterate over it so we need it to add
self.plugins[name] = plugin
self.packages[plugin.project_name] = Path(plugin.include_path)
for dep in plugin.dependencies:
self.load_plugin(dep)
return plugin.credentials
def register_adapter(self, config: AdapterRequiredConfig) -> None:
adapter_name = config.credentials.type
adapter_type = self.get_adapter_class_by_name(adapter_name)
with self.lock:
if adapter_name in self.adapters:
# this shouldn't really happen...
return
adapter: Adapter = adapter_type(config) # type: ignore
self.adapters[adapter_name] = adapter
def lookup_adapter(self, adapter_name: str) -> Adapter:
return self.adapters[adapter_name]
def reset_adapters(self):
"""Clear the adapters. This is useful for tests, which change configs.
"""
with self.lock:
for adapter in self.adapters.values():
adapter.cleanup_connections()
self.adapters.clear()
def cleanup_connections(self):
"""Only clean up the adapter connections list without resetting the
actual adapters.
"""
with self.lock:
for adapter in self.adapters.values():
adapter.cleanup_connections()
def get_adapter_plugins(self, name: Optional[str]) -> List[AdapterPlugin]:
"""Iterate over the known adapter plugins. If a name is provided,
iterate in dependency order over the named plugin and its dependencies.
"""
if name is None:
return list(self.plugins.values())
plugins: List[AdapterPlugin] = []
seen: Set[str] = set()
plugin_names: List[str] = [name]
while plugin_names:
plugin_name = plugin_names[0]
plugin_names = plugin_names[1:]
try:
plugin = self.plugins[plugin_name]
except KeyError:
raise InternalException(
f'No plugin found for {plugin_name}'
) from None
plugins.append(plugin)
seen.add(plugin_name)
if plugin.dependencies is None:
continue
for dep in plugin.dependencies:
if dep not in seen:
plugin_names.append(dep)
return plugins
def get_adapter_package_names(self, name: Optional[str]) -> List[str]:
package_names: List[str] = [
p.project_name for p in self.get_adapter_plugins(name)
]
package_names.append(GLOBAL_PROJECT_NAME)
return package_names
def get_include_paths(self, name: Optional[str]) -> List[Path]:
paths = []
for package_name in self.get_adapter_package_names(name):
try:
path = self.packages[package_name]
except KeyError:
raise InternalException(
f'No internal package listing found for {package_name}'
)
paths.append(path)
return paths
def get_adapter_type_names(self, name: Optional[str]) -> List[str]:
return [p.adapter.type() for p in self.get_adapter_plugins(name)]
FACTORY: AdapterContainer = AdapterContainer()
def register_adapter(config: AdapterRequiredConfig) -> None:
FACTORY.register_adapter(config)
def get_adapter(config: AdapterRequiredConfig):
return FACTORY.lookup_adapter(config.credentials.type)
def reset_adapters():
"""Clear the adapters. This is useful for tests, which change configs.
"""
FACTORY.reset_adapters()
def cleanup_connections():
"""Only clean up the adapter connections list without resetting the actual
adapters.
"""
FACTORY.cleanup_connections()
def get_adapter_class_by_name(name: str) -> Type[AdapterProtocol]:
return FACTORY.get_adapter_class_by_name(name)
def get_config_class_by_name(name: str) -> Type[AdapterConfig]:
return FACTORY.get_config_class_by_name(name)
def get_relation_class_by_name(name: str) -> Type[RelationProtocol]:
return FACTORY.get_relation_class_by_name(name)
def load_plugin(name: str) -> Type[Credentials]:
return FACTORY.load_plugin(name)
def get_include_paths(name: Optional[str]) -> List[Path]:
return FACTORY.get_include_paths(name)
def get_adapter_package_names(name: Optional[str]) -> List[str]:
return FACTORY.get_adapter_package_names(name)
def get_adapter_type_names(name: Optional[str]) -> List[str]:
return FACTORY.get_adapter_type_names(name)

View File

@@ -0,0 +1,161 @@
from dataclasses import dataclass
from typing import (
Type, Hashable, Optional, ContextManager, List, Generic, TypeVar, ClassVar,
Tuple, Union, Dict, Any
)
from typing_extensions import Protocol
import agate
from dbt.contracts.connection import Connection, AdapterRequiredConfig
from dbt.contracts.graph.compiled import (
CompiledNode, ManifestNode, NonSourceCompiledNode
)
from dbt.contracts.graph.parsed import ParsedNode, ParsedSourceDefinition
from dbt.contracts.graph.model_config import BaseConfig
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.relation import Policy, HasQuoting
from dbt.graph import Graph
@dataclass
class AdapterConfig(BaseConfig):
pass
class ConnectionManagerProtocol(Protocol):
TYPE: str
class ColumnProtocol(Protocol):
pass
Self = TypeVar('Self', bound='RelationProtocol')
class RelationProtocol(Protocol):
@classmethod
def get_default_quote_policy(cls) -> Policy:
...
@classmethod
def create_from(
cls: Type[Self],
config: HasQuoting,
node: Union[CompiledNode, ParsedNode, ParsedSourceDefinition],
) -> Self:
...
class CompilerProtocol(Protocol):
def compile(self, manifest: Manifest, write=True) -> Graph:
...
def compile_node(
self,
node: ManifestNode,
manifest: Manifest,
extra_context: Optional[Dict[str, Any]] = None,
) -> NonSourceCompiledNode:
...
AdapterConfig_T = TypeVar(
'AdapterConfig_T', bound=AdapterConfig
)
ConnectionManager_T = TypeVar(
'ConnectionManager_T', bound=ConnectionManagerProtocol
)
Relation_T = TypeVar(
'Relation_T', bound=RelationProtocol
)
Column_T = TypeVar(
'Column_T', bound=ColumnProtocol
)
Compiler_T = TypeVar('Compiler_T', bound=CompilerProtocol)
class AdapterProtocol(
Protocol,
Generic[
AdapterConfig_T,
ConnectionManager_T,
Relation_T,
Column_T,
Compiler_T,
]
):
AdapterSpecificConfigs: ClassVar[Type[AdapterConfig_T]]
Column: ClassVar[Type[Column_T]]
Relation: ClassVar[Type[Relation_T]]
ConnectionManager: ClassVar[Type[ConnectionManager_T]]
connections: ConnectionManager_T
def __init__(self, config: AdapterRequiredConfig):
...
@classmethod
def type(cls) -> str:
pass
def set_query_header(self, manifest: Manifest) -> None:
...
@staticmethod
def get_thread_identifier() -> Hashable:
...
def get_thread_connection(self) -> Connection:
...
def set_thread_connection(self, conn: Connection) -> None:
...
def get_if_exists(self) -> Optional[Connection]:
...
def clear_thread_connection(self) -> None:
...
def clear_transaction(self) -> None:
...
def exception_handler(self, sql: str) -> ContextManager:
...
def set_connection_name(self, name: Optional[str] = None) -> Connection:
...
def cancel_open(self) -> Optional[List[str]]:
...
def open(cls, connection: Connection) -> Connection:
...
def release(self) -> None:
...
def cleanup_all(self) -> None:
...
def begin(self) -> None:
...
def commit(self) -> None:
...
def close(cls, connection: Connection) -> Connection:
...
def commit_if_has_connection(self) -> None:
...
def execute(
self, sql: str, auto_begin: bool = False, fetch: bool = False
) -> Tuple[str, agate.Table]:
...
def get_compiler(self) -> Compiler_T:
...

View File

@@ -0,0 +1,3 @@
# these are all just exports, #noqa them so flake8 will be happy
from dbt.adapters.sql.connections import SQLConnectionManager # noqa
from dbt.adapters.sql.impl import SQLAdapter # noqa

View File

@@ -0,0 +1,174 @@
import abc
import time
from typing import List, Optional, Tuple, Any, Iterable, Dict
import agate
import dbt.clients.agate_helper
import dbt.exceptions
from dbt.adapters.base import BaseConnectionManager
from dbt.contracts.connection import Connection, ConnectionState
from dbt.logger import GLOBAL_LOGGER as logger
from dbt import flags
class SQLConnectionManager(BaseConnectionManager):
"""The default connection manager with some common SQL methods implemented.
Methods to implement:
- exception_handler
- cancel
- get_status
- open
"""
@abc.abstractmethod
def cancel(self, connection: Connection):
"""Cancel the given connection."""
raise dbt.exceptions.NotImplementedException(
'`cancel` is not implemented for this adapter!'
)
def cancel_open(self) -> List[str]:
names = []
this_connection = self.get_if_exists()
with self.lock:
for connection in self.thread_connections.values():
if connection is this_connection:
continue
# if the connection failed, the handle will be None so we have
# nothing to cancel.
if (
connection.handle is not None and
connection.state == ConnectionState.OPEN
):
self.cancel(connection)
if connection.name is not None:
names.append(connection.name)
return names
def add_query(
self,
sql: str,
auto_begin: bool = True,
bindings: Optional[Any] = None,
abridge_sql_log: bool = False
) -> Tuple[Connection, Any]:
connection = self.get_thread_connection()
if auto_begin and connection.transaction_open is False:
self.begin()
logger.debug('Using {} connection "{}".'
.format(self.TYPE, connection.name))
with self.exception_handler(sql):
if abridge_sql_log:
log_sql = '{}...'.format(sql[:512])
else:
log_sql = sql
logger.debug(
'On {connection_name}: {sql}',
connection_name=connection.name,
sql=log_sql,
)
pre = time.time()
cursor = connection.handle.cursor()
cursor.execute(sql, bindings)
logger.debug(
"SQL status: {status} in {elapsed:0.2f} seconds",
status=self.get_status(cursor),
elapsed=(time.time() - pre)
)
return connection, cursor
@abc.abstractclassmethod
def get_status(cls, cursor: Any) -> str:
"""Get the status of the cursor."""
raise dbt.exceptions.NotImplementedException(
'`get_status` is not implemented for this adapter!'
)
@classmethod
def process_results(
cls,
column_names: Iterable[str],
rows: Iterable[Any]
) -> List[Dict[str, Any]]:
return [dict(zip(column_names, row)) for row in rows]
@classmethod
def get_result_from_cursor(cls, cursor: Any) -> agate.Table:
data: List[Any] = []
column_names: List[str] = []
if cursor.description is not None:
column_names = [col[0] for col in cursor.description]
rows = cursor.fetchall()
data = cls.process_results(column_names, rows)
return dbt.clients.agate_helper.table_from_data_flat(
data,
column_names
)
def execute(
self, sql: str, auto_begin: bool = False, fetch: bool = False
) -> Tuple[str, agate.Table]:
sql = self._add_query_comment(sql)
_, cursor = self.add_query(sql, auto_begin)
status = self.get_status(cursor)
if fetch:
table = self.get_result_from_cursor(cursor)
else:
table = dbt.clients.agate_helper.empty_table()
return status, table
def add_begin_query(self):
return self.add_query('BEGIN', auto_begin=False)
def add_commit_query(self):
return self.add_query('COMMIT', auto_begin=False)
def begin(self):
connection = self.get_thread_connection()
if flags.STRICT_MODE:
if not isinstance(connection, Connection):
raise dbt.exceptions.CompilerException(
f'In begin, got {connection} - not a Connection!'
)
if connection.transaction_open is True:
raise dbt.exceptions.InternalException(
'Tried to begin a new transaction on connection "{}", but '
'it already had one open!'.format(connection.name))
self.add_begin_query()
connection.transaction_open = True
return connection
def commit(self):
connection = self.get_thread_connection()
if flags.STRICT_MODE:
if not isinstance(connection, Connection):
raise dbt.exceptions.CompilerException(
f'In commit, got {connection} - not a Connection!'
)
if connection.transaction_open is False:
raise dbt.exceptions.InternalException(
'Tried to commit transaction on connection "{}", but '
'it does not have one open!'.format(connection.name))
logger.debug('On {}: COMMIT'.format(connection.name))
self.add_commit_query()
connection.transaction_open = False
return connection

View File

@@ -0,0 +1,250 @@
import agate
from typing import Any, Optional, Tuple, Type, List
import dbt.clients.agate_helper
from dbt.contracts.connection import Connection
import dbt.exceptions
from dbt.adapters.base import BaseAdapter, available
from dbt.adapters.sql import SQLConnectionManager
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.adapters.base.relation import BaseRelation
LIST_RELATIONS_MACRO_NAME = 'list_relations_without_caching'
GET_COLUMNS_IN_RELATION_MACRO_NAME = 'get_columns_in_relation'
LIST_SCHEMAS_MACRO_NAME = 'list_schemas'
CHECK_SCHEMA_EXISTS_MACRO_NAME = 'check_schema_exists'
CREATE_SCHEMA_MACRO_NAME = 'create_schema'
DROP_SCHEMA_MACRO_NAME = 'drop_schema'
RENAME_RELATION_MACRO_NAME = 'rename_relation'
TRUNCATE_RELATION_MACRO_NAME = 'truncate_relation'
DROP_RELATION_MACRO_NAME = 'drop_relation'
ALTER_COLUMN_TYPE_MACRO_NAME = 'alter_column_type'
class SQLAdapter(BaseAdapter):
"""The default adapter with the common agate conversions and some SQL
methods implemented. This adapter has a different much shorter list of
methods to implement, but some more macros that must be implemented.
To implement a macro, implement "${adapter_type}__${macro_name}". in the
adapter's internal project.
Methods to implement:
- date_function
Macros to implement:
- get_catalog
- list_relations_without_caching
- get_columns_in_relation
"""
ConnectionManager: Type[SQLConnectionManager]
connections: SQLConnectionManager
@available.parse(lambda *a, **k: (None, None))
def add_query(
self,
sql: str,
auto_begin: bool = True,
bindings: Optional[Any] = None,
abridge_sql_log: bool = False,
) -> Tuple[Connection, Any]:
"""Add a query to the current transaction. A thin wrapper around
ConnectionManager.add_query.
:param sql: The SQL query to add
:param auto_begin: If set and there is no transaction in progress,
begin a new one.
:param bindings: An optional list of bindings for the query.
:param abridge_sql_log: If set, limit the raw sql logged to 512
characters
"""
return self.connections.add_query(sql, auto_begin, bindings,
abridge_sql_log)
@classmethod
def convert_text_type(cls, agate_table: agate.Table, col_idx: int) -> str:
return "text"
@classmethod
def convert_number_type(
cls, agate_table: agate.Table, col_idx: int
) -> str:
decimals = agate_table.aggregate(agate.MaxPrecision(col_idx))
return "float8" if decimals else "integer"
@classmethod
def convert_boolean_type(
cls, agate_table: agate.Table, col_idx: int
) -> str:
return "boolean"
@classmethod
def convert_datetime_type(
cls, agate_table: agate.Table, col_idx: int
) -> str:
return "timestamp without time zone"
@classmethod
def convert_date_type(cls, agate_table: agate.Table, col_idx: int) -> str:
return "date"
@classmethod
def convert_time_type(cls, agate_table: agate.Table, col_idx: int) -> str:
return "time"
@classmethod
def is_cancelable(cls) -> bool:
return True
def expand_column_types(self, goal, current):
reference_columns = {
c.name: c for c in
self.get_columns_in_relation(goal)
}
target_columns = {
c.name: c for c
in self.get_columns_in_relation(current)
}
for column_name, reference_column in reference_columns.items():
target_column = target_columns.get(column_name)
if target_column is not None and \
target_column.can_expand_to(reference_column):
col_string_size = reference_column.string_size()
new_type = self.Column.string_type(col_string_size)
logger.debug("Changing col type from {} to {} in table {}",
target_column.data_type, new_type, current)
self.alter_column_type(current, column_name, new_type)
def alter_column_type(
self, relation, column_name, new_column_type
) -> None:
"""
1. Create a new column (w/ temp name and correct type)
2. Copy data over to it
3. Drop the existing column (cascade!)
4. Rename the new column to existing column
"""
kwargs = {
'relation': relation,
'column_name': column_name,
'new_column_type': new_column_type,
}
self.execute_macro(
ALTER_COLUMN_TYPE_MACRO_NAME,
kwargs=kwargs
)
def drop_relation(self, relation):
if relation.type is None:
dbt.exceptions.raise_compiler_error(
'Tried to drop relation {}, but its type is null.'
.format(relation))
self.cache_dropped(relation)
self.execute_macro(
DROP_RELATION_MACRO_NAME,
kwargs={'relation': relation}
)
def truncate_relation(self, relation):
self.execute_macro(
TRUNCATE_RELATION_MACRO_NAME,
kwargs={'relation': relation}
)
def rename_relation(self, from_relation, to_relation):
self.cache_renamed(from_relation, to_relation)
kwargs = {'from_relation': from_relation, 'to_relation': to_relation}
self.execute_macro(
RENAME_RELATION_MACRO_NAME,
kwargs=kwargs
)
def get_columns_in_relation(self, relation):
return self.execute_macro(
GET_COLUMNS_IN_RELATION_MACRO_NAME,
kwargs={'relation': relation}
)
def create_schema(self, relation: BaseRelation) -> None:
relation = relation.without_identifier()
logger.debug('Creating schema "{}"', relation)
kwargs = {
'relation': relation,
}
self.execute_macro(CREATE_SCHEMA_MACRO_NAME, kwargs=kwargs)
self.commit_if_has_connection()
# we can't update the cache here, as if the schema already existed we
# don't want to (incorrectly) say that it's empty
def drop_schema(self, relation: BaseRelation) -> None:
relation = relation.without_identifier()
logger.debug('Dropping schema "{}".', relation)
kwargs = {
'relation': relation,
}
self.execute_macro(DROP_SCHEMA_MACRO_NAME, kwargs=kwargs)
# we can update the cache here
self.cache.drop_schema(relation.database, relation.schema)
def list_relations_without_caching(
self, schema_relation: BaseRelation,
) -> List[BaseRelation]:
kwargs = {'schema_relation': schema_relation}
results = self.execute_macro(
LIST_RELATIONS_MACRO_NAME,
kwargs=kwargs
)
relations = []
quote_policy = {
'database': True,
'schema': True,
'identifier': True
}
for _database, name, _schema, _type in results:
try:
_type = self.Relation.get_relation_type(_type)
except ValueError:
_type = self.Relation.External
relations.append(self.Relation.create(
database=_database,
schema=_schema,
identifier=name,
quote_policy=quote_policy,
type=_type
))
return relations
def quote(self, identifier):
return '"{}"'.format(identifier)
def list_schemas(self, database: str) -> List[str]:
results = self.execute_macro(
LIST_SCHEMAS_MACRO_NAME,
kwargs={'database': database}
)
return [row[0] for row in results]
def check_schema_exists(self, database: str, schema: str) -> bool:
information_schema = self.Relation.create(
database=database,
schema=schema,
identifier='INFORMATION_SCHEMA',
quote_policy=self.config.quoting
).information_schema()
kwargs = {'information_schema': information_schema, 'schema': schema}
results = self.execute_macro(
CHECK_SCHEMA_EXISTS_MACRO_NAME,
kwargs=kwargs
)
return results[0][0] > 0

View File

@@ -0,0 +1,393 @@
import re
from collections import namedtuple
import dbt.exceptions
def regex(pat):
return re.compile(pat, re.DOTALL | re.MULTILINE)
class BlockData:
"""raw plaintext data from the top level of the file."""
def __init__(self, contents):
self.block_type_name = '__dbt__data'
self.contents = contents
self.full_block = contents
class BlockTag:
def __init__(self, block_type_name, block_name, contents=None,
full_block=None, **kw):
self.block_type_name = block_type_name
self.block_name = block_name
self.contents = contents
self.full_block = full_block
def __str__(self):
return 'BlockTag({!r}, {!r})'.format(self.block_type_name,
self.block_name)
def __repr__(self):
return str(self)
@property
def end_block_type_name(self):
return 'end{}'.format(self.block_type_name)
def end_pat(self):
# we don't want to use string formatting here because jinja uses most
# of the string formatting operators in its syntax...
pattern = ''.join((
r'(?P<endblock>((?:\s*\{\%\-|\{\%)\s*',
self.end_block_type_name,
r'\s*(?:\-\%\}\s*|\%\})))',
))
return regex(pattern)
Tag = namedtuple('Tag', 'block_type_name block_name start end')
_NAME_PATTERN = r'[A-Za-z_][A-Za-z_0-9]*'
COMMENT_START_PATTERN = regex(r'(?:(?P<comment_start>(\s*\{\#)))')
COMMENT_END_PATTERN = regex(r'(.*?)(\s*\#\})')
RAW_START_PATTERN = regex(
r'(?:\s*\{\%\-|\{\%)\s*(?P<raw_start>(raw))\s*(?:\-\%\}\s*|\%\})'
)
EXPR_START_PATTERN = regex(r'(?P<expr_start>(\{\{\s*))')
EXPR_END_PATTERN = regex(r'(?P<expr_end>(\s*\}\}))')
BLOCK_START_PATTERN = regex(''.join((
r'(?:\s*\{\%\-|\{\%)\s*',
r'(?P<block_type_name>({}))'.format(_NAME_PATTERN),
# some blocks have a 'block name'.
r'(?:\s+(?P<block_name>({})))?'.format(_NAME_PATTERN),
)))
RAW_BLOCK_PATTERN = regex(''.join((
r'(?:\s*\{\%\-|\{\%)\s*raw\s*(?:\-\%\}\s*|\%\})',
r'(?:.*?)',
r'(?:\s*\{\%\-|\{\%)\s*endraw\s*(?:\-\%\}\s*|\%\})',
)))
TAG_CLOSE_PATTERN = regex(r'(?:(?P<tag_close>(\-\%\}\s*|\%\})))')
# stolen from jinja's lexer. Note that we've consumed all prefix whitespace by
# the time we want to use this.
STRING_PATTERN = regex(
r"(?P<string>('([^'\\]*(?:\\.[^'\\]*)*)'|"
r'"([^"\\]*(?:\\.[^"\\]*)*)"))'
)
QUOTE_START_PATTERN = regex(r'''(?P<quote>(['"]))''')
class TagIterator:
def __init__(self, data):
self.data = data
self.blocks = []
self._parenthesis_stack = []
self.pos = 0
def linepos(self, end=None) -> str:
"""Given an absolute position in the input data, return a pair of
line number + relative position to the start of the line.
"""
end_val: int = self.pos if end is None else end
data = self.data[:end_val]
# if not found, rfind returns -1, and -1+1=0, which is perfect!
last_line_start = data.rfind('\n') + 1
# it's easy to forget this, but line numbers are 1-indexed
line_number = data.count('\n') + 1
return f'{line_number}:{end_val - last_line_start}'
def advance(self, new_position):
self.pos = new_position
def rewind(self, amount=1):
self.pos -= amount
def _search(self, pattern):
return pattern.search(self.data, self.pos)
def _match(self, pattern):
return pattern.match(self.data, self.pos)
def _first_match(self, *patterns, **kwargs):
matches = []
for pattern in patterns:
# default to 'search', but sometimes we want to 'match'.
if kwargs.get('method', 'search') == 'search':
match = self._search(pattern)
else:
match = self._match(pattern)
if match:
matches.append(match)
if not matches:
return None
# if there are multiple matches, pick the least greedy match
# TODO: do I need to account for m.start(), or is this ok?
return min(matches, key=lambda m: m.end())
def _expect_match(self, expected_name, *patterns, **kwargs):
match = self._first_match(*patterns, **kwargs)
if match is None:
msg = 'unexpected EOF, expected {}, got "{}"'.format(
expected_name, self.data[self.pos:]
)
dbt.exceptions.raise_compiler_error(msg)
return match
def handle_expr(self, match):
"""Handle an expression. At this point we're at a string like:
{{ 1 + 2 }}
^ right here
And the match contains "{{ "
We expect to find a `}}`, but we might find one in a string before
that. Imagine the case of `{{ 2 * "}}" }}`...
You're not allowed to have blocks or comments inside an expr so it is
pretty straightforward, I hope: only strings can get in the way.
"""
self.advance(match.end())
while True:
match = self._expect_match('}}',
EXPR_END_PATTERN,
QUOTE_START_PATTERN)
if match.groupdict().get('expr_end') is not None:
break
else:
# it's a quote. we haven't advanced for this match yet, so
# just slurp up the whole string, no need to rewind.
match = self._expect_match('string', STRING_PATTERN)
self.advance(match.end())
self.advance(match.end())
def handle_comment(self, match):
self.advance(match.end())
match = self._expect_match('#}', COMMENT_END_PATTERN)
self.advance(match.end())
def _expect_block_close(self):
"""Search for the tag close marker.
To the right of the type name, there are a few possiblities:
- a name (handled by the regex's 'block_name')
- any number of: `=`, `(`, `)`, strings, etc (arguments)
- nothing
followed eventually by a %}
So the only characters we actually have to worry about in this context
are quote and `%}` - nothing else can hide the %} and be valid jinja.
"""
while True:
end_match = self._expect_match(
'tag close ("%}")',
QUOTE_START_PATTERN,
TAG_CLOSE_PATTERN
)
self.advance(end_match.end())
if end_match.groupdict().get('tag_close') is not None:
return
# must be a string. Rewind to its start and advance past it.
self.rewind()
string_match = self._expect_match('string', STRING_PATTERN)
self.advance(string_match.end())
def handle_raw(self):
# raw blocks are super special, they are a single complete regex
match = self._expect_match('{% raw %}...{% endraw %}',
RAW_BLOCK_PATTERN)
self.advance(match.end())
return match.end()
def handle_tag(self, match):
"""The tag could be one of a few things:
{% mytag %}
{% mytag x = y %}
{% mytag x = "y" %}
{% mytag x.y() %}
{% mytag foo("a", "b", c="d") %}
But the key here is that it's always going to be `{% mytag`!
"""
groups = match.groupdict()
# always a value
block_type_name = groups['block_type_name']
# might be None
block_name = groups.get('block_name')
start_pos = self.pos
if block_type_name == 'raw':
match = self._expect_match('{% raw %}...{% endraw %}',
RAW_BLOCK_PATTERN)
self.advance(match.end())
else:
self.advance(match.end())
self._expect_block_close()
return Tag(
block_type_name=block_type_name,
block_name=block_name,
start=start_pos,
end=self.pos
)
def find_tags(self):
while True:
match = self._first_match(
BLOCK_START_PATTERN,
COMMENT_START_PATTERN,
EXPR_START_PATTERN
)
if match is None:
break
self.advance(match.start())
# start = self.pos
groups = match.groupdict()
comment_start = groups.get('comment_start')
expr_start = groups.get('expr_start')
block_type_name = groups.get('block_type_name')
if comment_start is not None:
self.handle_comment(match)
elif expr_start is not None:
self.handle_expr(match)
elif block_type_name is not None:
yield self.handle_tag(match)
else:
raise dbt.exceptions.InternalException(
'Invalid regex match in next_block, expected block start, '
'expr start, or comment start'
)
def __iter__(self):
return self.find_tags()
duplicate_tags = (
'Got nested tags: {outer.block_type_name} (started at {outer.start}) did '
'not have a matching {{% end{outer.block_type_name} %}} before a '
'subsequent {inner.block_type_name} was found (started at {inner.start})'
)
_CONTROL_FLOW_TAGS = {
'if': 'endif',
'for': 'endfor',
}
_CONTROL_FLOW_END_TAGS = {
v: k
for k, v in _CONTROL_FLOW_TAGS.items()
}
class BlockIterator:
def __init__(self, data):
self.tag_parser = TagIterator(data)
self.current = None
self.stack = []
self.last_position = 0
@property
def current_end(self):
if self.current is None:
return 0
else:
return self.current.end
@property
def data(self):
return self.tag_parser.data
def is_current_end(self, tag):
return (
tag.block_type_name.startswith('end') and
self.current is not None and
tag.block_type_name[3:] == self.current.block_type_name
)
def find_blocks(self, allowed_blocks=None, collect_raw_data=True):
"""Find all top-level blocks in the data."""
if allowed_blocks is None:
allowed_blocks = {'snapshot', 'macro', 'materialization', 'docs'}
for tag in self.tag_parser.find_tags():
if tag.block_type_name in _CONTROL_FLOW_TAGS:
self.stack.append(tag.block_type_name)
elif tag.block_type_name in _CONTROL_FLOW_END_TAGS:
found = None
if self.stack:
found = self.stack.pop()
else:
expected = _CONTROL_FLOW_END_TAGS[tag.block_type_name]
dbt.exceptions.raise_compiler_error((
'Got an unexpected control flow end tag, got {} but '
'never saw a preceeding {} (@ {})'
).format(
tag.block_type_name,
expected,
self.tag_parser.linepos(tag.start)
))
expected = _CONTROL_FLOW_TAGS[found]
if expected != tag.block_type_name:
dbt.exceptions.raise_compiler_error((
'Got an unexpected control flow end tag, got {} but '
'expected {} next (@ {})'
).format(
tag.block_type_name,
expected,
self.tag_parser.linepos(tag.start)
))
if tag.block_type_name in allowed_blocks:
if self.stack:
dbt.exceptions.raise_compiler_error((
'Got a block definition inside control flow at {}. '
'All dbt block definitions must be at the top level'
).format(self.tag_parser.linepos(tag.start)))
if self.current is not None:
dbt.exceptions.raise_compiler_error(
duplicate_tags.format(outer=self.current, inner=tag)
)
if collect_raw_data:
raw_data = self.data[self.last_position:tag.start]
self.last_position = tag.start
if raw_data:
yield BlockData(raw_data)
self.current = tag
elif self.is_current_end(tag):
self.last_position = tag.end
assert self.current is not None
yield BlockTag(
block_type_name=self.current.block_type_name,
block_name=self.current.block_name,
contents=self.data[self.current.end:tag.start],
full_block=self.data[self.current.start:tag.end]
)
self.current = None
if self.current:
linecount = self.data[:self.current.end].count('\n') + 1
dbt.exceptions.raise_compiler_error((
'Reached EOF without finding a close tag for '
'{} (searched from line {})'
).format(self.current.block_type_name, linecount))
if collect_raw_data:
raw_data = self.data[self.last_position:]
if raw_data:
yield BlockData(raw_data)
def lex_for_blocks(self, allowed_blocks=None, collect_raw_data=True):
return list(self.find_blocks(allowed_blocks=allowed_blocks,
collect_raw_data=collect_raw_data))

View File

@@ -0,0 +1,203 @@
from codecs import BOM_UTF8
import agate
import datetime
import isodate
import json
import dbt.utils
from typing import Iterable, List, Dict, Union, Optional, Any
from dbt.exceptions import RuntimeException
BOM = BOM_UTF8.decode('utf-8') # '\ufeff'
class ISODateTime(agate.data_types.DateTime):
def cast(self, d):
# this is agate.data_types.DateTime.cast with the "clever" bits removed
# so we only handle ISO8601 stuff
if isinstance(d, datetime.datetime) or d is None:
return d
elif isinstance(d, datetime.date):
return datetime.datetime.combine(d, datetime.time(0, 0, 0))
elif isinstance(d, str):
d = d.strip()
if d.lower() in self.null_values:
return None
try:
return isodate.parse_datetime(d)
except: # noqa
pass
raise agate.exceptions.CastError(
'Can not parse value "%s" as datetime.' % d
)
def build_type_tester(text_columns: Iterable[str]) -> agate.TypeTester:
types = [
agate.data_types.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'),
ISODateTime(null_values=('null', '')),
agate.data_types.Boolean(true_values=('true',),
false_values=('false',),
null_values=('null', '')),
agate.data_types.Text(null_values=('null', ''))
]
force = {
k: agate.data_types.Text(null_values=('null', ''))
for k in text_columns
}
return agate.TypeTester(force=force, types=types)
DEFAULT_TYPE_TESTER = build_type_tester(())
def table_from_rows(
rows: List[Any],
column_names: Iterable[str],
text_only_columns: Optional[Iterable[str]] = None,
) -> agate.Table:
if text_only_columns is None:
column_types = DEFAULT_TYPE_TESTER
else:
column_types = build_type_tester(text_only_columns)
return agate.Table(rows, column_names, column_types=column_types)
def table_from_data(data, column_names: Iterable[str]) -> agate.Table:
"Convert list of dictionaries into an Agate table"
# The agate table is generated from a list of dicts, so the column order
# from `data` is not preserved. We can use `select` to reorder the columns
#
# If there is no data, create an empty table with the specified columns
if len(data) == 0:
return agate.Table([], column_names=column_names)
else:
table = agate.Table.from_object(data, column_types=DEFAULT_TYPE_TESTER)
return table.select(column_names)
def table_from_data_flat(data, column_names: Iterable[str]) -> agate.Table:
"Convert list of dictionaries into an Agate table"
rows = []
for _row in data:
row = []
for value in list(_row.values()):
if isinstance(value, (dict, list, tuple)):
row.append(json.dumps(value, cls=dbt.utils.JSONEncoder))
else:
row.append(value)
rows.append(row)
return table_from_rows(rows=rows, column_names=column_names)
def empty_table():
"Returns an empty Agate table. To be used in place of None"
return agate.Table(rows=[])
def as_matrix(table):
"Return an agate table as a matrix of data sans columns"
return [r.values() for r in table.rows.values()]
def from_csv(abspath, text_columns):
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)
class _NullMarker:
pass
NullableAgateType = Union[agate.data_types.DataType, _NullMarker]
class ColumnTypeBuilder(Dict[str, NullableAgateType]):
def __init__(self):
super().__init__()
def __setitem__(self, key, value):
if key not in self:
super().__setitem__(key, value)
return
existing_type = self[key]
if isinstance(existing_type, _NullMarker):
# overwrite
super().__setitem__(key, value)
elif isinstance(value, _NullMarker):
# use the existing value
return
elif not isinstance(value, type(existing_type)):
# actual type mismatch!
raise RuntimeException(
f'Tables contain columns with the same names ({key}), '
f'but different types ({value} vs {existing_type})'
)
def finalize(self) -> Dict[str, agate.data_types.DataType]:
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()
else:
result[key] = value
return result
def _merged_column_types(
tables: List[agate.Table]
) -> Dict[str, agate.data_types.DataType]:
# this is a lot like agate.Table.merge, but with handling for all-null
# rows being "any type".
new_columns: ColumnTypeBuilder = ColumnTypeBuilder()
for table in tables:
for i in range(len(table.columns)):
column_name: str = table.column_names[i]
column_type: NullableAgateType = table.column_types[i]
# avoid over-sensitive type inference
if all(x is None for x in table.columns[column_name]):
column_type = _NullMarker()
new_columns[column_name] = column_type
return new_columns.finalize()
def merge_tables(tables: List[agate.Table]) -> agate.Table:
"""This is similar to agate.Table.merge, but it handles rows of all 'null'
values more gracefully during merges.
"""
new_columns = _merged_column_types(tables)
column_names = tuple(new_columns.keys())
column_types = tuple(new_columns.values())
rows: List[agate.Row] = []
for table in tables:
if (
table.column_names == column_names and
table.column_types == column_types
):
rows.extend(table.rows)
else:
for row in table.rows:
data = [row.get(name, None) for name in column_names]
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)

110
core/dbt/clients/git.py Normal file
View File

@@ -0,0 +1,110 @@
import re
import os.path
from dbt.clients.system import run_cmd, rmdir
from dbt.logger import GLOBAL_LOGGER as logger
import dbt.exceptions
def clone(repo, cwd, dirname=None, remove_git_dir=False, branch=None):
clone_cmd = ['git', 'clone', '--depth', '1']
if branch is not None:
clone_cmd.extend(['--branch', branch])
clone_cmd.append(repo)
if dirname is not None:
clone_cmd.append(dirname)
result = run_cmd(cwd, clone_cmd, env={'LC_ALL': 'C'})
if remove_git_dir:
rmdir(os.path.join(dirname, '.git'))
return result
def list_tags(cwd):
out, err = run_cmd(cwd, ['git', 'tag', '--list'], env={'LC_ALL': 'C'})
tags = out.decode('utf-8').strip().split("\n")
return tags
def _checkout(cwd, repo, branch):
logger.debug(' Checking out branch {}.'.format(branch))
run_cmd(cwd, ['git', 'remote', 'set-branches', 'origin', branch])
run_cmd(cwd, ['git', 'fetch', '--tags', '--depth', '1', 'origin', branch])
tags = list_tags(cwd)
# Prefer tags to branches if one exists
if branch in tags:
spec = 'tags/{}'.format(branch)
else:
spec = 'origin/{}'.format(branch)
out, err = run_cmd(cwd, ['git', 'reset', '--hard', spec],
env={'LC_ALL': 'C'})
return out, err
def checkout(cwd, repo, branch=None):
if branch is None:
branch = 'master'
try:
return _checkout(cwd, repo, branch)
except dbt.exceptions.CommandResultError as exc:
stderr = exc.stderr.decode('utf-8').strip()
dbt.exceptions.bad_package_spec(repo, branch, stderr)
def get_current_sha(cwd):
out, err = run_cmd(cwd, ['git', 'rev-parse', 'HEAD'], env={'LC_ALL': 'C'})
return out.decode('utf-8')
def remove_remote(cwd):
return run_cmd(cwd, ['git', 'remote', 'rm', 'origin'], env={'LC_ALL': 'C'})
def clone_and_checkout(repo, cwd, dirname=None, remove_git_dir=False,
branch=None):
exists = None
try:
_, err = clone(repo, cwd, dirname=dirname,
remove_git_dir=remove_git_dir)
except dbt.exceptions.CommandResultError as exc:
err = exc.stderr.decode('utf-8')
exists = re.match("fatal: destination path '(.+)' already exists", err)
if not exists: # something else is wrong, raise it
raise
directory = None
start_sha = None
if exists:
directory = exists.group(1)
logger.debug('Updating existing dependency {}.', directory)
else:
matches = re.match("Cloning into '(.+)'", err.decode('utf-8'))
if matches is None:
raise dbt.exceptions.RuntimeException(
f'Error cloning {repo} - never saw "Cloning into ..." from git'
)
directory = matches.group(1)
logger.debug('Pulling new dependency {}.', directory)
full_path = os.path.join(cwd, directory)
start_sha = get_current_sha(full_path)
checkout(full_path, repo, branch)
end_sha = get_current_sha(full_path)
if exists:
if start_sha == end_sha:
logger.debug(' Already at {}, nothing to do.', start_sha[:7])
else:
logger.debug(' Updated checkout from {} to {}.',
start_sha[:7], end_sha[:7])
else:
logger.debug(' Checked out at {}.', end_sha[:7])
return directory

644
core/dbt/clients/jinja.py Normal file
View File

@@ -0,0 +1,644 @@
import codecs
import linecache
import os
import re
import tempfile
import threading
from ast import literal_eval
from contextlib import contextmanager
from itertools import chain, islice
from typing import (
List, Union, Set, Optional, Dict, Any, Iterator, Type, NoReturn, Tuple,
Callable
)
import jinja2
import jinja2.ext
import jinja2.nativetypes # type: ignore
import jinja2.nodes
import jinja2.parser
import jinja2.sandbox
from dbt.utils import (
get_dbt_macro_name, get_docs_macro_name, get_materialization_macro_name,
deep_map
)
from dbt.clients._jinja_blocks import BlockIterator, BlockData, BlockTag
from dbt.contracts.graph.compiled import CompiledSchemaTestNode
from dbt.contracts.graph.parsed import ParsedSchemaTestNode
from dbt.exceptions import (
InternalException, raise_compiler_error, CompilationException,
invalid_materialization_argument, MacroReturn, JinjaRenderingException
)
from dbt import flags
from dbt.logger import GLOBAL_LOGGER as logger # noqa
def _linecache_inject(source, write):
if write:
# this is the only reliable way to accomplish this. Obviously, it's
# really darn noisy and will fill your temporary directory
tmp_file = tempfile.NamedTemporaryFile(
prefix='dbt-macro-compiled-',
suffix='.py',
delete=False,
mode='w+',
encoding='utf-8',
)
tmp_file.write(source)
filename = tmp_file.name
else:
# `codecs.encode` actually takes a `bytes` as the first argument if
# the second argument is 'hex' - mypy does not know this.
rnd = codecs.encode(os.urandom(12), 'hex') # type: ignore
filename = rnd.decode('ascii')
# put ourselves in the cache
cache_entry = (
len(source),
None,
[line + '\n' for line in source.splitlines()],
filename
)
# linecache does in fact have an attribute `cache`, thanks
linecache.cache[filename] = cache_entry # type: ignore
return filename
class MacroFuzzParser(jinja2.parser.Parser):
def parse_macro(self):
node = jinja2.nodes.Macro(lineno=next(self.stream).lineno)
# modified to fuzz macros defined in the same file. this way
# dbt can understand the stack of macros being called.
# - @cmcarthur
node.name = get_dbt_macro_name(
self.parse_assign_target(name_only=True).name)
self.parse_signature(node)
node.body = self.parse_statements(('name:endmacro',),
drop_needle=True)
return node
class MacroFuzzEnvironment(jinja2.sandbox.SandboxedEnvironment):
def _parse(self, source, name, filename):
return MacroFuzzParser(self, source, name, filename).parse()
def _compile(self, source, filename):
"""Override jinja's compilation to stash the rendered source inside
the python linecache for debugging when the appropriate environment
variable is set.
If the value is 'write', also write the files to disk.
WARNING: This can write a ton of data if you aren't careful.
"""
if filename == '<template>' and flags.MACRO_DEBUGGING:
write = flags.MACRO_DEBUGGING == 'write'
filename = _linecache_inject(source, write)
return super()._compile(source, filename) # type: ignore
class NativeSandboxEnvironment(MacroFuzzEnvironment):
code_generator_class = jinja2.nativetypes.NativeCodeGenerator
class TextMarker(str):
"""A special native-env marker that indicates that a value is text and is
not to be evaluated. Use this to prevent your numbery-strings from becoming
numbers!
"""
class NativeMarker(str):
"""A special native-env marker that indicates the field should be passed to
literal_eval.
"""
class BoolMarker(NativeMarker):
pass
class NumberMarker(NativeMarker):
pass
def _is_number(value) -> bool:
return isinstance(value, (int, float)) and not isinstance(value, bool)
def quoted_native_concat(nodes):
"""This is almost native_concat from the NativeTemplate, except in the
special case of a single argument that is a quoted string and returns a
string, the quotes are re-inserted.
"""
head = list(islice(nodes, 2))
if not head:
return ''
if len(head) == 1:
raw = head[0]
if isinstance(raw, TextMarker):
return str(raw)
elif not isinstance(raw, NativeMarker):
# return non-strings as-is
return raw
else:
# multiple nodes become a string.
return "".join([str(v) for v in chain(head, nodes)])
try:
result = literal_eval(raw)
except (ValueError, SyntaxError, MemoryError):
result = raw
if isinstance(raw, BoolMarker) and not isinstance(result, bool):
raise JinjaRenderingException(
f"Could not convert value '{raw!s}' into type 'bool'"
)
if isinstance(raw, NumberMarker) and not _is_number(result):
raise JinjaRenderingException(
f"Could not convert value '{raw!s}' into type 'number'"
)
return result
class NativeSandboxTemplate(jinja2.nativetypes.NativeTemplate): # mypy: ignore
environment_class = NativeSandboxEnvironment
def render(self, *args, **kwargs):
"""Render the template to produce a native Python type. If the
result is a single node, its value is returned. Otherwise, the
nodes are concatenated as strings. If the result can be parsed
with :func:`ast.literal_eval`, the parsed value is returned.
Otherwise, the string is returned.
"""
vars = dict(*args, **kwargs)
try:
return quoted_native_concat(
self.root_render_func(self.new_context(vars))
)
except Exception:
return self.environment.handle_exception()
NativeSandboxEnvironment.template_class = NativeSandboxTemplate # type: ignore
class TemplateCache:
def __init__(self):
self.file_cache: Dict[str, jinja2.Template] = {}
def get_node_template(self, node) -> jinja2.Template:
key = node.macro_sql
if key in self.file_cache:
return self.file_cache[key]
template = get_template(
string=node.macro_sql,
ctx={},
node=node,
)
self.file_cache[key] = template
return template
def clear(self):
self.file_cache.clear()
template_cache = TemplateCache()
class BaseMacroGenerator:
def __init__(self, context: Optional[Dict[str, Any]] = None) -> None:
self.context: Optional[Dict[str, Any]] = context
def get_template(self):
raise NotImplementedError('get_template not implemented!')
def get_name(self) -> str:
raise NotImplementedError('get_name not implemented!')
def get_macro(self):
name = self.get_name()
template = self.get_template()
# make the module. previously we set both vars and local, but that's
# redundant: They both end up in the same place
module = template.make_module(vars=self.context, shared=False)
macro = module.__dict__[get_dbt_macro_name(name)]
module.__dict__.update(self.context)
return macro
@contextmanager
def exception_handler(self) -> Iterator[None]:
try:
yield
except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
raise_compiler_error(str(e))
def call_macro(self, *args, **kwargs):
if self.context is None:
raise InternalException(
'Context is still None in call_macro!'
)
assert self.context is not None
macro = self.get_macro()
with self.exception_handler():
try:
return macro(*args, **kwargs)
except MacroReturn as e:
return e.value
class MacroStack(threading.local):
def __init__(self):
super().__init__()
self.call_stack = []
@property
def depth(self) -> int:
return len(self.call_stack)
def push(self, name):
self.call_stack.append(name)
def pop(self, name):
got = self.call_stack.pop()
if got != name:
raise InternalException(f'popped {got}, expected {name}')
class MacroGenerator(BaseMacroGenerator):
def __init__(
self,
macro,
context: Optional[Dict[str, Any]] = None,
node: Optional[Any] = None,
stack: Optional[MacroStack] = None
) -> None:
super().__init__(context)
self.macro = macro
self.node = node
self.stack = stack
def get_template(self):
return template_cache.get_node_template(self.macro)
def get_name(self) -> str:
return self.macro.name
@contextmanager
def exception_handler(self) -> Iterator[None]:
try:
yield
except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
raise_compiler_error(str(e), self.macro)
except CompilationException as e:
e.stack.append(self.macro)
raise e
@contextmanager
def track_call(self):
if self.stack is None or self.node is None:
yield
else:
unique_id = self.macro.unique_id
depth = self.stack.depth
# only mark depth=0 as a dependency
if depth == 0:
self.node.depends_on.add_macro(unique_id)
self.stack.push(unique_id)
try:
yield
finally:
self.stack.pop(unique_id)
def __call__(self, *args, **kwargs):
with self.track_call():
return self.call_macro(*args, **kwargs)
class QueryStringGenerator(BaseMacroGenerator):
def __init__(
self, template_str: str, context: Dict[str, Any]
) -> None:
super().__init__(context)
self.template_str: str = template_str
env = get_environment()
self.template = env.from_string(
self.template_str,
globals=self.context,
)
def get_name(self) -> str:
return 'query_comment_macro'
def get_template(self):
"""Don't use the template cache, we don't have a node"""
return self.template
def __call__(self, connection_name: str, node) -> str:
return str(self.call_macro(connection_name, node))
class MaterializationExtension(jinja2.ext.Extension):
tags = ['materialization']
def parse(self, parser):
node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
materialization_name = \
parser.parse_assign_target(name_only=True).name
adapter_name = 'default'
node.args = []
node.defaults = []
while parser.stream.skip_if('comma'):
target = parser.parse_assign_target(name_only=True)
if target.name == 'default':
pass
elif target.name == 'adapter':
parser.stream.expect('assign')
value = parser.parse_expression()
adapter_name = value.value
else:
invalid_materialization_argument(
materialization_name, target.name
)
node.name = get_materialization_macro_name(
materialization_name, adapter_name
)
node.body = parser.parse_statements(('name:endmaterialization',),
drop_needle=True)
return node
class DocumentationExtension(jinja2.ext.Extension):
tags = ['docs']
def parse(self, parser):
node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
docs_name = parser.parse_assign_target(name_only=True).name
node.args = []
node.defaults = []
node.name = get_docs_macro_name(docs_name)
node.body = parser.parse_statements(('name:enddocs',),
drop_needle=True)
return node
def _is_dunder_name(name):
return name.startswith('__') and name.endswith('__')
def create_undefined(node=None):
class Undefined(jinja2.Undefined):
def __init__(self, hint=None, obj=None, name=None, exc=None):
super().__init__(hint=hint, name=name)
self.node = node
self.name = name
self.hint = hint
# jinja uses these for safety, so we have to override them.
# see https://github.com/pallets/jinja/blob/master/jinja2/sandbox.py#L332-L339 # noqa
self.unsafe_callable = False
self.alters_data = False
def __getitem__(self, name):
# Propagate the undefined value if a caller accesses this as if it
# were a dictionary
return self
def __getattr__(self, name):
if name == 'name' or _is_dunder_name(name):
raise AttributeError(
"'{}' object has no attribute '{}'"
.format(type(self).__name__, name)
)
self.name = name
return self.__class__(hint=self.hint, name=self.name)
def __call__(self, *args, **kwargs):
return self
def __reduce__(self):
raise_compiler_error(f'{self.name} is undefined', node=node)
return Undefined
NATIVE_FILTERS: Dict[str, Callable[[Any], Any]] = {
'as_text': TextMarker,
'as_bool': BoolMarker,
'as_native': NativeMarker,
'as_number': NumberMarker,
}
TEXT_FILTERS: Dict[str, Callable[[Any], Any]] = {
'as_text': lambda x: x,
'as_bool': lambda x: x,
'as_native': lambda x: x,
'as_number': lambda x: x,
}
def get_environment(
node=None,
capture_macros: bool = False,
native: bool = False,
) -> jinja2.Environment:
args: Dict[str, List[Union[str, Type[jinja2.ext.Extension]]]] = {
'extensions': ['jinja2.ext.do']
}
if capture_macros:
args['undefined'] = create_undefined(node)
args['extensions'].append(MaterializationExtension)
args['extensions'].append(DocumentationExtension)
env_cls: Type[jinja2.Environment]
text_filter: Type
if native:
env_cls = NativeSandboxEnvironment
filters = NATIVE_FILTERS
else:
env_cls = MacroFuzzEnvironment
filters = TEXT_FILTERS
env = env_cls(**args)
env.filters.update(filters)
return env
@contextmanager
def catch_jinja(node=None) -> Iterator[None]:
try:
yield
except jinja2.exceptions.TemplateSyntaxError as e:
e.translated = False
raise CompilationException(str(e), node) from e
except jinja2.exceptions.UndefinedError as e:
raise CompilationException(str(e), node) from e
except CompilationException as exc:
exc.add_node(node)
raise
def parse(string):
with catch_jinja():
return get_environment().parse(str(string))
def get_template(
string: str,
ctx: Dict[str, Any],
node=None,
capture_macros: bool = False,
native: bool = False,
):
with catch_jinja(node):
env = get_environment(node, capture_macros, native=native)
template_source = str(string)
return env.from_string(template_source, globals=ctx)
def render_template(template, ctx: Dict[str, Any], node=None) -> str:
with catch_jinja(node):
return template.render(ctx)
def _requote_result(raw_value: str, rendered: str) -> str:
double_quoted = raw_value.startswith('"') and raw_value.endswith('"')
single_quoted = raw_value.startswith("'") and raw_value.endswith("'")
if double_quoted:
quote_char = '"'
elif single_quoted:
quote_char = "'"
else:
quote_char = ''
return f'{quote_char}{rendered}{quote_char}'
# performance note: Local benmcharking (so take it with a big grain of salt!)
# on this indicates that it is is on average slightly slower than
# checking two separate patterns, but the standard deviation is smaller with
# one pattern. The time difference between the two was ~2 std deviations, which
# is small enough that I've just chosen the more readable option.
_HAS_RENDER_CHARS_PAT = re.compile(r'({[{%#]|[#}%]})')
def get_rendered(
string: str,
ctx: Dict[str, Any],
node=None,
capture_macros: bool = False,
native: bool = False,
) -> str:
# performance optimization: if there are no jinja control characters in the
# string, we can just return the input. Fall back to jinja if the type is
# not a string or if native rendering is enabled (so '1' -> 1, etc...)
# If this is desirable in the native env as well, we could handle the
# native=True case by passing the input string to ast.literal_eval, like
# the native renderer does.
if (
not native and
isinstance(string, str) and
_HAS_RENDER_CHARS_PAT.search(string) is None
):
return string
template = get_template(
string,
ctx,
node,
capture_macros=capture_macros,
native=native,
)
return render_template(template, ctx, node)
def undefined_error(msg) -> NoReturn:
raise jinja2.exceptions.UndefinedError(msg)
def extract_toplevel_blocks(
data: str,
allowed_blocks: Optional[Set[str]] = None,
collect_raw_data: bool = True,
) -> List[Union[BlockData, BlockTag]]:
"""Extract the top level blocks with matching block types from a jinja
file, with some special handling for block nesting.
:param data: The data to extract blocks from.
:param allowed_blocks: The names of the blocks to extract from the file.
They may not be nested within if/for blocks. If None, use the default
values.
:param collect_raw_data: If set, raw data between matched blocks will also
be part of the results, as `BlockData` objects. They have a
`block_type_name` field of `'__dbt_data'` and will never have a
`block_name`.
:return: A list of `BlockTag`s matching the allowed block types and (if
`collect_raw_data` is `True`) `BlockData` objects.
"""
return BlockIterator(data).lex_for_blocks(
allowed_blocks=allowed_blocks,
collect_raw_data=collect_raw_data
)
SCHEMA_TEST_KWARGS_NAME = '_dbt_schema_test_kwargs'
def add_rendered_test_kwargs(
context: Dict[str, Any],
node: Union[ParsedSchemaTestNode, CompiledSchemaTestNode],
capture_macros: bool = False,
) -> None:
"""Render each of the test kwargs in the given context using the native
renderer, then insert that value into the given context as the special test
keyword arguments member.
"""
looks_like_func = r'^\s*(env_var|ref|var|source|doc)\s*\(.+\)\s*$'
def _convert_function(
value: Any, keypath: Tuple[Union[str, int], ...]
) -> Any:
if isinstance(value, str):
if keypath == ('column_name',):
# special case: Don't render column names as native, make them
# be strings
return value
if re.match(looks_like_func, value) is not None:
# curly braces to make rendering happy
value = f'{{{{ {value} }}}}'
value = get_rendered(
value, context, node, capture_macros=capture_macros,
native=True
)
return value
kwargs = deep_map(_convert_function, node.test_metadata.kwargs)
context[SCHEMA_TEST_KWARGS_NAME] = kwargs

View File

@@ -1,9 +1,10 @@
from functools import wraps
import six
import requests
from dbt.exceptions import RegistryException
from dbt.utils import memoized
from dbt.logger import GLOBAL_LOGGER as logger
import os
import time
if os.getenv('DBT_PACKAGE_HUB_URL'):
DEFAULT_REGISTRY_BASE_URL = os.getenv('DBT_PACKAGE_HUB_URL')
@@ -21,18 +22,30 @@ def _get_url(url, registry_base_url=None):
def _wrap_exceptions(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except requests.exceptions.ConnectionError as e:
six.raise_from(
RegistryException('Unable to connect to registry hub'), e)
max_attempts = 5
attempt = 0
while True:
attempt += 1
try:
return fn(*args, **kwargs)
except requests.exceptions.ConnectionError as exc:
if attempt < max_attempts:
time.sleep(1)
continue
raise RegistryException(
'Unable to connect to registry hub'
) from exc
return wrapper
@_wrap_exceptions
def _get(path, registry_base_url=None):
url = _get_url(path, registry_base_url)
logger.debug('Making package registry request: GET {}'.format(url))
resp = requests.get(url)
logger.debug('Response from registry: GET {} {}'.format(url,
resp.status_code))
resp.raise_for_status()
return resp.json()

541
core/dbt/clients/system.py Normal file
View File

@@ -0,0 +1,541 @@
import errno
import fnmatch
import json
import os
import os.path
import re
import shutil
import subprocess
import sys
import tarfile
import requests
import stat
from typing import (
Type, NoReturn, List, Optional, Dict, Any, Tuple, Callable, Union
)
import dbt.exceptions
import dbt.utils
from dbt.logger import GLOBAL_LOGGER as logger
if sys.platform == 'win32':
from ctypes import WinDLL, c_bool
else:
WinDLL = None
c_bool = None
def find_matching(
root_path: str,
relative_paths_to_search: List[str],
file_pattern: str,
) -> List[Dict[str, str]]:
"""
Given an absolute `root_path`, a list of relative paths to that
absolute root path (`relative_paths_to_search`), and a `file_pattern`
like '*.sql', returns information about the files. For example:
> find_matching('/root/path', ['models'], '*.sql')
[ { 'absolute_path': '/root/path/models/model_one.sql',
'relative_path': 'model_one.sql',
'searched_path': 'models' },
{ 'absolute_path': '/root/path/models/subdirectory/model_two.sql',
'relative_path': 'subdirectory/model_two.sql',
'searched_path': 'models' } ]
"""
matching = []
root_path = os.path.normpath(root_path)
regex = fnmatch.translate(file_pattern)
reobj = re.compile(regex, re.IGNORECASE)
for relative_path_to_search in relative_paths_to_search:
absolute_path_to_search = os.path.join(
root_path, relative_path_to_search)
walk_results = os.walk(absolute_path_to_search)
for current_path, subdirectories, local_files in walk_results:
for local_file in local_files:
absolute_path = os.path.join(current_path, local_file)
relative_path = os.path.relpath(
absolute_path, absolute_path_to_search
)
if reobj.match(local_file):
matching.append({
'searched_path': relative_path_to_search,
'absolute_path': absolute_path,
'relative_path': relative_path,
})
return matching
def load_file_contents(path: str, strip: bool = True) -> str:
path = convert_path(path)
with open(path, 'rb') as handle:
to_return = handle.read().decode('utf-8')
if strip:
to_return = to_return.strip()
return to_return
def make_directory(path: str) -> None:
"""
Make a directory and any intermediate directories that don't already
exist. This function handles the case where two threads try to create
a directory at once.
"""
path = convert_path(path)
if not os.path.exists(path):
# concurrent writes that try to create the same dir can fail
try:
os.makedirs(path)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise e
def make_file(path: str, contents: str = '', overwrite: bool = False) -> bool:
"""
Make a file at `path` assuming that the directory it resides in already
exists. The file is saved with contents `contents`
"""
if overwrite or not os.path.exists(path):
path = convert_path(path)
with open(path, 'w') as fh:
fh.write(contents)
return True
return False
def make_symlink(source: str, link_path: str) -> None:
"""
Create a symlink at `link_path` referring to `source`.
"""
if not supports_symlinks():
dbt.exceptions.system_error('create a symbolic link')
os.symlink(source, link_path)
def supports_symlinks() -> bool:
return getattr(os, "symlink", None) is not None
def write_file(path: str, contents: str = '') -> bool:
path = convert_path(path)
try:
make_directory(os.path.dirname(path))
with open(path, 'w', encoding='utf-8') as f:
f.write(str(contents))
except Exception as exc:
# note that you can't just catch FileNotFound, because sometimes
# windows apparently raises something else.
# It's also not sufficient to look at the path length, because
# sometimes windows fails to write paths that are less than the length
# limit. So on windows, suppress all errors that happen from writing
# to disk.
if os.name == 'nt':
# sometimes we get a winerror of 3 which means the path was
# definitely too long, but other times we don't and it means the
# path was just probably too long. This is probably based on the
# windows/python version.
if getattr(exc, 'winerror', 0) == 3:
reason = 'Path was too long'
else:
reason = 'Path was possibly too long'
# all our hard work and the path was still too long. Log and
# continue.
logger.debug(
f'Could not write to path {path}({len(path)} characters): '
f'{reason}\nexception: {exc}'
)
else:
raise
return True
def read_json(path: str) -> Dict[str, Any]:
return json.loads(load_file_contents(path))
def write_json(path: str, data: Dict[str, Any]) -> bool:
return write_file(path, json.dumps(data, cls=dbt.utils.JSONEncoder))
def _windows_rmdir_readonly(
func: Callable[[str], Any], path: str, exc: Tuple[Any, OSError, Any]
):
exception_val = exc[1]
if exception_val.errno == errno.EACCES:
os.chmod(path, stat.S_IWUSR)
func(path)
else:
raise
def resolve_path_from_base(path_to_resolve: str, base_path: str) -> str:
"""
If path-to_resolve is a relative path, create an absolute path
with base_path as the base.
If path_to_resolve is an absolute path or a user path (~), just
resolve it to an absolute path and return.
"""
return os.path.abspath(
os.path.join(
base_path,
os.path.expanduser(path_to_resolve)))
def rmdir(path: str) -> None:
"""
Recursively deletes a directory. Includes an error handler to retry with
different permissions on Windows. Otherwise, removing directories (eg.
cloned via git) can cause rmtree to throw a PermissionError exception
"""
path = convert_path(path)
if sys.platform == 'win32':
onerror = _windows_rmdir_readonly
else:
onerror = None
shutil.rmtree(path, onerror=onerror)
def _win_prepare_path(path: str) -> str:
"""Given a windows path, prepare it for use by making sure it is absolute
and normalized.
"""
path = os.path.normpath(path)
# if a path starts with '\', splitdrive() on it will return '' for the
# drive, but the prefix requires a drive letter. So let's add the drive
# letter back in.
# Unless it starts with '\\'. In that case, the path is a UNC mount point
# and splitdrive will be fine.
if not path.startswith('\\\\') and path.startswith('\\'):
curdrive = os.path.splitdrive(os.getcwd())[0]
path = curdrive + path
# now our path is either an absolute UNC path or relative to the current
# directory. If it's relative, we need to make it absolute or the prefix
# won't work. `ntpath.abspath` allegedly doesn't always play nice with long
# paths, so do this instead.
if not os.path.splitdrive(path)[0]:
path = os.path.join(os.getcwd(), path)
return path
def _supports_long_paths() -> bool:
if sys.platform != 'win32':
return True
# Eryk Sun says to use `WinDLL('ntdll')` instead of `windll.ntdll` because
# of pointer caching in a comment here:
# https://stackoverflow.com/a/35097999/11262881
# I don't know exaclty what he means, but I am inclined to believe him as
# he's pretty active on Python windows bugs!
try:
dll = WinDLL('ntdll')
except OSError: # I don't think this happens? you need ntdll to run python
return False
# not all windows versions have it at all
if not hasattr(dll, 'RtlAreLongPathsEnabled'):
return False
# tell windows we want to get back a single unsigned byte (a bool).
dll.RtlAreLongPathsEnabled.restype = c_bool
return dll.RtlAreLongPathsEnabled()
def convert_path(path: str) -> str:
"""Convert a path that dbt has, which might be >260 characters long, to one
that will be writable/readable on Windows.
On other platforms, this is a no-op.
"""
# some parts of python seem to append '\*.*' to strings, better safe than
# sorry.
if len(path) < 250:
return path
if _supports_long_paths():
return path
prefix = '\\\\?\\'
# Nothing to do
if path.startswith(prefix):
return path
path = _win_prepare_path(path)
# add the prefix. The check is just in case os.getcwd() does something
# unexpected - I believe this if-state should always be True though!
if not path.startswith(prefix):
path = prefix + path
return path
def remove_file(path: str) -> None:
path = convert_path(path)
os.remove(path)
def path_exists(path: str) -> bool:
path = convert_path(path)
return os.path.lexists(path)
def path_is_symlink(path: str) -> bool:
path = convert_path(path)
return os.path.islink(path)
def open_dir_cmd() -> str:
# https://docs.python.org/2/library/sys.html#sys.platform
if sys.platform == 'win32':
return 'start'
elif sys.platform == 'darwin':
return 'open'
else:
return 'xdg-open'
def _handle_posix_cwd_error(
exc: OSError, cwd: str, cmd: List[str]
) -> NoReturn:
if exc.errno == errno.ENOENT:
message = 'Directory does not exist'
elif exc.errno == errno.EACCES:
message = 'Current user cannot access directory, check permissions'
elif exc.errno == errno.ENOTDIR:
message = 'Not a directory'
else:
message = 'Unknown OSError: {} - cwd'.format(str(exc))
raise dbt.exceptions.WorkingDirectoryError(cwd, cmd, message)
def _handle_posix_cmd_error(
exc: OSError, cwd: str, cmd: List[str]
) -> NoReturn:
if exc.errno == errno.ENOENT:
message = "Could not find command, ensure it is in the user's PATH"
elif exc.errno == errno.EACCES:
message = 'User does not have permissions for this command'
else:
message = 'Unknown OSError: {} - cmd'.format(str(exc))
raise dbt.exceptions.ExecutableError(cwd, cmd, message)
def _handle_posix_error(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
"""OSError handling for posix systems.
Some things that could happen to trigger an OSError:
- cwd could not exist
- exc.errno == ENOENT
- exc.filename == cwd
- cwd could have permissions that prevent the current user moving to it
- exc.errno == EACCES
- exc.filename == cwd
- cwd could exist but not be a directory
- exc.errno == ENOTDIR
- exc.filename == cwd
- cmd[0] could not exist
- exc.errno == ENOENT
- exc.filename == None(?)
- cmd[0] could exist but have permissions that prevents the current
user from executing it (executable bit not set for the user)
- exc.errno == EACCES
- exc.filename == None(?)
"""
if getattr(exc, 'filename', None) == cwd:
_handle_posix_cwd_error(exc, cwd, cmd)
else:
_handle_posix_cmd_error(exc, cwd, cmd)
def _handle_windows_error(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
cls: Type[dbt.exceptions.Exception] = dbt.exceptions.CommandError
if exc.errno == errno.ENOENT:
message = ("Could not find command, ensure it is in the user's PATH "
"and that the user has permissions to run it")
cls = dbt.exceptions.ExecutableError
elif exc.errno == errno.ENOEXEC:
message = ('Command was not executable, ensure it is valid')
cls = dbt.exceptions.ExecutableError
elif exc.errno == errno.ENOTDIR:
message = ('Unable to cd: path does not exist, user does not have'
' permissions, or not a directory')
cls = dbt.exceptions.WorkingDirectoryError
else:
message = 'Unknown error: {} (errno={}: "{}")'.format(
str(exc), exc.errno, errno.errorcode.get(exc.errno, '<Unknown!>')
)
raise cls(cwd, cmd, message)
def _interpret_oserror(exc: OSError, cwd: str, cmd: List[str]) -> NoReturn:
"""Interpret an OSError exc and raise the appropriate dbt exception.
"""
if len(cmd) == 0:
raise dbt.exceptions.CommandError(cwd, cmd)
# all of these functions raise unconditionally
if os.name == 'nt':
_handle_windows_error(exc, cwd, cmd)
else:
_handle_posix_error(exc, cwd, cmd)
# this should not be reachable, raise _something_ at least!
raise dbt.exceptions.InternalException(
'Unhandled exception in _interpret_oserror: {}'.format(exc)
)
def run_cmd(
cwd: str, cmd: List[str], env: Optional[Dict[str, Any]] = None
) -> Tuple[bytes, bytes]:
logger.debug('Executing "{}"'.format(' '.join(cmd)))
if len(cmd) == 0:
raise dbt.exceptions.CommandError(cwd, cmd)
# the env argument replaces the environment entirely, which has exciting
# consequences on Windows! Do an update instead.
full_env = env
if env is not None:
full_env = os.environ.copy()
full_env.update(env)
try:
proc = subprocess.Popen(
cmd,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=full_env)
out, err = proc.communicate()
except OSError as exc:
_interpret_oserror(exc, cwd, cmd)
logger.debug('STDOUT: "{!s}"'.format(out))
logger.debug('STDERR: "{!s}"'.format(err))
if proc.returncode != 0:
logger.debug('command return code={}'.format(proc.returncode))
raise dbt.exceptions.CommandResultError(cwd, cmd, proc.returncode,
out, err)
return out, err
def download(url: str, path: str, timeout: Union[float, tuple] = None) -> None:
path = convert_path(path)
connection_timeout = timeout or float(os.getenv('DBT_HTTP_TIMEOUT', 10))
response = requests.get(url, timeout=connection_timeout)
with open(path, 'wb') as handle:
for block in response.iter_content(1024 * 64):
handle.write(block)
def rename(from_path: str, to_path: str, force: bool = False) -> None:
from_path = convert_path(from_path)
to_path = convert_path(to_path)
is_symlink = path_is_symlink(to_path)
if os.path.exists(to_path) and force:
if is_symlink:
remove_file(to_path)
else:
rmdir(to_path)
shutil.move(from_path, to_path)
def untar_package(
tar_path: str, dest_dir: str, rename_to: Optional[str] = None
) -> None:
tar_path = convert_path(tar_path)
tar_dir_name = None
with tarfile.open(tar_path, 'r') as tarball:
tarball.extractall(dest_dir)
tar_dir_name = os.path.commonprefix(tarball.getnames())
if rename_to:
downloaded_path = os.path.join(dest_dir, tar_dir_name)
desired_path = os.path.join(dest_dir, rename_to)
dbt.clients.system.rename(downloaded_path, desired_path, force=True)
def chmod_and_retry(func, path, exc_info):
"""Define an error handler to pass to shutil.rmtree.
On Windows, when a file is marked read-only as git likes to do, rmtree will
fail. To handle that, on errors try to make the file writable.
We want to retry most operations here, but listdir is one that we know will
be useless.
"""
if func is os.listdir or os.name != 'nt':
raise
os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
# on error,this will raise.
func(path)
def _absnorm(path):
return os.path.normcase(os.path.abspath(path))
def move(src, dst):
"""A re-implementation of shutil.move that properly removes the source
directory on windows when it has read-only files in it and the move is
between two drives.
This is almost identical to the real shutil.move, except it uses our rmtree
and skips handling non-windows OSes since the existing one works ok there.
"""
src = convert_path(src)
dst = convert_path(dst)
if os.name != 'nt':
return shutil.move(src, dst)
if os.path.isdir(dst):
if _absnorm(src) == _absnorm(dst):
os.rename(src, dst)
return
dst = os.path.join(dst, os.path.basename(src.rstrip('/\\')))
if os.path.exists(dst):
raise EnvironmentError("Path '{}' already exists".format(dst))
try:
os.rename(src, dst)
except OSError:
# probably different drives
if os.path.isdir(src):
if _absnorm(dst + '\\').startswith(_absnorm(src + '\\')):
# dst is inside src
raise EnvironmentError(
"Cannot move a directory '{}' into itself '{}'"
.format(src, dst)
)
shutil.copytree(src, dst, symlinks=True)
rmtree(src)
else:
shutil.copy2(src, dst)
os.unlink(src)
def rmtree(path):
"""Recursively remove path. On permissions errors on windows, try to remove
the read-only flag and try again.
"""
path = convert_path(path)
return shutil.rmtree(path, onerror=chmod_and_retry)

View File

@@ -1,9 +1,17 @@
import dbt.compat
from typing import Any
import dbt.exceptions
import yaml
import yaml.scanner
# the C version is faster, but it doesn't always exist
YamlLoader: Any
try:
from yaml import CSafeLoader as YamlLoader
except ImportError:
from yaml import SafeLoader as YamlLoader
YAML_ERROR_MESSAGE = """
Syntax error near line {line_number}
@@ -17,7 +25,7 @@ Raw Error:
def line_no(i, line, width=3):
line_number = dbt.compat.to_string(i).ljust(width)
line_number = str(i).ljust(width)
return "{}| {}".format(line_number, line)
@@ -45,13 +53,17 @@ def contextualized_yaml_error(raw_contents, error):
raw_error=error)
def safe_load(contents):
return yaml.load(contents, Loader=YamlLoader)
def load_yaml_text(contents):
try:
return yaml.safe_load(contents)
return safe_load(contents)
except (yaml.scanner.ScannerError, yaml.YAMLError) as e:
if hasattr(e, 'problem_mark'):
error = contextualized_yaml_error(contents, e)
else:
error = dbt.compat.to_string(e)
error = str(e)
raise dbt.exceptions.ValidationException(error)

493
core/dbt/compilation.py Normal file
View File

@@ -0,0 +1,493 @@
import os
from collections import defaultdict
from typing import List, Dict, Any, Tuple, cast, Optional
import networkx as nx # type: ignore
import sqlparse
from dbt import flags
from dbt.adapters.factory import get_adapter
from dbt.clients import jinja
from dbt.clients.system import make_directory
from dbt.context.providers import generate_runtime_model
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.graph.compiled import (
CompiledDataTestNode,
CompiledSchemaTestNode,
COMPILED_TYPES,
GraphMemberNode,
InjectedCTE,
ManifestNode,
NonSourceCompiledNode,
)
from dbt.contracts.graph.parsed import ParsedNode
from dbt.exceptions import (
dependency_not_found,
InternalException,
RuntimeException,
)
from dbt.graph import Graph
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.node_types import NodeType
from dbt.utils import pluralize
graph_file_name = 'graph.gpickle'
def _compiled_type_for(model: ParsedNode):
if type(model) not in COMPILED_TYPES:
raise InternalException(
f'Asked to compile {type(model)} node, but it has no compiled form'
)
return COMPILED_TYPES[type(model)]
def print_compile_stats(stats):
names = {
NodeType.Model: 'model',
NodeType.Test: 'test',
NodeType.Snapshot: 'snapshot',
NodeType.Analysis: 'analysis',
NodeType.Macro: 'macro',
NodeType.Operation: 'operation',
NodeType.Seed: 'seed file',
NodeType.Source: 'source',
}
results = {k: 0 for k in names.keys()}
results.update(stats)
stat_line = ", ".join([
pluralize(ct, names.get(t)) for t, ct in results.items()
if t in names
])
logger.info("Found {}".format(stat_line))
def _node_enabled(node: ManifestNode):
# Disabled models are already excluded from the manifest
if node.resource_type == NodeType.Test and not node.config.enabled:
return False
else:
return True
def _generate_stats(manifest: Manifest):
stats: Dict[NodeType, int] = defaultdict(int)
for node in manifest.nodes.values():
if _node_enabled(node):
stats[node.resource_type] += 1
for source in manifest.sources.values():
stats[source.resource_type] += 1
for macro in manifest.macros.values():
stats[macro.resource_type] += 1
return stats
def _add_prepended_cte(prepended_ctes, new_cte):
for cte in prepended_ctes:
if cte.id == new_cte.id:
cte.sql = new_cte.sql
return
prepended_ctes.append(new_cte)
def _extend_prepended_ctes(prepended_ctes, new_prepended_ctes):
for new_cte in new_prepended_ctes:
_add_prepended_cte(prepended_ctes, new_cte)
class Linker:
def __init__(self, data=None):
if data is None:
data = {}
self.graph = nx.DiGraph(**data)
def edges(self):
return self.graph.edges()
def nodes(self):
return self.graph.nodes()
def find_cycles(self):
try:
cycle = nx.find_cycle(self.graph)
except nx.NetworkXNoCycle:
return None
else:
# cycles is a List[Tuple[str, ...]]
return " --> ".join(c[0] for c in cycle)
def dependency(self, node1, node2):
"indicate that node1 depends on node2"
self.graph.add_node(node1)
self.graph.add_node(node2)
self.graph.add_edge(node2, node1)
def add_node(self, node):
self.graph.add_node(node)
def write_graph(self, outfile: str, manifest: Manifest):
"""Write the graph to a gpickle file. Before doing so, serialize and
include all nodes in their corresponding graph entries.
"""
out_graph = self.graph.copy()
for node_id in self.graph.nodes():
data = manifest.expect(node_id).to_dict()
out_graph.add_node(node_id, **data)
nx.write_gpickle(out_graph, outfile)
class Compiler:
def __init__(self, config):
self.config = config
def initialize(self):
make_directory(self.config.target_path)
make_directory(self.config.modules_path)
def _create_node_context(
self,
node: NonSourceCompiledNode,
manifest: Manifest,
extra_context: Dict[str, Any],
) -> Dict[str, Any]:
context = generate_runtime_model(
node, self.config, manifest
)
context.update(extra_context)
if isinstance(node, CompiledSchemaTestNode):
# for test nodes, add a special keyword args value to the context
jinja.add_rendered_test_kwargs(context, node)
return context
def add_ephemeral_prefix(self, name: str):
adapter = get_adapter(self.config)
relation_cls = adapter.Relation
return relation_cls.add_ephemeral_prefix(name)
def _get_compiled_model(
self,
manifest: Manifest,
cte_id: str,
extra_context: Dict[str, Any],
) -> NonSourceCompiledNode:
if cte_id not in manifest.nodes:
raise InternalException(
f'During compilation, found a cte reference that could not be '
f'resolved: {cte_id}'
)
cte_model = manifest.nodes[cte_id]
if getattr(cte_model, 'compiled', False):
assert isinstance(cte_model, tuple(COMPILED_TYPES.values()))
return cast(NonSourceCompiledNode, cte_model)
elif cte_model.is_ephemeral_model:
# this must be some kind of parsed node that we can compile.
# we know it's not a parsed source definition
assert isinstance(cte_model, tuple(COMPILED_TYPES))
# update the node so
node = self.compile_node(cte_model, manifest, extra_context)
manifest.sync_update_node(node)
return node
else:
raise InternalException(
f'During compilation, found an uncompiled cte that '
f'was not an ephemeral model: {cte_id}'
)
def _inject_ctes_into_sql(self, sql: str, ctes: List[InjectedCTE]) -> str:
"""
`ctes` is a list of InjectedCTEs like:
[
InjectedCTE(
id="cte_id_1",
sql="__dbt__CTE__ephemeral as (select * from table)",
),
InjectedCTE(
id="cte_id_2",
sql="__dbt__CTE__events as (select id, type from events)",
),
]
Given `sql` like:
"with internal_cte as (select * from sessions)
select * from internal_cte"
This will spit out:
"with __dbt__CTE__ephemeral as (select * from table),
__dbt__CTE__events as (select id, type from events),
with internal_cte as (select * from sessions)
select * from internal_cte"
(Whitespace enhanced for readability.)
"""
if len(ctes) == 0:
return sql
parsed_stmts = sqlparse.parse(sql)
parsed = parsed_stmts[0]
with_stmt = None
for token in parsed.tokens:
if token.is_keyword and token.normalized == 'WITH':
with_stmt = token
break
if with_stmt is None:
# no with stmt, add one, and inject CTEs right at the beginning
first_token = parsed.token_first()
with_stmt = sqlparse.sql.Token(sqlparse.tokens.Keyword, 'with')
parsed.insert_before(first_token, with_stmt)
else:
# stmt exists, add a comma (which will come after injected CTEs)
trailing_comma = sqlparse.sql.Token(
sqlparse.tokens.Punctuation, ','
)
parsed.insert_after(with_stmt, trailing_comma)
token = sqlparse.sql.Token(
sqlparse.tokens.Keyword,
", ".join(c.sql for c in ctes)
)
parsed.insert_after(with_stmt, token)
return str(parsed)
def _model_prepend_ctes(
self,
model: NonSourceCompiledNode,
prepended_ctes: List[InjectedCTE]
) -> NonSourceCompiledNode:
if model.compiled_sql is None:
raise RuntimeException(
'Cannot prepend ctes to an unparsed node', model
)
injected_sql = self._inject_ctes_into_sql(
model.compiled_sql,
prepended_ctes,
)
model.extra_ctes_injected = True
model.extra_ctes = prepended_ctes
model.injected_sql = injected_sql
model.validate(model.to_dict())
return model
def _get_dbt_test_name(self) -> str:
return 'dbt__CTE__INTERNAL_test'
def _recursively_prepend_ctes(
self,
model: NonSourceCompiledNode,
manifest: Manifest,
extra_context: Dict[str, Any],
) -> Tuple[NonSourceCompiledNode, List[InjectedCTE]]:
if model.extra_ctes_injected:
return (model, model.extra_ctes)
if flags.STRICT_MODE:
if not isinstance(model, tuple(COMPILED_TYPES.values())):
raise InternalException(
f'Bad model type: {type(model)}'
)
prepended_ctes: List[InjectedCTE] = []
dbt_test_name = self._get_dbt_test_name()
for cte in model.extra_ctes:
if cte.id == dbt_test_name:
sql = cte.sql
else:
cte_model = self._get_compiled_model(
manifest,
cte.id,
extra_context,
)
cte_model, new_prepended_ctes = self._recursively_prepend_ctes(
cte_model, manifest, extra_context
)
_extend_prepended_ctes(prepended_ctes, new_prepended_ctes)
new_cte_name = self.add_ephemeral_prefix(cte_model.name)
sql = f' {new_cte_name} as (\n{cte_model.compiled_sql}\n)'
_add_prepended_cte(prepended_ctes, InjectedCTE(id=cte.id, sql=sql))
model = self._model_prepend_ctes(model, prepended_ctes)
manifest.update_node(model)
return model, prepended_ctes
def _insert_ctes(
self,
compiled_node: NonSourceCompiledNode,
manifest: Manifest,
extra_context: Dict[str, Any],
) -> NonSourceCompiledNode:
"""Insert the CTEs for the model."""
# for data tests, we need to insert a special CTE at the end of the
# list containing the test query, and then have the "real" query be a
# select count(*) from that model.
# the benefit of doing it this way is that _insert_ctes() can be
# rewritten for different adapters to handle databses that don't
# support CTEs, or at least don't have full support.
if isinstance(compiled_node, CompiledDataTestNode):
# the last prepend (so last in order) should be the data test body.
# then we can add our select count(*) from _that_ cte as the "real"
# compiled_sql, and do the regular prepend logic from CTEs.
name = self._get_dbt_test_name()
cte = InjectedCTE(
id=name,
sql=f' {name} as (\n{compiled_node.compiled_sql}\n)'
)
compiled_node.extra_ctes.append(cte)
compiled_node.compiled_sql = f'\nselect count(*) from {name}'
injected_node, _ = self._recursively_prepend_ctes(
compiled_node, manifest, extra_context
)
return injected_node
def _compile_node(
self,
node: ManifestNode,
manifest: Manifest,
extra_context: Optional[Dict[str, Any]] = None,
) -> NonSourceCompiledNode:
if extra_context is None:
extra_context = {}
logger.debug("Compiling {}".format(node.unique_id))
data = node.to_dict()
data.update({
'compiled': False,
'compiled_sql': None,
'extra_ctes_injected': False,
'extra_ctes': [],
'injected_sql': None,
})
compiled_node = _compiled_type_for(node).from_dict(data)
context = self._create_node_context(
compiled_node, manifest, extra_context
)
compiled_node.compiled_sql = jinja.get_rendered(
node.raw_sql,
context,
node,
)
compiled_node.compiled = True
injected_node = self._insert_ctes(
compiled_node, manifest, extra_context
)
return injected_node
def write_graph_file(self, linker: Linker, manifest: Manifest):
filename = graph_file_name
graph_path = os.path.join(self.config.target_path, filename)
if flags.WRITE_JSON:
linker.write_graph(graph_path, manifest)
def link_node(
self, linker: Linker, node: GraphMemberNode, manifest: Manifest
):
linker.add_node(node.unique_id)
for dependency in node.depends_on_nodes:
if dependency in manifest.nodes:
linker.dependency(
node.unique_id,
(manifest.nodes[dependency].unique_id)
)
elif dependency in manifest.sources:
linker.dependency(
node.unique_id,
(manifest.sources[dependency].unique_id)
)
else:
dependency_not_found(node, dependency)
def link_graph(self, linker: Linker, manifest: Manifest):
for source in manifest.sources.values():
linker.add_node(source.unique_id)
for node in manifest.nodes.values():
self.link_node(linker, node, manifest)
for exposure in manifest.exposures.values():
self.link_node(linker, exposure, manifest)
# linker.add_node(exposure.unique_id)
cycle = linker.find_cycles()
if cycle:
raise RuntimeError("Found a cycle: {}".format(cycle))
def compile(self, manifest: Manifest, write=True) -> Graph:
self.initialize()
linker = Linker()
self.link_graph(linker, manifest)
stats = _generate_stats(manifest)
if write:
self.write_graph_file(linker, manifest)
print_compile_stats(stats)
return Graph(linker.graph)
def _write_node(self, node: NonSourceCompiledNode) -> ManifestNode:
if not _is_writable(node):
return node
logger.debug(f'Writing injected SQL for node "{node.unique_id}"')
if node.injected_sql is None:
# this should not really happen, but it'd be a shame to crash
# over it
logger.error(
f'Compiled node "{node.unique_id}" had no injected_sql, '
'cannot write sql!'
)
else:
node.build_path = node.write_node(
self.config.target_path,
'compiled',
node.injected_sql
)
return node
def compile_node(
self,
node: ManifestNode,
manifest: Manifest,
extra_context: Optional[Dict[str, Any]] = None,
write: bool = True,
) -> NonSourceCompiledNode:
node = self._compile_node(node, manifest, extra_context)
if write and _is_writable(node):
self._write_node(node)
return node
def _is_writable(node):
if not node.injected_sql:
return False
if node.resource_type == NodeType.Snapshot:
return False
return True

View File

@@ -0,0 +1,4 @@
# all these are just exports, they need "noqa" so flake8 will not complain.
from .profile import Profile, PROFILES_DIR, read_user_config # noqa
from .project import Project # noqa
from .runtime import RuntimeConfig, UnsetProfileConfig # noqa

423
core/dbt/config/profile.py Normal file
View File

@@ -0,0 +1,423 @@
from dataclasses import dataclass
from typing import Any, Dict, Optional, Tuple
import os
from hologram import ValidationError
from dbt.clients.system import load_file_contents
from dbt.clients.yaml_helper import load_yaml_text
from dbt.contracts.connection import Credentials, HasCredentials
from dbt.contracts.project import ProfileConfig, UserConfig
from dbt.exceptions import CompilationException
from dbt.exceptions import DbtProfileError
from dbt.exceptions import DbtProjectError
from dbt.exceptions import ValidationException
from dbt.exceptions import RuntimeException
from dbt.exceptions import validator_error_message
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.utils import coerce_dict_str
from .renderer import ProfileRenderer
DEFAULT_THREADS = 1
DEFAULT_PROFILES_DIR = os.path.join(os.path.expanduser('~'), '.dbt')
PROFILES_DIR = os.path.expanduser(
os.getenv('DBT_PROFILES_DIR', DEFAULT_PROFILES_DIR)
)
INVALID_PROFILE_MESSAGE = """
dbt encountered an error while trying to read your profiles.yml file.
{error_string}
"""
NO_SUPPLIED_PROFILE_ERROR = """\
dbt cannot run because no profile was specified for this dbt project.
To specify a profile for this project, add a line like the this to
your dbt_project.yml file:
profile: [profile name]
Here, [profile name] should be replaced with a profile name
defined in your profiles.yml file. You can find profiles.yml here:
{profiles_file}/profiles.yml
""".format(profiles_file=PROFILES_DIR)
def read_profile(profiles_dir: str) -> Dict[str, Any]:
path = os.path.join(profiles_dir, 'profiles.yml')
contents = None
if os.path.isfile(path):
try:
contents = load_file_contents(path, strip=False)
yaml_content = load_yaml_text(contents)
if not yaml_content:
msg = f'The profiles.yml file at {path} is empty'
raise DbtProfileError(
INVALID_PROFILE_MESSAGE.format(
error_string=msg
)
)
return yaml_content
except ValidationException as e:
msg = INVALID_PROFILE_MESSAGE.format(error_string=e)
raise ValidationException(msg) from e
return {}
def read_user_config(directory: str) -> UserConfig:
try:
profile = read_profile(directory)
if profile:
user_cfg = coerce_dict_str(profile.get('config', {}))
if user_cfg is not None:
return UserConfig.from_dict(user_cfg)
except (RuntimeException, ValidationError):
pass
return UserConfig()
@dataclass
class Profile(HasCredentials):
profile_name: str
target_name: str
config: UserConfig
threads: int
credentials: Credentials
def to_profile_info(
self, serialize_credentials: bool = False
) -> Dict[str, Any]:
"""Unlike to_project_config, this dict is not a mirror of any existing
on-disk data structure. It's used when creating a new profile from an
existing one.
:param serialize_credentials bool: If True, serialize the credentials.
Otherwise, the Credentials object will be copied.
:returns dict: The serialized profile.
"""
result = {
'profile_name': self.profile_name,
'target_name': self.target_name,
'config': self.config,
'threads': self.threads,
'credentials': self.credentials,
}
if serialize_credentials:
result['config'] = self.config.to_dict()
result['credentials'] = self.credentials.to_dict()
return result
def to_target_dict(self) -> Dict[str, Any]:
target = dict(
self.credentials.connection_info(with_aliases=True)
)
target.update({
'type': self.credentials.type,
'threads': self.threads,
'name': self.target_name,
'target_name': self.target_name,
'profile_name': self.profile_name,
'config': self.config.to_dict(),
})
return target
def __eq__(self, other: object) -> bool:
if not (isinstance(other, self.__class__) and
isinstance(self, other.__class__)):
return NotImplemented
return self.to_profile_info() == other.to_profile_info()
def validate(self):
try:
if self.credentials:
self.credentials.to_dict(validate=True)
ProfileConfig.from_dict(
self.to_profile_info(serialize_credentials=True)
)
except ValidationError as exc:
raise DbtProfileError(validator_error_message(exc)) from exc
@staticmethod
def _credentials_from_profile(
profile: Dict[str, Any], profile_name: str, target_name: str
) -> Credentials:
# avoid an import cycle
from dbt.adapters.factory import load_plugin
# credentials carry their 'type' in their actual type, not their
# attributes. We do want this in order to pick our Credentials class.
if 'type' not in profile:
raise DbtProfileError(
'required field "type" not found in profile {} and target {}'
.format(profile_name, target_name))
typename = profile.pop('type')
try:
cls = load_plugin(typename)
credentials = cls.from_dict(profile)
except (RuntimeException, ValidationError) as e:
msg = str(e) if isinstance(e, RuntimeException) else e.message
raise DbtProfileError(
'Credentials in profile "{}", target "{}" invalid: {}'
.format(profile_name, target_name, msg)
) from e
return credentials
@staticmethod
def pick_profile_name(
args_profile_name: Optional[str],
project_profile_name: Optional[str] = None,
) -> str:
profile_name = project_profile_name
if args_profile_name is not None:
profile_name = args_profile_name
if profile_name is None:
raise DbtProjectError(NO_SUPPLIED_PROFILE_ERROR)
return profile_name
@staticmethod
def _get_profile_data(
profile: Dict[str, Any], profile_name: str, target_name: str
) -> Dict[str, Any]:
if 'outputs' not in profile:
raise DbtProfileError(
"outputs not specified in profile '{}'".format(profile_name)
)
outputs = profile['outputs']
if target_name not in outputs:
outputs = '\n'.join(' - {}'.format(output)
for output in outputs)
msg = ("The profile '{}' does not have a target named '{}'. The "
"valid target names for this profile are:\n{}"
.format(profile_name, target_name, outputs))
raise DbtProfileError(msg, result_type='invalid_target')
profile_data = outputs[target_name]
if not isinstance(profile_data, dict):
msg = (
f"output '{target_name}' of profile '{profile_name}' is "
f"misconfigured in profiles.yml"
)
raise DbtProfileError(msg, result_type='invalid_target')
return profile_data
@classmethod
def from_credentials(
cls,
credentials: Credentials,
threads: int,
profile_name: str,
target_name: str,
user_cfg: Optional[Dict[str, Any]] = None
) -> 'Profile':
"""Create a profile from an existing set of Credentials and the
remaining information.
:param credentials: The credentials dict for this profile.
:param threads: The number of threads to use for connections.
:param profile_name: The profile name used for this profile.
:param target_name: The target name used for this profile.
:param user_cfg: The user-level config block from the
raw profiles, if specified.
:raises DbtProfileError: If the profile is invalid.
:returns: The new Profile object.
"""
if user_cfg is None:
user_cfg = {}
config = UserConfig.from_dict(user_cfg)
profile = cls(
profile_name=profile_name,
target_name=target_name,
config=config,
threads=threads,
credentials=credentials
)
profile.validate()
return profile
@classmethod
def render_profile(
cls,
raw_profile: Dict[str, Any],
profile_name: str,
target_override: Optional[str],
renderer: ProfileRenderer,
) -> Tuple[str, Dict[str, Any]]:
"""This is a containment zone for the hateful way we're rendering
profiles.
"""
# rendering profiles is a bit complex. Two constraints cause trouble:
# 1) users should be able to use environment/cli variables to specify
# the target in their profile.
# 2) Missing environment/cli variables in profiles/targets that don't
# end up getting selected should not cause errors.
# so first we'll just render the target name, then we use that rendered
# name to extract a profile that we can render.
if target_override is not None:
target_name = target_override
elif 'target' in raw_profile:
# render the target if it was parsed from yaml
target_name = renderer.render_value(raw_profile['target'])
else:
target_name = 'default'
logger.debug(
"target not specified in profile '{}', using '{}'"
.format(profile_name, target_name)
)
raw_profile_data = cls._get_profile_data(
raw_profile, profile_name, target_name
)
try:
profile_data = renderer.render_data(raw_profile_data)
except CompilationException as exc:
raise DbtProfileError(str(exc)) from exc
return target_name, profile_data
@classmethod
def from_raw_profile_info(
cls,
raw_profile: Dict[str, Any],
profile_name: str,
renderer: ProfileRenderer,
user_cfg: Optional[Dict[str, Any]] = None,
target_override: Optional[str] = None,
threads_override: Optional[int] = None,
) -> 'Profile':
"""Create a profile from its raw profile information.
(this is an intermediate step, mostly useful for unit testing)
:param raw_profile: The profile data for a single profile, from
disk as yaml and its values rendered with jinja.
:param profile_name: The profile name used.
:param renderer: The config renderer.
:param user_cfg: The global config for the user, if it
was present.
:param target_override: The target to use, if provided on
the command line.
:param threads_override: The thread count to use, if
provided on the command line.
:raises DbtProfileError: If the profile is invalid or missing, or the
target could not be found
:returns: The new Profile object.
"""
# user_cfg is not rendered.
if user_cfg is None:
user_cfg = raw_profile.get('config')
# TODO: should it be, and the values coerced to bool?
target_name, profile_data = cls.render_profile(
raw_profile, profile_name, target_override, renderer
)
# valid connections never include the number of threads, but it's
# stored on a per-connection level in the raw configs
threads = profile_data.pop('threads', DEFAULT_THREADS)
if threads_override is not None:
threads = threads_override
credentials: Credentials = cls._credentials_from_profile(
profile_data, profile_name, target_name
)
return cls.from_credentials(
credentials=credentials,
profile_name=profile_name,
target_name=target_name,
threads=threads,
user_cfg=user_cfg
)
@classmethod
def from_raw_profiles(
cls,
raw_profiles: Dict[str, Any],
profile_name: str,
renderer: ProfileRenderer,
target_override: Optional[str] = None,
threads_override: Optional[int] = None,
) -> 'Profile':
"""
:param raw_profiles: The profile data, from disk as yaml.
:param profile_name: The profile name to use.
:param renderer: The config renderer.
:param target_override: The target to use, if provided on the command
line.
:param threads_override: The thread count to use, if provided on the
command line.
:raises DbtProjectError: If there is no profile name specified in the
project or the command line arguments
:raises DbtProfileError: If the profile is invalid or missing, or the
target could not be found
:returns: The new Profile object.
"""
if profile_name not in raw_profiles:
raise DbtProjectError(
"Could not find profile named '{}'".format(profile_name)
)
# First, we've already got our final decision on profile name, and we
# don't render keys, so we can pluck that out
raw_profile = raw_profiles[profile_name]
if not raw_profile:
msg = (
f'Profile {profile_name} in profiles.yml is empty'
)
raise DbtProfileError(
INVALID_PROFILE_MESSAGE.format(
error_string=msg
)
)
user_cfg = raw_profiles.get('config')
return cls.from_raw_profile_info(
raw_profile=raw_profile,
profile_name=profile_name,
renderer=renderer,
user_cfg=user_cfg,
target_override=target_override,
threads_override=threads_override,
)
@classmethod
def render_from_args(
cls,
args: Any,
renderer: ProfileRenderer,
project_profile_name: Optional[str],
) -> 'Profile':
"""Given the raw profiles as read from disk and the name of the desired
profile if specified, return the profile component of the runtime
config.
:param args argparse.Namespace: The arguments as parsed from the cli.
:param project_profile_name Optional[str]: The profile name, if
specified in a project.
:raises DbtProjectError: If there is no profile name specified in the
project or the command line arguments, or if the specified profile
is not found
:raises DbtProfileError: If the profile is invalid or missing, or the
target could not be found.
:returns Profile: The new Profile object.
"""
threads_override = getattr(args, 'threads', None)
target_override = getattr(args, 'target', None)
raw_profiles = read_profile(args.profiles_dir)
profile_name = cls.pick_profile_name(getattr(args, 'profile', None),
project_profile_name)
return cls.from_raw_profiles(
raw_profiles=raw_profiles,
profile_name=profile_name,
renderer=renderer,
target_override=target_override,
threads_override=threads_override
)

781
core/dbt/config/project.py Normal file
View File

@@ -0,0 +1,781 @@
from copy import deepcopy
from dataclasses import dataclass, field
from itertools import chain
from typing import (
List, Dict, Any, Optional, TypeVar, Union, Tuple, Callable, Mapping,
Iterable, Set
)
from typing_extensions import Protocol
import hashlib
import os
from dbt.clients.system import resolve_path_from_base
from dbt.clients.system import path_exists
from dbt.clients.system import load_file_contents
from dbt.clients.yaml_helper import load_yaml_text
from dbt.contracts.connection import QueryComment
from dbt.exceptions import DbtProjectError
from dbt.exceptions import RecursionException
from dbt.exceptions import SemverException
from dbt.exceptions import validator_error_message
from dbt.exceptions import RuntimeException
from dbt.graph import SelectionSpec
from dbt.helper_types import NoValue
from dbt.semver import VersionSpecifier
from dbt.semver import versions_compatible
from dbt.version import get_installed_version
from dbt.utils import deep_map, MultiDict
from dbt.legacy_config_updater import ConfigUpdater, IsFQNResource
from dbt.contracts.project import (
ProjectV1 as ProjectV1Contract,
ProjectV2 as ProjectV2Contract,
parse_project_config,
SemverString,
)
from dbt.contracts.project import PackageConfig
from hologram import ValidationError
from .renderer import DbtProjectYamlRenderer
from .selectors import (
selector_config_from_data,
selector_data_from_root,
SelectorConfig,
)
INVALID_VERSION_ERROR = """\
This version of dbt is not supported with the '{package}' package.
Installed version of dbt: {installed}
Required version of dbt for '{package}': {version_spec}
Check the requirements for the '{package}' package, or run dbt again with \
--no-version-check
"""
IMPOSSIBLE_VERSION_ERROR = """\
The package version requirement can never be satisfied for the '{package}
package.
Required versions of dbt for '{package}': {version_spec}
Check the requirements for the '{package}' package, or run dbt again with \
--no-version-check
"""
MALFORMED_PACKAGE_ERROR = """\
The packages.yml file in this project is malformed. Please double check
the contents of this file and fix any errors before retrying.
You can find more information on the syntax for this file here:
https://docs.getdbt.com/docs/package-management
Validator Error:
{error}
"""
def _list_if_none(value):
if value is None:
value = []
return value
def _dict_if_none(value):
if value is None:
value = {}
return value
def _list_if_none_or_string(value):
value = _list_if_none(value)
if isinstance(value, str):
return [value]
return value
def _load_yaml(path):
contents = load_file_contents(path)
return load_yaml_text(contents)
def package_data_from_root(project_root):
package_filepath = resolve_path_from_base(
'packages.yml', project_root
)
if path_exists(package_filepath):
packages_dict = _load_yaml(package_filepath)
else:
packages_dict = None
return packages_dict
def package_config_from_data(packages_data):
if packages_data is None:
packages_data = {'packages': []}
try:
packages = PackageConfig.from_dict(packages_data)
except ValidationError as e:
raise DbtProjectError(
MALFORMED_PACKAGE_ERROR.format(error=str(e.message))
) from e
return packages
def _parse_versions(versions: Union[List[str], str]) -> List[VersionSpecifier]:
"""Parse multiple versions as read from disk. The versions value may be any
one of:
- a single version string ('>0.12.1')
- a single string specifying multiple comma-separated versions
('>0.11.1,<=0.12.2')
- an array of single-version strings (['>0.11.1', '<=0.12.2'])
Regardless, this will return a list of VersionSpecifiers
"""
if isinstance(versions, str):
versions = versions.split(',')
return [VersionSpecifier.from_version_string(v) for v in versions]
def _all_source_paths(
source_paths: List[str],
data_paths: List[str],
snapshot_paths: List[str],
analysis_paths: List[str],
macro_paths: List[str],
) -> List[str]:
return list(chain(source_paths, data_paths, snapshot_paths, analysis_paths,
macro_paths))
T = TypeVar('T')
def value_or(value: Optional[T], default: T) -> T:
if value is None:
return default
else:
return value
def _raw_project_from(project_root: str) -> Dict[str, Any]:
project_root = os.path.normpath(project_root)
project_yaml_filepath = os.path.join(project_root, 'dbt_project.yml')
# get the project.yml contents
if not path_exists(project_yaml_filepath):
raise DbtProjectError(
'no dbt_project.yml found at expected path {}'
.format(project_yaml_filepath)
)
project_dict = _load_yaml(project_yaml_filepath)
if not isinstance(project_dict, dict):
raise DbtProjectError(
'dbt_project.yml does not parse to a dictionary'
)
return project_dict
def _query_comment_from_cfg(
cfg_query_comment: Union[QueryComment, NoValue, str, None]
) -> QueryComment:
if not cfg_query_comment:
return QueryComment(comment='')
if isinstance(cfg_query_comment, str):
return QueryComment(comment=cfg_query_comment)
if isinstance(cfg_query_comment, NoValue):
return QueryComment()
return cfg_query_comment
@dataclass
class PartialProject:
config_version: int = field(metadata=dict(
description='The version of the configuration file format'
))
profile_name: Optional[str] = field(metadata=dict(
description='The unrendered profile name in the project, if set'
))
project_name: Optional[str] = field(metadata=dict(
description=(
'The name of the project. This should always be set and will not '
'be rendered'
)
))
project_root: str = field(
metadata=dict(description='The root directory of the project'),
)
project_dict: Dict[str, Any]
verify_version: bool = field(
metadata=dict(description=(
'If True, verify the dbt version matches the required version'
))
)
def render(self, renderer):
packages_dict = package_data_from_root(self.project_root)
selectors_dict = selector_data_from_root(self.project_root)
return Project.render_from_dict(
self.project_root,
self.project_dict,
packages_dict,
selectors_dict,
renderer,
verify_version=self.verify_version,
)
def render_profile_name(self, renderer) -> Optional[str]:
if self.profile_name is None:
return None
return renderer.render_value(self.profile_name)
class VarProvider(Protocol):
"""Var providers are tied to a particular Project."""
def vars_for(
self, node: IsFQNResource, adapter_type: str
) -> Mapping[str, Any]:
raise NotImplementedError(
f'vars_for not implemented for {type(self)}!'
)
def to_dict(self):
raise NotImplementedError(
f'to_dict not implemented for {type(self)}!'
)
class V1VarProvider(VarProvider):
def __init__(
self,
models: Dict[str, Any],
seeds: Dict[str, Any],
snapshots: Dict[str, Any],
) -> None:
self.models = models
self.seeds = seeds
self.snapshots = snapshots
self.sources: Dict[str, Any] = {}
def vars_for(
self, node: IsFQNResource, adapter_type: str
) -> Mapping[str, Any]:
updater = ConfigUpdater(adapter_type)
return updater.get_project_config(node, self).get('vars', {})
def to_dict(self):
raise ValidationError(
'to_dict was called on a v1 vars, but it should only be called '
'on v2 vars'
)
class V2VarProvider(VarProvider):
def __init__(
self,
vars: Dict[str, Dict[str, Any]]
) -> None:
self.vars = vars
def vars_for(
self, node: IsFQNResource, adapter_type: str
) -> Mapping[str, Any]:
# in v2, vars are only either project or globally scoped
merged = MultiDict([self.vars])
merged.add(self.vars.get(node.package_name, {}))
return merged
def to_dict(self):
return self.vars
def validate_version(
required: List[VersionSpecifier],
project_name: str,
) -> None:
"""Ensure this package works with the installed version of dbt."""
installed = get_installed_version()
if not versions_compatible(*required):
msg = IMPOSSIBLE_VERSION_ERROR.format(
package=project_name,
version_spec=[
x.to_version_string() for x in required
]
)
raise DbtProjectError(msg)
if not versions_compatible(installed, *required):
msg = INVALID_VERSION_ERROR.format(
package=project_name,
installed=installed.to_version_string(),
version_spec=[
x.to_version_string() for x in required
]
)
raise DbtProjectError(msg)
@dataclass
class Project:
project_name: str
version: Union[SemverString, float]
project_root: str
profile_name: Optional[str]
source_paths: List[str]
macro_paths: List[str]
data_paths: List[str]
test_paths: List[str]
analysis_paths: List[str]
docs_paths: List[str]
asset_paths: List[str]
target_path: str
snapshot_paths: List[str]
clean_targets: List[str]
log_path: str
modules_path: str
quoting: Dict[str, Any]
models: Dict[str, Any]
on_run_start: List[str]
on_run_end: List[str]
seeds: Dict[str, Any]
snapshots: Dict[str, Any]
sources: Dict[str, Any]
vars: VarProvider
dbt_version: List[VersionSpecifier]
packages: Dict[str, Any]
selectors: SelectorConfig
query_comment: QueryComment
config_version: int
@property
def all_source_paths(self) -> List[str]:
return _all_source_paths(
self.source_paths, self.data_paths, self.snapshot_paths,
self.analysis_paths, self.macro_paths
)
@staticmethod
def _preprocess(project_dict: Dict[str, Any]) -> Dict[str, Any]:
"""Pre-process certain special keys to convert them from None values
into empty containers, and to turn strings into arrays of strings.
"""
handlers: Dict[Tuple[Union[str, int], ...], Callable[[Any], Any]] = {
('on-run-start',): _list_if_none_or_string,
('on-run-end',): _list_if_none_or_string,
}
for k in ('models', 'seeds', 'snapshots'):
handlers[(k,)] = _dict_if_none
handlers[(k, 'vars')] = _dict_if_none
handlers[(k, 'pre-hook')] = _list_if_none_or_string
handlers[(k, 'post-hook')] = _list_if_none_or_string
handlers[('seeds', 'column_types')] = _dict_if_none
def converter(value: Any, keypath: Tuple[Union[str, int], ...]) -> Any:
if keypath in handlers:
handler = handlers[keypath]
return handler(value)
else:
return value
return deep_map(converter, project_dict)
@classmethod
def from_project_config(
cls,
project_dict: Dict[str, Any],
packages_dict: Optional[Dict[str, Any]] = None,
selectors_dict: Optional[Dict[str, Any]] = None,
required_dbt_version: Optional[List[VersionSpecifier]] = None,
) -> 'Project':
"""Create a project from its project and package configuration, as read
by yaml.safe_load().
:param project_dict: The dictionary as read from disk
:param packages_dict: If it exists, the packages file as
read from disk.
:raises DbtProjectError: If the project is missing or invalid, or if
the packages file exists and is invalid.
:returns: The project, with defaults populated.
"""
if required_dbt_version is None:
dbt_version = cls._get_required_version(project_dict)
else:
dbt_version = required_dbt_version
try:
project_dict = cls._preprocess(project_dict)
except RecursionException:
raise DbtProjectError(
'Cycle detected: Project input has a reference to itself',
project=project_dict
)
try:
cfg = parse_project_config(project_dict)
except ValidationError as e:
raise DbtProjectError(validator_error_message(e)) from e
# name/version are required in the Project definition, so we can assume
# they are present
name = cfg.name
version = cfg.version
# this is added at project_dict parse time and should always be here
# once we see it.
if cfg.project_root is None:
raise DbtProjectError('cfg must have a project root!')
else:
project_root = cfg.project_root
# this is only optional in the sense that if it's not present, it needs
# to have been a cli argument.
profile_name = cfg.profile
# these are all the defaults
source_paths: List[str] = value_or(cfg.source_paths, ['models'])
macro_paths: List[str] = value_or(cfg.macro_paths, ['macros'])
data_paths: List[str] = value_or(cfg.data_paths, ['data'])
test_paths: List[str] = value_or(cfg.test_paths, ['test'])
analysis_paths: List[str] = value_or(cfg.analysis_paths, [])
snapshot_paths: List[str] = value_or(cfg.snapshot_paths, ['snapshots'])
all_source_paths: List[str] = _all_source_paths(
source_paths, data_paths, snapshot_paths, analysis_paths,
macro_paths
)
docs_paths: List[str] = value_or(cfg.docs_paths, all_source_paths)
asset_paths: List[str] = value_or(cfg.asset_paths, [])
target_path: str = value_or(cfg.target_path, 'target')
clean_targets: List[str] = value_or(cfg.clean_targets, [target_path])
log_path: str = value_or(cfg.log_path, 'logs')
modules_path: str = value_or(cfg.modules_path, 'dbt_modules')
# in the default case we'll populate this once we know the adapter type
# It would be nice to just pass along a Quoting here, but that would
# break many things
quoting: Dict[str, Any] = {}
if cfg.quoting is not None:
quoting = cfg.quoting.to_dict()
models: Dict[str, Any]
seeds: Dict[str, Any]
snapshots: Dict[str, Any]
sources: Dict[str, Any]
vars_value: VarProvider
if cfg.config_version == 1:
assert isinstance(cfg, ProjectV1Contract)
# extract everything named 'vars'
models = cfg.models
seeds = cfg.seeds
snapshots = cfg.snapshots
sources = {}
vars_value = V1VarProvider(
models=models, seeds=seeds, snapshots=snapshots
)
elif cfg.config_version == 2:
assert isinstance(cfg, ProjectV2Contract)
models = cfg.models
seeds = cfg.seeds
snapshots = cfg.snapshots
sources = cfg.sources
if cfg.vars is None:
vars_dict: Dict[str, Any] = {}
else:
vars_dict = cfg.vars
vars_value = V2VarProvider(vars_dict)
else:
raise ValidationError(
f'Got unsupported config_version={cfg.config_version}'
)
on_run_start: List[str] = value_or(cfg.on_run_start, [])
on_run_end: List[str] = value_or(cfg.on_run_end, [])
query_comment = _query_comment_from_cfg(cfg.query_comment)
try:
packages = package_config_from_data(packages_dict)
except ValidationError as e:
raise DbtProjectError(validator_error_message(e)) from e
try:
selectors = selector_config_from_data(selectors_dict)
except ValidationError as e:
raise DbtProjectError(validator_error_message(e)) from e
project = cls(
project_name=name,
version=version,
project_root=project_root,
profile_name=profile_name,
source_paths=source_paths,
macro_paths=macro_paths,
data_paths=data_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,
modules_path=modules_path,
quoting=quoting,
models=models,
on_run_start=on_run_start,
on_run_end=on_run_end,
seeds=seeds,
snapshots=snapshots,
dbt_version=dbt_version,
packages=packages,
selectors=selectors,
query_comment=query_comment,
sources=sources,
vars=vars_value,
config_version=cfg.config_version,
)
# sanity check - this means an internal issue
project.validate()
return project
def __str__(self):
cfg = self.to_project_config(with_packages=True)
return str(cfg)
def __eq__(self, other):
if not (isinstance(other, self.__class__) and
isinstance(self, other.__class__)):
return False
return self.to_project_config(with_packages=True) == \
other.to_project_config(with_packages=True)
def to_project_config(self, with_packages=False):
"""Return a dict representation of the config that could be written to
disk with `yaml.safe_dump` to get this configuration.
:param with_packages bool: If True, include the serialized packages
file in the root.
:returns dict: The serialized profile.
"""
result = deepcopy({
'name': self.project_name,
'version': self.version,
'project-root': self.project_root,
'profile': self.profile_name,
'source-paths': self.source_paths,
'macro-paths': self.macro_paths,
'data-paths': self.data_paths,
'test-paths': self.test_paths,
'analysis-paths': self.analysis_paths,
'docs-paths': self.docs_paths,
'asset-paths': self.asset_paths,
'target-path': self.target_path,
'snapshot-paths': self.snapshot_paths,
'clean-targets': self.clean_targets,
'log-path': self.log_path,
'quoting': self.quoting,
'models': self.models,
'on-run-start': self.on_run_start,
'on-run-end': self.on_run_end,
'seeds': self.seeds,
'snapshots': self.snapshots,
'require-dbt-version': [
v.to_version_string() for v in self.dbt_version
],
'config-version': self.config_version,
})
if self.query_comment:
result['query-comment'] = self.query_comment.to_dict()
if with_packages:
result.update(self.packages.to_dict())
if self.config_version == 2:
result.update({
'sources': self.sources,
'vars': self.vars.to_dict()
})
return result
def validate(self):
try:
ProjectV2Contract.from_dict(self.to_project_config())
except ValidationError as e:
raise DbtProjectError(validator_error_message(e)) from e
@classmethod
def _get_required_version(
cls, rendered_project: Dict[str, Any], verify_version: bool = False
) -> List[VersionSpecifier]:
dbt_raw_version: Union[List[str], str] = '>=0.0.0'
required = rendered_project.get('require-dbt-version')
if required is not None:
dbt_raw_version = required
try:
dbt_version = _parse_versions(dbt_raw_version)
except SemverException as e:
raise DbtProjectError(str(e)) from e
if verify_version:
# no name is also an error that we want to raise
if 'name' not in rendered_project:
raise DbtProjectError(
'Required "name" field not present in project',
)
validate_version(dbt_version, rendered_project['name'])
return dbt_version
@classmethod
def render_from_dict(
cls,
project_root: str,
project_dict: Dict[str, Any],
packages_dict: Dict[str, Any],
selectors_dict: Dict[str, Any],
renderer: DbtProjectYamlRenderer,
*,
verify_version: bool = False
) -> 'Project':
rendered_project = renderer.render_data(project_dict)
rendered_project['project-root'] = project_root
package_renderer = renderer.get_package_renderer()
rendered_packages = package_renderer.render_data(packages_dict)
selectors_renderer = renderer.get_selector_renderer()
rendered_selectors = selectors_renderer.render_data(selectors_dict)
try:
dbt_version = cls._get_required_version(
rendered_project, verify_version=verify_version
)
return cls.from_project_config(
rendered_project,
rendered_packages,
rendered_selectors,
dbt_version,
)
except DbtProjectError as exc:
if exc.path is None:
exc.path = os.path.join(project_root, 'dbt_project.yml')
raise
@classmethod
def partial_load(
cls, project_root: str, *, verify_version: bool = False
) -> PartialProject:
project_root = os.path.normpath(project_root)
project_dict = _raw_project_from(project_root)
project_name = project_dict.get('name')
profile_name = project_dict.get('profile')
config_version = project_dict.get('config-version', 1)
return PartialProject(
config_version=config_version,
profile_name=profile_name,
project_name=project_name,
project_root=project_root,
project_dict=project_dict,
verify_version=verify_version,
)
@classmethod
def from_project_root(
cls,
project_root: str,
renderer: DbtProjectYamlRenderer,
*,
verify_version: bool = False,
) -> 'Project':
partial = cls.partial_load(project_root, verify_version=verify_version)
renderer.version = partial.config_version
return partial.render(renderer)
def hashed_name(self):
return hashlib.md5(self.project_name.encode('utf-8')).hexdigest()
def as_v1(self, all_projects: Iterable[str]):
if self.config_version == 1:
return self
dct = self.to_project_config()
mutated = deepcopy(dct)
# remove sources, it doesn't exist
mutated.pop('sources', None)
common_config_keys = ['models', 'seeds', 'snapshots']
if 'vars' in dct and isinstance(dct['vars'], dict):
v2_vars_to_v1(mutated, dct['vars'], set(all_projects))
# ok, now we want to look through all the existing cfgkeys and mirror
# it, except expand the '+' prefix.
for cfgkey in common_config_keys:
if cfgkey not in dct:
continue
mutated[cfgkey] = _flatten_config(dct[cfgkey])
mutated['config-version'] = 1
project = Project.from_project_config(mutated)
project.packages = self.packages
return project
def get_selector(self, name: str) -> SelectionSpec:
if name not in self.selectors:
raise RuntimeException(
f'Could not find selector named {name}, expected one of '
f'{list(self.selectors)}'
)
return self.selectors[name]
def v2_vars_to_v1(
dst: Dict[str, Any], src_vars: Dict[str, Any], project_names: Set[str]
) -> None:
# stuff any 'vars' entries into the old-style
# models/seeds/snapshots dicts
common_config_keys = ['models', 'seeds', 'snapshots']
for project_name in project_names:
for cfgkey in common_config_keys:
if cfgkey not in dst:
dst[cfgkey] = {}
if project_name not in dst[cfgkey]:
dst[cfgkey][project_name] = {}
project_type_cfg = dst[cfgkey][project_name]
if 'vars' not in project_type_cfg:
project_type_cfg['vars'] = {}
project_type_vars = project_type_cfg['vars']
project_type_vars.update({
k: v for k, v in src_vars.items()
if not isinstance(v, dict)
})
items = src_vars.get(project_name, None)
if isinstance(items, dict):
project_type_vars.update(items)
# remove this from the v1 form
dst.pop('vars')
def _flatten_config(dct: Dict[str, Any]):
result = {}
for key, value in dct.items():
if isinstance(value, dict) and not key.startswith('+'):
result[key] = _flatten_config(value)
else:
if key.startswith('+'):
key = key[1:]
result[key] = value
return result

217
core/dbt/config/renderer.py Normal file
View File

@@ -0,0 +1,217 @@
from typing import Dict, Any, Tuple, Optional, Union
from dbt.clients.jinja import get_rendered, catch_jinja
from dbt.exceptions import (
DbtProjectError, CompilationException, RecursionException
)
from dbt.node_types import NodeType
from dbt.utils import deep_map
Keypath = Tuple[Union[str, int], ...]
class BaseRenderer:
def __init__(self, context: Dict[str, Any]) -> None:
self.context = context
@property
def name(self):
return 'Rendering'
def should_render_keypath(self, keypath: Keypath) -> bool:
return True
def render_entry(self, value: Any, keypath: Keypath) -> Any:
if not self.should_render_keypath(keypath):
return value
return self.render_value(value, keypath)
def render_value(
self, value: Any, keypath: Optional[Keypath] = None
) -> Any:
# keypath is ignored.
# if it wasn't read as a string, ignore it
if not isinstance(value, str):
return value
try:
with catch_jinja():
return get_rendered(value, self.context, native=True)
except CompilationException as exc:
msg = f'Could not render {value}: {exc.msg}'
raise CompilationException(msg) from exc
def render_data(
self, data: Dict[str, Any]
) -> Dict[str, Any]:
try:
return deep_map(self.render_entry, data)
except RecursionException:
raise DbtProjectError(
f'Cycle detected: {self.name} input has a reference to itself',
project=data
)
class DbtProjectYamlRenderer(BaseRenderer):
def __init__(
self, context: Dict[str, Any], version: Optional[int] = None
) -> None:
super().__init__(context)
self.version: Optional[int] = version
@property
def name(self):
'Project config'
def get_package_renderer(self) -> BaseRenderer:
return PackageRenderer(self.context)
def get_selector_renderer(self) -> BaseRenderer:
return SelectorRenderer(self.context)
def should_render_keypath_v1(self, keypath: Keypath) -> bool:
if not keypath:
return True
first = keypath[0]
# run hooks
if first in {'on-run-start', 'on-run-end', 'query-comment'}:
return False
# models have two things to avoid
if first in {'seeds', 'models', 'snapshots'}:
# model-level hooks
if 'pre-hook' in keypath or 'post-hook' in keypath:
return False
# model-level 'vars' declarations
if 'vars' in keypath:
return False
return True
def should_render_keypath_v2(self, keypath: Keypath) -> bool:
if not keypath:
return True
first = keypath[0]
# run hooks are not rendered
if first in {'on-run-start', 'on-run-end', 'query-comment'}:
return False
# don't render vars blocks until runtime
if first == 'vars':
return False
if first in {'seeds', 'models', 'snapshots', 'seeds'}:
keypath_parts = {
(k.lstrip('+') if isinstance(k, str) else k)
for k in keypath
}
# model-level hooks
if 'pre-hook' in keypath_parts or 'post-hook' in keypath_parts:
return False
return True
def should_render_keypath(self, keypath: Keypath) -> bool:
if self.version == 2:
return self.should_render_keypath_v2(keypath)
else: # could be None
return self.should_render_keypath_v1(keypath)
def render_data(
self, data: Dict[str, Any]
) -> Dict[str, Any]:
if self.version is None:
self.version = data.get('current-version')
try:
return deep_map(self.render_entry, data)
except RecursionException:
raise DbtProjectError(
f'Cycle detected: {self.name} input has a reference to itself',
project=data
)
class ProfileRenderer(BaseRenderer):
@property
def name(self):
'Profile'
class SchemaYamlRenderer(BaseRenderer):
DOCUMENTABLE_NODES = frozenset(
n.pluralize() for n in NodeType.documentable()
)
@property
def name(self):
return 'Rendering yaml'
def _is_norender_key(self, keypath: Keypath) -> bool:
"""
models:
- name: blah
- description: blah
tests: ...
- columns:
- name:
- description: blah
tests: ...
Return True if it's tests or description - those aren't rendered
"""
if len(keypath) >= 2 and keypath[1] in ('tests', 'description'):
return True
if (
len(keypath) >= 4 and
keypath[1] == 'columns' and
keypath[3] in ('tests', 'description')
):
return True
return False
# don't render descriptions or test keyword arguments
def should_render_keypath(self, keypath: Keypath) -> bool:
if len(keypath) < 2:
return True
if keypath[0] not in self.DOCUMENTABLE_NODES:
return True
if len(keypath) < 3:
return True
if keypath[0] == NodeType.Source.pluralize():
if keypath[2] == 'description':
return False
if keypath[2] == 'tables':
if self._is_norender_key(keypath[3:]):
return False
elif keypath[0] == NodeType.Macro.pluralize():
if keypath[2] == 'arguments':
if self._is_norender_key(keypath[3:]):
return False
elif self._is_norender_key(keypath[1:]):
return False
else: # keypath[0] in self.DOCUMENTABLE_NODES:
if self._is_norender_key(keypath[1:]):
return False
return True
class PackageRenderer(BaseRenderer):
@property
def name(self):
return 'Packages config'
class SelectorRenderer(BaseRenderer):
@property
def name(self):
return 'Selector config'

592
core/dbt/config/runtime.py Normal file
View File

@@ -0,0 +1,592 @@
import itertools
import os
from copy import deepcopy
from dataclasses import dataclass, fields
from pathlib import Path
from typing import (
Dict, Any, Optional, Mapping, Iterator, Iterable, Tuple, List, MutableSet,
Type
)
from .profile import Profile
from .project import Project
from .renderer import DbtProjectYamlRenderer, ProfileRenderer
from .utils import parse_cli_vars
from dbt import tracking
from dbt.adapters.factory import get_relation_class_by_name, get_include_paths
from dbt.helper_types import FQNPath, PathSet
from dbt.context.base import generate_base_context
from dbt.context.target import generate_target_context
from dbt.contracts.connection import AdapterRequiredConfig, Credentials
from dbt.contracts.graph.manifest import ManifestMetadata
from dbt.contracts.relation import ComponentName
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.ui import warning_tag
from dbt.contracts.project import Configuration, UserConfig
from dbt.exceptions import (
RuntimeException,
DbtProfileError,
DbtProjectError,
validator_error_message,
warn_or_error,
raise_compiler_error
)
from dbt.legacy_config_updater import ConfigUpdater
from hologram import ValidationError
def _project_quoting_dict(
proj: Project, profile: Profile
) -> Dict[ComponentName, bool]:
src: Dict[str, Any] = profile.credentials.translate_aliases(proj.quoting)
result: Dict[ComponentName, bool] = {}
for key in ComponentName:
if key in src:
value = src[key]
if isinstance(value, bool):
result[key] = value
return result
@dataclass
class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
args: Any
profile_name: str
cli_vars: Dict[str, Any]
dependencies: Optional[Mapping[str, 'RuntimeConfig']] = None
def __post_init__(self):
self.validate()
@classmethod
def from_parts(
cls,
project: Project,
profile: Profile,
args: Any,
dependencies: Optional[Mapping[str, 'RuntimeConfig']] = None,
) -> 'RuntimeConfig':
"""Instantiate a RuntimeConfig from its components.
:param profile: A parsed dbt Profile.
:param project: A parsed dbt Project.
:param args: The parsed command-line arguments.
:returns RuntimeConfig: The new configuration.
"""
quoting: Dict[str, Any] = (
get_relation_class_by_name(profile.credentials.type)
.get_default_quote_policy()
.replace_dict(_project_quoting_dict(project, profile))
).to_dict()
cli_vars: Dict[str, Any] = parse_cli_vars(getattr(args, 'vars', '{}'))
return cls(
project_name=project.project_name,
version=project.version,
project_root=project.project_root,
source_paths=project.source_paths,
macro_paths=project.macro_paths,
data_paths=project.data_paths,
test_paths=project.test_paths,
analysis_paths=project.analysis_paths,
docs_paths=project.docs_paths,
asset_paths=project.asset_paths,
target_path=project.target_path,
snapshot_paths=project.snapshot_paths,
clean_targets=project.clean_targets,
log_path=project.log_path,
modules_path=project.modules_path,
quoting=quoting,
models=project.models,
on_run_start=project.on_run_start,
on_run_end=project.on_run_end,
seeds=project.seeds,
snapshots=project.snapshots,
dbt_version=project.dbt_version,
packages=project.packages,
selectors=project.selectors,
query_comment=project.query_comment,
sources=project.sources,
vars=project.vars,
config_version=project.config_version,
profile_name=profile.profile_name,
target_name=profile.target_name,
config=profile.config,
threads=profile.threads,
credentials=profile.credentials,
args=args,
cli_vars=cli_vars,
dependencies=dependencies,
)
def new_project(self, project_root: str) -> 'RuntimeConfig':
"""Given a new project root, read in its project dictionary, supply the
existing project's profile info, and create a new project file.
:param project_root: A filepath to a dbt project.
:raises DbtProfileError: If the profile is invalid.
:raises DbtProjectError: If project is missing or invalid.
:returns: The new configuration.
"""
# copy profile
profile = Profile(**self.to_profile_info())
profile.validate()
# load the new project and its packages. Don't pass cli variables.
renderer = DbtProjectYamlRenderer(generate_target_context(profile, {}))
project = Project.from_project_root(
project_root,
renderer,
verify_version=getattr(self.args, 'version_check', False),
)
cfg = self.from_parts(
project=project,
profile=profile,
args=deepcopy(self.args),
)
# force our quoting back onto the new project.
cfg.quoting = deepcopy(self.quoting)
return cfg
def serialize(self) -> Dict[str, Any]:
"""Serialize the full configuration to a single dictionary. For any
instance that has passed validate() (which happens in __init__), it
matches the Configuration contract.
Note that args are not serialized.
:returns dict: The serialized configuration.
"""
result = self.to_project_config(with_packages=True)
result.update(self.to_profile_info(serialize_credentials=True))
result['cli_vars'] = deepcopy(self.cli_vars)
return result
def validate(self):
"""Validate the configuration against its contract.
:raises DbtProjectError: If the configuration fails validation.
"""
try:
Configuration.from_dict(self.serialize())
except ValidationError as e:
raise DbtProjectError(validator_error_message(e)) from e
@classmethod
def _get_rendered_profile(
cls,
args: Any,
profile_renderer: ProfileRenderer,
profile_name: Optional[str],
) -> Profile:
return Profile.render_from_args(
args, profile_renderer, profile_name
)
@classmethod
def collect_parts(
cls: Type['RuntimeConfig'], args: Any
) -> Tuple[Project, Profile]:
# profile_name from the project
project_root = args.project_dir if args.project_dir else os.getcwd()
version_check = getattr(args, 'version_check', False)
partial = Project.partial_load(
project_root,
verify_version=version_check
)
# build the profile using the base renderer and the one fact we know
cli_vars: Dict[str, Any] = parse_cli_vars(getattr(args, 'vars', '{}'))
profile_renderer = ProfileRenderer(generate_base_context(cli_vars))
profile_name = partial.render_profile_name(profile_renderer)
profile = cls._get_rendered_profile(
args, profile_renderer, profile_name
)
# get a new renderer using our target information and render the
# project
ctx = generate_target_context(profile, cli_vars)
project_renderer = DbtProjectYamlRenderer(ctx, partial.config_version)
project = partial.render(project_renderer)
return (project, profile)
@classmethod
def from_args(cls, args: Any) -> 'RuntimeConfig':
"""Given arguments, read in dbt_project.yml from the current directory,
read in packages.yml if it exists, and use them to find the profile to
load.
:param args: The arguments as parsed from the cli.
:raises DbtProjectError: If the project is invalid or missing.
:raises DbtProfileError: If the profile is invalid or missing.
:raises ValidationException: If the cli variables are invalid.
"""
project, profile = cls.collect_parts(args)
return cls.from_parts(
project=project,
profile=profile,
args=args,
)
def get_metadata(self) -> ManifestMetadata:
return ManifestMetadata(
project_id=self.hashed_name(),
adapter_type=self.credentials.type
)
def _get_v2_config_paths(
self,
config,
path: FQNPath,
paths: MutableSet[FQNPath],
) -> PathSet:
for key, value in config.items():
if isinstance(value, dict) and not key.startswith('+'):
self._get_v2_config_paths(value, path + (key,), paths)
else:
paths.add(path)
return frozenset(paths)
def _get_v1_config_paths(
self,
config: Dict[str, Any],
path: FQNPath,
paths: MutableSet[FQNPath],
) -> PathSet:
keys = ConfigUpdater(self.credentials.type).ConfigKeys
for key, value in config.items():
if isinstance(value, dict):
if key in keys:
if path not in paths:
paths.add(path)
else:
self._get_v1_config_paths(value, path + (key,), paths)
else:
if path not in paths:
paths.add(path)
return frozenset(paths)
def _get_config_paths(
self,
config: Dict[str, Any],
path: FQNPath = (),
paths: Optional[MutableSet[FQNPath]] = None,
) -> PathSet:
if paths is None:
paths = set()
if self.config_version == 2:
return self._get_v2_config_paths(config, path, paths)
else:
return self._get_v1_config_paths(config, path, paths)
def get_resource_config_paths(self) -> Dict[str, PathSet]:
"""Return a dictionary with 'seeds' and 'models' keys whose values are
lists of lists of strings, where each inner list of strings represents
a configured path in the resource.
"""
return {
'models': self._get_config_paths(self.models),
'seeds': self._get_config_paths(self.seeds),
'snapshots': self._get_config_paths(self.snapshots),
'sources': self._get_config_paths(self.sources),
}
def get_unused_resource_config_paths(
self,
resource_fqns: Mapping[str, PathSet],
disabled: PathSet,
) -> List[FQNPath]:
"""Return a list of lists of strings, where each inner list of strings
represents a type + FQN path of a resource configuration that is not
used.
"""
disabled_fqns = frozenset(tuple(fqn) for fqn in disabled)
resource_config_paths = self.get_resource_config_paths()
unused_resource_config_paths = []
for resource_type, config_paths in resource_config_paths.items():
used_fqns = resource_fqns.get(resource_type, frozenset())
fqns = used_fqns | disabled_fqns
for config_path in config_paths:
if not _is_config_used(config_path, fqns):
unused_resource_config_paths.append(
(resource_type,) + config_path
)
return unused_resource_config_paths
def warn_for_unused_resource_config_paths(
self,
resource_fqns: Mapping[str, PathSet],
disabled: PathSet,
) -> None:
unused = self.get_unused_resource_config_paths(resource_fqns, disabled)
if len(unused) == 0:
return
msg = UNUSED_RESOURCE_CONFIGURATION_PATH_MESSAGE.format(
len(unused),
'\n'.join('- {}'.format('.'.join(u)) for u in unused)
)
warn_or_error(msg, log_fmt=warning_tag('{}'))
def load_dependencies(self) -> Mapping[str, 'RuntimeConfig']:
if self.dependencies is None:
all_projects = {self.project_name: self}
internal_packages = get_include_paths(self.credentials.type)
project_paths = itertools.chain(
internal_packages,
self._get_project_directories()
)
for project_name, project in self.load_projects(project_paths):
if project_name in all_projects:
raise_compiler_error(
f'dbt found more than one package with the name '
f'"{project_name}" included in this project. Package '
f'names must be unique in a project. Please rename '
f'one of these packages.'
)
all_projects[project_name] = project
self.dependencies = all_projects
return self.dependencies
def clear_dependencies(self):
self.dependencies = None
def load_projects(
self, paths: Iterable[Path]
) -> Iterator[Tuple[str, 'RuntimeConfig']]:
for path in paths:
try:
project = self.new_project(str(path))
except DbtProjectError as e:
raise DbtProjectError(
f'Failed to read package: {e}',
result_type='invalid_project',
path=path,
) from e
else:
yield project.project_name, project
def _get_project_directories(self) -> Iterator[Path]:
root = Path(self.project_root) / self.modules_path
if root.exists():
for path in root.iterdir():
if path.is_dir() and not path.name.startswith('__'):
yield path
def as_v1(self, all_projects: Iterable[str]):
if self.config_version == 1:
return self
return self.from_parts(
project=Project.as_v1(self, all_projects),
profile=self,
args=self.args,
dependencies=self.dependencies,
)
class UnsetCredentials(Credentials):
def __init__(self):
super().__init__('', '')
@property
def type(self):
return None
def connection_info(self, *args, **kwargs):
return {}
def _connection_keys(self):
return ()
class UnsetConfig(UserConfig):
def __getattribute__(self, name):
if name in {f.name for f in fields(UserConfig)}:
raise AttributeError(
f"'UnsetConfig' object has no attribute {name}"
)
def to_dict(self):
return {}
class UnsetProfile(Profile):
def __init__(self):
self.credentials = UnsetCredentials()
self.config = UnsetConfig()
self.profile_name = ''
self.target_name = ''
self.threads = -1
def to_target_dict(self):
return {}
def __getattribute__(self, name):
if name in {'profile_name', 'target_name', 'threads'}:
raise RuntimeException(
f'Error: disallowed attribute "{name}" - no profile!'
)
return Profile.__getattribute__(self, name)
@dataclass
class UnsetProfileConfig(RuntimeConfig):
"""This class acts a lot _like_ a RuntimeConfig, except if your profile is
missing, any access to profile members results in an exception.
"""
def __post_init__(self):
# instead of futzing with InitVar overrides or rewriting __init__, just
# `del` the attrs we don't want users touching.
del self.profile_name
del self.target_name
# don't call super().__post_init__(), as that calls validate(), and
# this object isn't very valid
def __getattribute__(self, name):
# Override __getattribute__ to check that the attribute isn't 'banned'.
if name in {'profile_name', 'target_name'}:
raise RuntimeException(
f'Error: disallowed attribute "{name}" - no profile!'
)
# avoid every attribute access triggering infinite recursion
return RuntimeConfig.__getattribute__(self, name)
def to_target_dict(self):
# re-override the poisoned profile behavior
return {}
@classmethod
def from_parts(
cls,
project: Project,
profile: Profile,
args: Any,
dependencies: Optional[Mapping[str, 'RuntimeConfig']] = None,
) -> 'RuntimeConfig':
"""Instantiate a RuntimeConfig from its components.
:param profile: Ignored.
:param project: A parsed dbt Project.
:param args: The parsed command-line arguments.
:returns RuntimeConfig: The new configuration.
"""
cli_vars: Dict[str, Any] = parse_cli_vars(getattr(args, 'vars', '{}'))
return cls(
project_name=project.project_name,
version=project.version,
project_root=project.project_root,
source_paths=project.source_paths,
macro_paths=project.macro_paths,
data_paths=project.data_paths,
test_paths=project.test_paths,
analysis_paths=project.analysis_paths,
docs_paths=project.docs_paths,
asset_paths=project.asset_paths,
target_path=project.target_path,
snapshot_paths=project.snapshot_paths,
clean_targets=project.clean_targets,
log_path=project.log_path,
modules_path=project.modules_path,
quoting=project.quoting, # we never use this anyway.
models=project.models,
on_run_start=project.on_run_start,
on_run_end=project.on_run_end,
seeds=project.seeds,
snapshots=project.snapshots,
dbt_version=project.dbt_version,
packages=project.packages,
selectors=project.selectors,
query_comment=project.query_comment,
sources=project.sources,
vars=project.vars,
config_version=project.config_version,
profile_name='',
target_name='',
config=UnsetConfig(),
threads=getattr(args, 'threads', 1),
credentials=UnsetCredentials(),
args=args,
cli_vars=cli_vars,
dependencies=dependencies,
)
@classmethod
def _get_rendered_profile(
cls,
args: Any,
profile_renderer: ProfileRenderer,
profile_name: Optional[str],
) -> Profile:
try:
profile = Profile.render_from_args(
args, profile_renderer, profile_name
)
except (DbtProjectError, DbtProfileError) as exc:
logger.debug(
'Profile not loaded due to error: {}', exc, exc_info=True
)
logger.info(
'No profile "{}" found, continuing with no target',
profile_name
)
# return the poisoned form
profile = UnsetProfile()
# disable anonymous usage statistics
tracking.disable_tracking()
return profile
@classmethod
def from_args(cls: Type[RuntimeConfig], args: Any) -> 'RuntimeConfig':
"""Given arguments, read in dbt_project.yml from the current directory,
read in packages.yml if it exists, and use them to find the profile to
load.
:param args: The arguments as parsed from the cli.
:raises DbtProjectError: If the project is invalid or missing.
:raises DbtProfileError: If the profile is invalid or missing.
:raises ValidationException: If the cli variables are invalid.
"""
project, profile = cls.collect_parts(args)
if not isinstance(profile, UnsetProfile):
# if it's a real profile, return a real config
cls = RuntimeConfig
return cls.from_parts(
project=project,
profile=profile,
args=args
)
UNUSED_RESOURCE_CONFIGURATION_PATH_MESSAGE = """\
Configuration paths exist in your dbt_project.yml file which do not \
apply to any resources.
There are {} unused configuration paths:
{}
"""
def _is_config_used(path, fqns):
if fqns:
for fqn in fqns:
if len(path) <= len(fqn) and fqn[:len(path)] == path:
return True
return False

View File

@@ -0,0 +1,104 @@
from pathlib import Path
from typing import Dict, Any, Optional
from hologram import ValidationError
from .renderer import SelectorRenderer
from dbt.clients.system import (
load_file_contents,
path_exists,
resolve_path_from_base,
)
from dbt.clients.yaml_helper import load_yaml_text
from dbt.contracts.selection import SelectorFile
from dbt.exceptions import DbtSelectorsError, RuntimeException
from dbt.graph import parse_from_selectors_definition, SelectionSpec
MALFORMED_SELECTOR_ERROR = """\
The selectors.yml file in this project is malformed. Please double check
the contents of this file and fix any errors before retrying.
You can find more information on the syntax for this file here:
https://docs.getdbt.com/docs/package-management
Validator Error:
{error}
"""
class SelectorConfig(Dict[str, SelectionSpec]):
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'SelectorConfig':
try:
selector_file = SelectorFile.from_dict(data)
selectors = parse_from_selectors_definition(selector_file)
except (ValidationError, RuntimeException) as exc:
raise DbtSelectorsError(
f'Could not read selector file data: {exc}',
result_type='invalid_selector',
) from exc
return cls(selectors)
@classmethod
def render_from_dict(
cls,
data: Dict[str, Any],
renderer: SelectorRenderer,
) -> 'SelectorConfig':
try:
rendered = renderer.render_data(data)
except (ValidationError, RuntimeException) as exc:
raise DbtSelectorsError(
f'Could not render selector data: {exc}',
result_type='invalid_selector',
) from exc
return cls.from_dict(rendered)
@classmethod
def from_path(
cls, path: Path, renderer: SelectorRenderer,
) -> 'SelectorConfig':
try:
data = load_yaml_text(load_file_contents(str(path)))
except (ValidationError, RuntimeException) as exc:
raise DbtSelectorsError(
f'Could not read selector file: {exc}',
result_type='invalid_selector',
path=path,
) from exc
try:
return cls.render_from_dict(data, renderer)
except DbtSelectorsError as exc:
exc.path = path
raise
def selector_data_from_root(project_root: str) -> Dict[str, Any]:
selector_filepath = resolve_path_from_base(
'selectors.yml', project_root
)
if path_exists(selector_filepath):
selectors_dict = load_yaml_text(load_file_contents(selector_filepath))
else:
selectors_dict = None
return selectors_dict
def selector_config_from_data(
selectors_data: Optional[Dict[str, Any]]
) -> SelectorConfig:
if selectors_data is None:
selectors_data = {'selectors': []}
try:
selectors = SelectorConfig.from_dict(selectors_data)
except ValidationError as e:
raise DbtSelectorsError(
MALFORMED_SELECTOR_ERROR.format(error=str(e.message)),
result_type='invalid_selector',
) from e
return selectors

23
core/dbt/config/utils.py Normal file
View File

@@ -0,0 +1,23 @@
from typing import Dict, Any
from dbt.clients import yaml_helper
from dbt.exceptions import raise_compiler_error, ValidationException
from dbt.logger import GLOBAL_LOGGER as logger
def parse_cli_vars(var_string: str) -> Dict[str, Any]:
try:
cli_vars = yaml_helper.load_yaml_text(var_string)
var_type = type(cli_vars)
if var_type is dict:
return cli_vars
else:
type_name = var_type.__name__
raise_compiler_error(
"The --vars argument must be a YAML dictionary, but was "
"of type '{}'".format(type_name))
except ValidationException:
logger.error(
"The YAML provided in the --vars argument is not valid.\n"
)
raise

529
core/dbt/context/base.py Normal file
View File

@@ -0,0 +1,529 @@
import json
import os
from typing import (
Any, Dict, NoReturn, Optional, Mapping
)
from dbt import flags
from dbt import tracking
from dbt.clients.jinja import undefined_error, get_rendered
from dbt.clients import yaml_helper
from dbt.contracts.graph.compiled import CompiledResource
from dbt.exceptions import raise_compiler_error, MacroReturn
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.version import __version__ as dbt_version
import yaml
# These modules are added to the context. Consider alternative
# approaches which will extend well to potentially many modules
import pytz
import datetime
def get_pytz_module_context() -> Dict[str, Any]:
context_exports = pytz.__all__ # type: ignore
return {
name: getattr(pytz, name) for name in context_exports
}
def get_datetime_module_context() -> Dict[str, Any]:
context_exports = [
'date',
'datetime',
'time',
'timedelta',
'tzinfo'
]
return {
name: getattr(datetime, name) for name in context_exports
}
def get_context_modules() -> Dict[str, Dict[str, Any]]:
return {
'pytz': get_pytz_module_context(),
'datetime': get_datetime_module_context(),
}
class ContextMember:
def __init__(self, value, name=None):
self.name = name
self.inner = value
def key(self, default):
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 contextproperty(value):
if isinstance(value, str):
return lambda v: ContextMember(property(v), name=value)
return ContextMember(property(value))
class ContextMeta(type):
def __new__(mcls, name, bases, dct):
context_members = {}
context_attrs = {}
new_dct = {}
for base in bases:
context_members.update(getattr(base, '_context_members_', {}))
context_attrs.update(getattr(base, '_context_attrs_', {}))
for key, value in dct.items():
if isinstance(value, ContextMember):
context_key = value.key(key)
context_members[context_key] = value.inner
context_attrs[context_key] = key
value = value.inner
new_dct[key] = value
new_dct['_context_members_'] = context_members
new_dct['_context_attrs_'] = context_attrs
return type.__new__(mcls, name, bases, new_dct)
class Var:
UndefinedVarError = "Required var '{}' not found in config:\nVars "\
"supplied to {} = {}"
_VAR_NOTSET = object()
def __init__(
self,
context: Mapping[str, Any],
cli_vars: Mapping[str, Any],
node: Optional[CompiledResource] = None
) -> None:
self._context: Mapping[str, Any] = context
self._cli_vars: Mapping[str, Any] = cli_vars
self._node: Optional[CompiledResource] = node
self._merged: Mapping[str, Any] = self._generate_merged()
def _generate_merged(self) -> Mapping[str, Any]:
return self._cli_vars
@property
def node_name(self):
if self._node is not None:
return self._node.name
else:
return '<Configuration>'
def get_missing_var(self, var_name):
dct = {k: self._merged[k] for k in self._merged}
pretty_vars = json.dumps(dct, sort_keys=True, indent=4)
msg = self.UndefinedVarError.format(
var_name, self.node_name, pretty_vars
)
raise_compiler_error(msg, self._node)
def has_var(self, var_name: str):
return var_name in self._merged
def get_rendered_var(self, var_name):
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)
def __call__(self, var_name, default=_VAR_NOTSET):
if self.has_var(var_name):
return self.get_rendered_var(var_name)
elif default is not self._VAR_NOTSET:
return default
else:
return self.get_missing_var(var_name)
class BaseContext(metaclass=ContextMeta):
def __init__(self, cli_vars):
self._ctx = {}
self.cli_vars = cli_vars
def generate_builtins(self):
builtins: Dict[str, Any] = {}
for key, value in self._context_members_.items():
if hasattr(value, '__get__'):
# handle properties, bound methods, etc
value = value.__get__(self)
builtins[key] = value
return builtins
def to_dict(self):
self._ctx['context'] = self._ctx
builtins = self.generate_builtins()
self._ctx['builtins'] = builtins
self._ctx.update(builtins)
return self._ctx
@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
purposes.
> macros/get_version.sql
{% macro get_version() %}
{% set msg = "The installed version of dbt is: " ~ dbt_version %}
{% do log(msg, info=true) %}
{% endmacro %}
Example output:
$ dbt run-operation get_version
The installed version of dbt is 0.16.0
"""
return dbt_version
@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
for deployment in multiple environments, or defining values that should
be used across multiple models within a package.
To add a variable to a model, use the `var()` function:
> my_model.sql:
select * from events where event_type = '{{ var("event_type") }}'
If you try to run this model without supplying an `event_type`
variable, you'll receive a compilation error that looks like this:
Encountered an error:
! Compilation error while compiling model package_name.my_model:
! Required var 'event_type' not found in config:
Vars supplied to package_name.my_model = {
}
To supply a variable to a given model, add one or more `vars`
dictionaries to the `models` config in your `dbt_project.yml` file.
These `vars` are in-scope for all models at or below where they are
defined, so place them where they make the most sense. Below are three
different placements of the `vars` dict, all of which will make the
`my_model` model compile.
> dbt_project.yml:
# 1) scoped at the model level
models:
package_name:
my_model:
materialized: view
vars:
event_type: activation
# 2) scoped at the package level
models:
package_name:
vars:
event_type: activation
my_model:
materialized: view
# 3) scoped globally
models:
vars:
event_type: activation
package_name:
my_model:
materialized: view
## Variable default values
The `var()` function takes an optional second argument, `default`. If
this argument is provided, then it will be the default value for the
variable if one is not explicitly defined.
> my_model.sql:
-- Use 'activation' as the event_type if the variable is not
-- defined.
select *
from events
where event_type = '{{ var("event_type", "activation") }}'
"""
return Var(self._ctx, self.cli_vars)
@contextmember
@staticmethod
def env_var(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.
If the default is None, raise an exception for an undefined variable.
"""
if var in os.environ:
return os.environ[var]
elif default is not None:
return default
else:
msg = f"Env var required but not provided: '{var}'"
undefined_error(msg)
if os.environ.get('DBT_MACRO_DEBUGGING'):
@contextmember
@staticmethod
def debug():
"""Enter a debugger at this line in the compiled jinja code."""
import sys
import ipdb # type: ignore
frame = sys._getframe(3)
ipdb.set_trace(frame)
return ''
@contextmember('return')
@staticmethod
def _return(data: Any) -> NoReturn:
"""The `return` function can be used in macros to return data to the
caller. The type of the data (`dict`, `list`, `int`, etc) will be
preserved through the return call.
:param data: The data to return to the caller
> macros/example.sql:
{% macro get_data() %}
{{ return([1,2,3]) }}
{% endmacro %}
> models/my_model.sql:
select
-- getdata() returns a list!
{% for i in getdata() %}
{{ i }}
{% if not loop.last %},{% endif %}
{% endfor %}
"""
raise MacroReturn(data)
@contextmember
@staticmethod
def fromjson(string: str, default: Any = None) -> Any:
"""The `fromjson` context method can be used to deserialize a json
string into a Python object primitive, eg. a `dict` or `list`.
:param value: The json string to deserialize
:param default: A default value to return if the `string` argument
cannot be deserialized (optional)
Usage:
{% set my_json_str = '{"abc": 123}' %}
{% set my_dict = fromjson(my_json_str) %}
{% do log(my_dict['abc']) %}
"""
try:
return json.loads(string)
except ValueError:
return default
@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
object primitive, eg. a `dict` or `list` to a json string.
:param value: The value serialize to json
:param default: A default value to return if the `value` argument
cannot be serialized
:param sort_keys: If True, sort the keys.
Usage:
{% set my_dict = {"abc": 123} %}
{% set my_json_string = tojson(my_dict) %}
{% do log(my_json_string) %}
"""
try:
return json.dumps(value, sort_keys=sort_keys)
except ValueError:
return default
@contextmember
@staticmethod
def fromyaml(value: str, default: Any = None) -> Any:
"""The fromyaml context method can be used to deserialize a yaml string
into a Python object primitive, eg. a `dict` or `list`.
:param value: The yaml string to deserialize
:param default: A default value to return if the `string` argument
cannot be deserialized (optional)
Usage:
{% set my_yml_str -%}
dogs:
- good
- bad
{%- endset %}
{% set my_dict = fromyaml(my_yml_str) %}
{% do log(my_dict['dogs'], info=true) %}
-- ["good", "bad"]
{% do my_dict['dogs'].pop() }
{% do log(my_dict['dogs'], info=true) %}
-- ["good"]
"""
try:
return yaml_helper.safe_load(value)
except (AttributeError, ValueError, yaml.YAMLError):
return default
# safe_dump defaults to sort_keys=True, but we act like json.dumps (the
# opposite)
@contextmember
@staticmethod
def toyaml(
value: Any, default: Optional[str] = None, sort_keys: bool = False
) -> Optional[str]:
"""The `tojson` context method can be used to serialize a Python
object primitive, eg. a `dict` or `list` to a yaml string.
:param value: The value serialize to yaml
:param default: A default value to return if the `value` argument
cannot be serialized
:param sort_keys: If True, sort the keys.
Usage:
{% set my_dict = {"abc": 123} %}
{% set my_yaml_string = toyaml(my_dict) %}
{% do log(my_yaml_string) %}
"""
try:
return yaml.safe_dump(data=value, sort_keys=sort_keys)
except (ValueError, yaml.YAMLError):
return default
@contextmember
@staticmethod
def log(msg: str, info: bool = False) -> str:
"""Logs a line to either the log file or stdout.
:param msg: The message to log
:param info: If `False`, write to the log file. If `True`, write to
both the log file and stdout.
> macros/my_log_macro.sql
{% macro some_macro(arg1, arg2) %}
{{ log("Running some_macro: " ~ arg1 ~ ", " ~ arg2) }}
{% endmacro %}"
"""
if info:
logger.info(msg)
else:
logger.debug(msg)
return ''
@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
`datetime` object. As of 0.9.1, the timezone of this variable defaults
to UTC.
> run_started_at_example.sql
select
'{{ run_started_at.strftime("%Y-%m-%d") }}' as date_day
from ...
To modify the timezone of this variable, use the the `pytz` module:
> run_started_at_utc.sql
{% set est = modules.pytz.timezone("America/New_York") %}
select
'{{ run_started_at.astimezone(est) }}' as run_started_est
from ...
"""
if tracking.active_user is not None:
return tracking.active_user.run_started_at
else:
return None
@contextproperty
def invocation_id(self) -> Optional[str]:
"""invocation_id outputs a UUID generated for this dbt run (useful for
auditing)
"""
if tracking.active_user is not None:
return tracking.active_user.invocation_id
else:
return None
@contextproperty
def modules(self) -> Dict[str, Any]:
"""The `modules` variable in the Jinja context contains useful Python
modules for operating on data.
# datetime
This variable is a pointer to the Python datetime module.
Usage:
{% set dt = modules.datetime.datetime.now() %}
# pytz
This variable is a pointer to the Python pytz module.
Usage:
{% set dt = modules.datetime.datetime(2002, 10, 27, 6, 0, 0) %}
{% set dt_local = modules.pytz.timezone('US/Eastern').localize(dt) %}
{{ dt_local }}
""" # noqa
return get_context_modules()
@contextproperty
def flags(self) -> Any:
"""The `flags` variable contains true/false values for flags provided
on the command line.
> flags.sql:
{% if flags.FULL_REFRESH %}
drop table ...
{% else %}
-- no-op
{% endif %}
The list of valid flags are:
- `flags.STRICT_MODE`: True if `--strict` (or `-S`) was provided on the
command line
- `flags.FULL_REFRESH`: True if `--full-refresh` was provided on the
command line
- `flags.NON_DESTRUCTIVE`: True if `--non-destructive` was provided on
the command line
"""
return flags
def generate_base_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]:
ctx = BaseContext(cli_vars)
return ctx.to_dict()

View File

@@ -0,0 +1,83 @@
from typing import Any, Dict
from dbt.contracts.connection import AdapterRequiredConfig
from dbt.node_types import NodeType
from dbt.utils import MultiDict
from dbt.context.base import contextproperty, Var
from dbt.context.target import TargetContext
class ConfiguredContext(TargetContext):
config: AdapterRequiredConfig
def __init__(
self, config: AdapterRequiredConfig
) -> None:
super().__init__(config, config.cli_vars)
@contextproperty
def project_name(self) -> str:
return self.config.project_name
class FQNLookup:
def __init__(self, package_name: str):
self.package_name = package_name
self.fqn = [package_name]
self.resource_type = NodeType.Model
class ConfiguredVar(Var):
def __init__(
self,
context: Dict[str, Any],
config: AdapterRequiredConfig,
project_name: str,
):
super().__init__(context, config.cli_vars)
self._config = config
self._project_name = project_name
def __call__(self, var_name, default=Var._VAR_NOTSET):
my_config = self._config.load_dependencies()[self._project_name]
# cli vars > active project > local project
if var_name in self._config.cli_vars:
return self._config.cli_vars[var_name]
if self._config.config_version == 2 and my_config.config_version == 2:
adapter_type = self._config.credentials.type
lookup = FQNLookup(self._project_name)
active_vars = self._config.vars.vars_for(lookup, adapter_type)
all_vars = MultiDict([active_vars])
if self._config.project_name != my_config.project_name:
all_vars.add(my_config.vars.vars_for(lookup, adapter_type))
if var_name in all_vars:
return all_vars[var_name]
if default is not Var._VAR_NOTSET:
return default
return self.get_missing_var(var_name)
class SchemaYamlContext(ConfiguredContext):
def __init__(self, config, project_name: str):
super().__init__(config)
self._project_name = project_name
@contextproperty
def var(self) -> ConfiguredVar:
return ConfiguredVar(
self._ctx, self.config, self._project_name
)
def generate_schema_yml(
config: AdapterRequiredConfig, project_name: str
) -> Dict[str, Any]:
ctx = SchemaYamlContext(config, project_name)
return ctx.to_dict()

View File

@@ -0,0 +1,204 @@
from copy import deepcopy
from dataclasses import dataclass
from typing import List, Iterator, Dict, Any, TypeVar, Union
from dbt.config import RuntimeConfig, Project
from dbt.contracts.graph.model_config import BaseConfig, get_config_for
from dbt.exceptions import InternalException
from dbt.legacy_config_updater import ConfigUpdater, IsFQNResource
from dbt.node_types import NodeType
from dbt.utils import fqn_search
@dataclass
class ModelParts(IsFQNResource):
fqn: List[str]
resource_type: NodeType
package_name: str
class LegacyContextConfig:
def __init__(
self,
active_project: RuntimeConfig,
own_project: Project,
fqn: List[str],
node_type: NodeType,
):
self._config = None
self._active_project: RuntimeConfig = active_project
self._own_project: Project = own_project
self._model = ModelParts(
fqn=fqn,
resource_type=node_type,
package_name=self._own_project.project_name,
)
self._updater = ConfigUpdater(active_project.credentials.type)
# the config options defined within the model
self.in_model_config: Dict[str, Any] = {}
def get_default(self) -> Dict[str, Any]:
defaults = {"enabled": True, "materialized": "view"}
if self._model.resource_type == NodeType.Seed:
defaults['materialized'] = 'seed'
elif self._model.resource_type == NodeType.Snapshot:
defaults['materialized'] = 'snapshot'
if self._model.resource_type == NodeType.Test:
defaults['severity'] = 'ERROR'
return defaults
def build_config_dict(self, base: bool = False) -> Dict[str, Any]:
defaults = self.get_default()
active_config = self.load_config_from_active_project()
if self._active_project.project_name == self._own_project.project_name:
cfg = self._updater.merge(
defaults, active_config, self.in_model_config
)
else:
own_config = self.load_config_from_own_project()
cfg = self._updater.merge(
defaults, own_config, self.in_model_config, active_config
)
return cfg
def _translate_adapter_aliases(self, config: Dict[str, Any]):
return self._active_project.credentials.translate_aliases(config)
def update_in_model_config(self, config: Dict[str, Any]) -> None:
config = self._translate_adapter_aliases(config)
self._updater.update_into(self.in_model_config, config)
def load_config_from_own_project(self) -> Dict[str, Any]:
return self._updater.get_project_config(self._model, self._own_project)
def load_config_from_active_project(self) -> Dict[str, Any]:
return self._updater.get_project_config(
self._model,
self._active_project,
)
T = TypeVar('T', bound=BaseConfig)
class ContextConfigGenerator:
def __init__(self, active_project: RuntimeConfig):
self._active_project = active_project
def get_node_project(self, project_name: str):
if project_name == self._active_project.project_name:
return self._active_project
dependencies = self._active_project.load_dependencies()
if project_name not in dependencies:
raise InternalException(
f'Project name {project_name} not found in dependencies '
f'(found {list(dependencies)})'
)
return dependencies[project_name]
def _project_configs(
self, project: Project, fqn: List[str], resource_type: NodeType
) -> Iterator[Dict[str, Any]]:
if resource_type == NodeType.Seed:
model_configs = project.seeds
elif resource_type == NodeType.Snapshot:
model_configs = project.snapshots
elif resource_type == NodeType.Source:
model_configs = project.sources
else:
model_configs = project.models
for level_config in fqn_search(model_configs, fqn):
result = {}
for key, value in level_config.items():
if key.startswith('+'):
result[key[1:]] = deepcopy(value)
elif not isinstance(value, dict):
result[key] = deepcopy(value)
yield result
def _active_project_configs(
self, fqn: List[str], resource_type: NodeType
) -> Iterator[Dict[str, Any]]:
return self._project_configs(self._active_project, fqn, resource_type)
def _update_from_config(
self, result: T, partial: Dict[str, Any], validate: bool = False
) -> T:
translated = self._active_project.credentials.translate_aliases(
partial
)
return result.update_from(
translated,
self._active_project.credentials.type,
validate=validate
)
def calculate_node_config(
self,
config_calls: List[Dict[str, Any]],
fqn: List[str],
resource_type: NodeType,
project_name: str,
base: bool,
) -> BaseConfig:
own_config = self.get_node_project(project_name)
# defaults, own_config, config calls, active_config (if != own_config)
config_cls = get_config_for(resource_type, base=base)
# Calculate the defaults. We don't want to validate the defaults,
# because it might be invalid in the case of required config members
# (such as on snapshots!)
result = config_cls.from_dict({}, validate=False)
project_configs = self._project_configs(own_config, fqn, resource_type)
for fqn_config in project_configs:
result = self._update_from_config(result, fqn_config)
for config_call in config_calls:
result = self._update_from_config(result, config_call)
if own_config.project_name != self._active_project.project_name:
for fqn_config in self._active_project_configs(fqn, resource_type):
result = self._update_from_config(result, fqn_config)
# this is mostly impactful in the snapshot config case
return result.finalize_and_validate()
class ContextConfig:
def __init__(
self,
active_project: RuntimeConfig,
fqn: List[str],
resource_type: NodeType,
project_name: str,
) -> None:
self._config_calls: List[Dict[str, Any]] = []
self._cfg_source = ContextConfigGenerator(active_project)
self._fqn = fqn
self._resource_type = resource_type
self._project_name = project_name
def update_in_model_config(self, opts: Dict[str, Any]) -> None:
self._config_calls.append(opts)
def build_config_dict(self, base: bool = False) -> Dict[str, Any]:
return self._cfg_source.calculate_node_config(
config_calls=self._config_calls,
fqn=self._fqn,
resource_type=self._resource_type,
project_name=self._project_name,
base=base,
).to_dict()
ContextConfigType = Union[LegacyContextConfig, ContextConfig]

80
core/dbt/context/docs.py Normal file
View File

@@ -0,0 +1,80 @@
from typing import (
Any, Dict, Union
)
from dbt.exceptions import (
doc_invalid_args,
doc_target_not_found,
)
from dbt.config.runtime import RuntimeConfig
from dbt.contracts.graph.compiled import CompileResultNode
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.graph.parsed import ParsedMacro
from dbt.context.base import contextmember
from dbt.context.configured import SchemaYamlContext
class DocsRuntimeContext(SchemaYamlContext):
def __init__(
self,
config: RuntimeConfig,
node: Union[ParsedMacro, CompileResultNode],
manifest: Manifest,
current_project: str,
) -> None:
super().__init__(config, current_project)
self.node = node
self.manifest = manifest
@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,
consult the Documentation guide.
> orders.md:
{% docs orders %}
# docs
- go
- here
{% enddocs %}
> schema.yml
version: 2
models:
- name: orders
description: "{{ doc('orders') }}"
"""
# when you call doc(), this is what happens at runtime
if len(args) == 1:
doc_package_name = None
doc_name = args[0]
elif len(args) == 2:
doc_package_name, doc_name = args
else:
doc_invalid_args(self.node, args)
target_doc = self.manifest.resolve_doc(
doc_name,
doc_package_name,
self._project_name,
self.node.package_name,
)
if target_doc is None:
doc_target_not_found(self.node, doc_name, doc_package_name)
return target_doc.block_contents
def generate_runtime_docs(
config: RuntimeConfig,
target: Any,
manifest: Manifest,
current_project: str,
) -> Dict[str, Any]:
ctx = DocsRuntimeContext(config, target, manifest, current_project)
return ctx.to_dict()

153
core/dbt/context/macros.py Normal file
View File

@@ -0,0 +1,153 @@
from typing import (
Any, Dict, Iterable, Union, Optional, List, Iterator, Mapping, Set
)
from dbt.clients.jinja import MacroGenerator, MacroStack
from dbt.contracts.graph.parsed import ParsedMacro
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
from dbt.exceptions import (
raise_duplicate_macro_name, raise_compiler_error
)
FlatNamespace = Dict[str, MacroGenerator]
NamespaceMember = Union[FlatNamespace, MacroGenerator]
FullNamespace = Dict[str, NamespaceMember]
class MacroNamespace(Mapping):
def __init__(
self,
global_namespace: FlatNamespace,
local_namespace: FlatNamespace,
global_project_namespace: FlatNamespace,
packages: Dict[str, FlatNamespace],
):
self.global_namespace: FlatNamespace = global_namespace
self.local_namespace: FlatNamespace = local_namespace
self.packages: Dict[str, FlatNamespace] = packages
self.global_project_namespace: FlatNamespace = global_project_namespace
def _search_order(self) -> Iterable[Union[FullNamespace, FlatNamespace]]:
yield self.local_namespace
yield self.global_namespace
yield self.packages
yield {
GLOBAL_PROJECT_NAME: self.global_project_namespace,
}
yield self.global_project_namespace
def _keys(self) -> Set[str]:
keys: Set[str] = set()
for search in self._search_order():
keys.update(search)
return keys
def __iter__(self) -> Iterator[str]:
for key in self._keys():
yield key
def __len__(self):
return len(self._keys())
def __getitem__(self, key: str) -> NamespaceMember:
for dct in self._search_order():
if key in dct:
return dct[key]
raise KeyError(key)
def get_from_package(
self, package_name: Optional[str], name: str
) -> Optional[MacroGenerator]:
pkg: FlatNamespace
if package_name is None:
return self.get(name)
elif package_name == GLOBAL_PROJECT_NAME:
return self.global_project_namespace.get(name)
elif package_name in self.packages:
return self.packages[package_name].get(name)
else:
raise_compiler_error(
f"Could not find package '{package_name}'"
)
class MacroNamespaceBuilder:
def __init__(
self,
root_package: str,
search_package: str,
thread_ctx: MacroStack,
internal_packages: List[str],
node: Optional[Any] = None,
) -> None:
self.root_package = root_package
self.search_package = search_package
self.internal_package_names = set(internal_packages)
self.internal_package_names_order = internal_packages
self.globals: FlatNamespace = {}
self.locals: FlatNamespace = {}
self.internal_packages: Dict[str, FlatNamespace] = {}
self.packages: Dict[str, FlatNamespace] = {}
self.thread_ctx = thread_ctx
self.node = node
def _add_macro_to(
self,
heirarchy: Dict[str, FlatNamespace],
macro: ParsedMacro,
macro_func: MacroGenerator,
):
if macro.package_name in heirarchy:
namespace = heirarchy[macro.package_name]
else:
namespace = {}
heirarchy[macro.package_name] = namespace
if macro.name in namespace:
raise_duplicate_macro_name(
macro_func.macro, macro, macro.package_name
)
heirarchy[macro.package_name][macro.name] = macro_func
def add_macro(self, macro: ParsedMacro, ctx: Dict[str, Any]):
macro_name: str = macro.name
macro_func: MacroGenerator = MacroGenerator(
macro, ctx, self.node, self.thread_ctx
)
# internal macros (from plugins) will be processed separately from
# project macros, so store them in a different place
if macro.package_name in self.internal_package_names:
self._add_macro_to(self.internal_packages, macro, macro_func)
else:
self._add_macro_to(self.packages, macro, macro_func)
if macro.package_name == self.search_package:
self.locals[macro_name] = macro_func
elif macro.package_name == self.root_package:
self.globals[macro_name] = macro_func
def add_macros(self, macros: Iterable[ParsedMacro], ctx: Dict[str, Any]):
for macro in macros:
self.add_macro(macro, ctx)
def build_namespace(
self, macros: Iterable[ParsedMacro], ctx: Dict[str, Any]
) -> MacroNamespace:
self.add_macros(macros, ctx)
# Iterate in reverse-order and overwrite: the packages that are first
# in the list are the ones we want to "win".
global_project_namespace: FlatNamespace = {}
for pkg in reversed(self.internal_package_names_order):
if pkg in self.internal_packages:
global_project_namespace.update(self.internal_packages[pkg])
return MacroNamespace(
global_namespace=self.globals,
local_namespace=self.locals,
global_project_namespace=global_project_namespace,
packages=self.packages,
)

View File

@@ -0,0 +1,66 @@
from typing import List
from dbt.clients.jinja import MacroStack
from dbt.contracts.connection import AdapterRequiredConfig
from dbt.contracts.graph.manifest import Manifest
from .configured import ConfiguredContext
from .macros import MacroNamespaceBuilder
class ManifestContext(ConfiguredContext):
"""The Macro context has everything in the target context, plus the macros
in the manifest.
The given macros can override any previous context values, which will be
available as if they were accessed relative to the package name.
"""
def __init__(
self,
config: AdapterRequiredConfig,
manifest: Manifest,
search_package: str,
) -> None:
super().__init__(config)
self.manifest = manifest
self.search_package = search_package
self.macro_stack = MacroStack()
builder = self._get_namespace_builder()
self.namespace = builder.build_namespace(
self.manifest.macros.values(),
self._ctx,
)
def _get_namespace_builder(self) -> MacroNamespaceBuilder:
# avoid an import loop
from dbt.adapters.factory import get_adapter_package_names
internal_packages: List[str] = get_adapter_package_names(
self.config.credentials.type
)
return MacroNamespaceBuilder(
self.config.project_name,
self.search_package,
self.macro_stack,
internal_packages,
None,
)
def to_dict(self):
dct = super().to_dict()
dct.update(self.namespace)
return dct
class QueryHeaderContext(ManifestContext):
def __init__(
self, config: AdapterRequiredConfig, manifest: Manifest
) -> None:
super().__init__(config, manifest, config.project_name)
def generate_query_header_context(
config: AdapterRequiredConfig, manifest: Manifest
):
ctx = QueryHeaderContext(config, manifest)
return ctx.to_dict()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
from typing import Any, Dict
from dbt.contracts.connection import HasCredentials
from dbt.context.base import (
BaseContext, contextproperty
)
class TargetContext(BaseContext):
def __init__(self, config: HasCredentials, cli_vars: Dict[str, Any]):
super().__init__(cli_vars=cli_vars)
self.config = config
@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
adapters, while others are adapter-specific.
Common:
|----------|-----------|------------------------------------------|
| Variable | Example | Description |
|----------|-----------|------------------------------------------|
| name | dev | Name of the active target |
|----------|-----------|------------------------------------------|
| schema | dbt_alice | Name of the dbt schema (or, dataset on |
| | | BigQuery) |
|----------|-----------|------------------------------------------|
| type | postgres | The active adapter being used. |
|----------|-----------|------------------------------------------|
| threads | 4 | The number of threads in use by dbt |
|----------|-----------|------------------------------------------|
Snowflake:
|----------|-----------|------------------------------------------|
| Variable | Example | Description |
|----------|-----------|------------------------------------------|
| database | RAW | The active target's database. |
|----------|-----------|------------------------------------------|
| warehouse| TRANSFORM | The active target's warehouse. |
|----------|-----------|------------------------------------------|
| user | USERNAME | The active target's user |
|----------|-----------|------------------------------------------|
| role | ROLENAME | The active target's role |
|----------|-----------|------------------------------------------|
| account | abc123 | The active target's account |
|----------|-----------|------------------------------------------|
Postgres/Redshift:
|----------|-------------------|----------------------------------|
| Variable | Example | Description |
|----------|-------------------|----------------------------------|
| dbname | analytics | The active target's database. |
|----------|-------------------|----------------------------------|
| host | abc123.us-west-2. | The active target's host. |
| | redshift.amazonaws| |
| | .com | |
|----------|-------------------|----------------------------------|
| user | dbt_user | The active target's user |
|----------|-------------------|----------------------------------|
| port | 5439 | The active target's port |
|----------|-------------------|----------------------------------|
BigQuery:
|----------|-----------|------------------------------------------|
| Variable | Example | Description |
|----------|-----------|------------------------------------------|
| project | abc-123 | The active target's project. |
|----------|-----------|------------------------------------------|
"""
return self.config.to_target_dict()
def generate_target_context(
config: HasCredentials, cli_vars: Dict[str, Any]
) -> Dict[str, Any]:
ctx = TargetContext(config, cli_vars)
return ctx.to_dict()

View File

@@ -0,0 +1,217 @@
import abc
import itertools
from dataclasses import dataclass, field
from typing import (
Any, ClassVar, Dict, Tuple, Iterable, Optional, NewType, List, Callable,
)
from typing_extensions import Protocol
from hologram import JsonSchemaMixin
from hologram.helpers import (
StrEnum, register_pattern, ExtensibleJsonSchemaMixin
)
from dbt.contracts.util import Replaceable
from dbt.exceptions import InternalException
from dbt.utils import translate_aliases
from dbt.logger import GLOBAL_LOGGER as logger
Identifier = NewType('Identifier', str)
register_pattern(Identifier, r'^[A-Za-z_][A-Za-z0-9_]+$')
class ConnectionState(StrEnum):
INIT = 'init'
OPEN = 'open'
CLOSED = 'closed'
FAIL = 'fail'
@dataclass(init=False)
class Connection(ExtensibleJsonSchemaMixin, Replaceable):
type: Identifier
name: Optional[str]
state: ConnectionState = ConnectionState.INIT
transaction_open: bool = False
# prevent serialization
_handle: Optional[Any] = None
_credentials: JsonSchemaMixin = field(init=False)
def __init__(
self,
type: Identifier,
name: Optional[str],
credentials: JsonSchemaMixin,
state: ConnectionState = ConnectionState.INIT,
transaction_open: bool = False,
handle: Optional[Any] = None,
) -> None:
self.type = type
self.name = name
self.state = state
self.credentials = credentials
self.transaction_open = transaction_open
self.handle = handle
@property
def credentials(self):
return self._credentials
@credentials.setter
def credentials(self, value):
self._credentials = value
@property
def handle(self):
if isinstance(self._handle, LazyHandle):
try:
# this will actually change 'self._handle'.
self._handle.resolve(self)
except RecursionError as exc:
raise InternalException(
"A connection's open() method attempted to read the "
"handle value"
) from exc
return self._handle
@handle.setter
def handle(self, value):
self._handle = value
class LazyHandle:
"""Opener must be a callable that takes a Connection object and opens the
connection, updating the handle on the Connection.
"""
def __init__(self, opener: Callable[[Connection], Connection]):
self.opener = opener
def resolve(self, connection: Connection) -> Connection:
logger.debug(
'Opening a new connection, currently in state {}'
.format(connection.state)
)
return self.opener(connection)
# see https://github.com/python/mypy/issues/4717#issuecomment-373932080
# and https://github.com/python/mypy/issues/5374
# for why we have type: ignore. Maybe someday dataclasses + abstract classes
# will work.
@dataclass # type: ignore
class Credentials(
ExtensibleJsonSchemaMixin,
Replaceable,
metaclass=abc.ABCMeta
):
database: str
schema: str
_ALIASES: ClassVar[Dict[str, str]] = field(default={}, init=False)
@abc.abstractproperty
def type(self) -> str:
raise NotImplementedError(
'type not implemented for base credentials class'
)
def connection_info(
self, *, with_aliases: bool = False
) -> Iterable[Tuple[str, Any]]:
"""Return an ordered iterator of key/value pairs for pretty-printing.
"""
as_dict = self.to_dict(omit_none=False, with_aliases=with_aliases)
connection_keys = set(self._connection_keys())
aliases: List[str] = []
if with_aliases:
aliases = [
k for k, v in self._ALIASES.items() if v in connection_keys
]
for key in itertools.chain(self._connection_keys(), aliases):
if key in as_dict:
yield key, as_dict[key]
@abc.abstractmethod
def _connection_keys(self) -> Tuple[str, ...]:
raise NotImplementedError
@classmethod
def from_dict(cls, data):
data = cls.translate_aliases(data)
return super().from_dict(data)
@classmethod
def translate_aliases(
cls, kwargs: Dict[str, Any], recurse: bool = False
) -> Dict[str, Any]:
return translate_aliases(kwargs, cls._ALIASES, recurse)
def to_dict(self, omit_none=True, validate=False, *, with_aliases=False):
serialized = super().to_dict(omit_none=omit_none, validate=validate)
if with_aliases:
serialized.update({
new_name: serialized[canonical_name]
for new_name, canonical_name in self._ALIASES.items()
if canonical_name in serialized
})
return serialized
class UserConfigContract(Protocol):
send_anonymous_usage_stats: bool
use_colors: Optional[bool]
partial_parse: Optional[bool]
printer_width: Optional[int]
def set_values(self, cookie_dir: str) -> None:
...
def to_dict(
self, omit_none: bool = True, validate: bool = False
) -> Dict[str, Any]:
...
class HasCredentials(Protocol):
credentials: Credentials
profile_name: str
config: UserConfigContract
target_name: str
threads: int
def to_target_dict(self):
raise NotImplementedError('to_target_dict not implemented')
DEFAULT_QUERY_COMMENT = '''
{%- set comment_dict = {} -%}
{%- do comment_dict.update(
app='dbt',
dbt_version=dbt_version,
profile_name=target.get('profile_name'),
target_name=target.get('target_name'),
) -%}
{%- if node is not none -%}
{%- do comment_dict.update(
node_id=node.unique_id,
) -%}
{% else %}
{# in the node context, the connection name is the node_id #}
{%- do comment_dict.update(connection_name=connection_name) -%}
{%- endif -%}
{{ return(tojson(comment_dict)) }}
'''
@dataclass
class QueryComment(JsonSchemaMixin):
comment: str = DEFAULT_QUERY_COMMENT
append: bool = False
class AdapterRequiredConfig(HasCredentials, Protocol):
project_name: str
query_comment: QueryComment
cli_vars: Dict[str, Any]
target_path: str

167
core/dbt/contracts/files.py Normal file
View File

@@ -0,0 +1,167 @@
import hashlib
import os
from dataclasses import dataclass, field
from typing import List, Optional, Union
from hologram import JsonSchemaMixin
from dbt.exceptions import InternalException
from .util import MacroKey, SourceKey
MAXIMUM_SEED_SIZE = 1 * 1024 * 1024
MAXIMUM_SEED_SIZE_NAME = '1MB'
@dataclass
class FilePath(JsonSchemaMixin):
searched_path: str
relative_path: str
project_root: str
@property
def search_key(self) -> str:
# TODO: should this be project name + path relative to project root?
return self.absolute_path
@property
def full_path(self) -> str:
# useful for symlink preservation
return os.path.join(
self.project_root, self.searched_path, self.relative_path
)
@property
def absolute_path(self) -> str:
return os.path.abspath(self.full_path)
@property
def original_file_path(self) -> str:
# this is mostly used for reporting errors. It doesn't show the project
# name, should it?
return os.path.join(
self.searched_path, self.relative_path
)
def seed_too_large(self) -> bool:
"""Return whether the file this represents is over the seed size limit
"""
return os.stat(self.full_path).st_size > MAXIMUM_SEED_SIZE
@dataclass
class FileHash(JsonSchemaMixin):
name: str # the hash type name
checksum: str # the hashlib.hash_type().hexdigest() of the file contents
@classmethod
def empty(cls):
return FileHash(name='none', checksum='')
@classmethod
def path(cls, path: str):
return FileHash(name='path', checksum=path)
def __eq__(self, other):
if not isinstance(other, FileHash):
return NotImplemented
if self.name == 'none' or self.name != other.name:
return False
return self.checksum == other.checksum
def compare(self, contents: str) -> bool:
"""Compare the file contents with the given hash"""
if self.name == 'none':
return False
return self.from_contents(contents, name=self.name) == self.checksum
@classmethod
def from_contents(cls, contents: str, name='sha256') -> 'FileHash':
"""Create a file hash from the given file contents. The hash is always
the utf-8 encoding of the contents given, because dbt only reads files
as utf-8.
"""
data = contents.encode('utf-8')
checksum = hashlib.new(name, data).hexdigest()
return cls(name=name, checksum=checksum)
@dataclass
class RemoteFile(JsonSchemaMixin):
@property
def searched_path(self) -> str:
return 'from remote system'
@property
def relative_path(self) -> str:
return 'from remote system'
@property
def absolute_path(self) -> str:
return 'from remote system'
@property
def original_file_path(self):
return 'from remote system'
@dataclass
class SourceFile(JsonSchemaMixin):
"""Define a source file in dbt"""
path: Union[FilePath, RemoteFile] # the path information
checksum: FileHash
# we don't want to serialize this
_contents: Optional[str] = None
# the unique IDs contained in this file
nodes: List[str] = field(default_factory=list)
docs: List[str] = field(default_factory=list)
macros: List[str] = field(default_factory=list)
sources: List[str] = field(default_factory=list)
exposures: List[str] = field(default_factory=list)
# any node patches in this file. The entries are names, not unique ids!
patches: List[str] = field(default_factory=list)
# any macro patches in this file. The entries are package, name pairs.
macro_patches: List[MacroKey] = field(default_factory=list)
# any source patches in this file. The entries are package, name pairs
source_patches: List[SourceKey] = field(default_factory=list)
@property
def search_key(self) -> Optional[str]:
if isinstance(self.path, RemoteFile):
return None
if self.checksum.name == 'none':
return None
return self.path.search_key
@property
def contents(self) -> str:
if self._contents is None:
raise InternalException('SourceFile has no contents!')
return self._contents
@contents.setter
def contents(self, value):
self._contents = value
@classmethod
def empty(cls, path: FilePath) -> 'SourceFile':
self = cls(path=path, checksum=FileHash.empty())
self.contents = ''
return self
@classmethod
def big_seed(cls, path: FilePath) -> 'SourceFile':
"""Parse seeds over the size limit with just the path"""
self = cls(path=path, checksum=FileHash.path(path.absolute_path))
self.contents = ''
return self
@classmethod
def remote(cls, contents: str) -> 'SourceFile':
self = cls(path=RemoteFile(), checksum=FileHash.empty())
self.contents = contents
return self

View File

@@ -0,0 +1,223 @@
from dbt.contracts.graph.parsed import (
HasTestMetadata,
ParsedNode,
ParsedAnalysisNode,
ParsedDataTestNode,
ParsedHookNode,
ParsedModelNode,
ParsedExposure,
ParsedResource,
ParsedRPCNode,
ParsedSchemaTestNode,
ParsedSeedNode,
ParsedSnapshotNode,
ParsedSourceDefinition,
SeedConfig,
TestConfig,
same_seeds,
)
from dbt.node_types import NodeType
from dbt.contracts.util import Replaceable
from hologram import JsonSchemaMixin
from dataclasses import dataclass, field
from typing import Optional, List, Union, Dict, Type
@dataclass
class InjectedCTE(JsonSchemaMixin, Replaceable):
id: str
sql: str
@dataclass
class CompiledNodeMixin(JsonSchemaMixin):
# this is a special mixin class to provide a required argument. If a node
# is missing a `compiled` flag entirely, it must not be a CompiledNode.
compiled: bool
@dataclass
class CompiledNode(ParsedNode, CompiledNodeMixin):
compiled_sql: Optional[str] = None
extra_ctes_injected: bool = False
extra_ctes: List[InjectedCTE] = field(default_factory=list)
injected_sql: Optional[str] = None
def set_cte(self, cte_id: str, sql: str):
"""This is the equivalent of what self.extra_ctes[cte_id] = sql would
do if extra_ctes were an OrderedDict
"""
for cte in self.extra_ctes:
if cte.id == cte_id:
cte.sql = sql
break
else:
self.extra_ctes.append(InjectedCTE(id=cte_id, sql=sql))
@dataclass
class CompiledAnalysisNode(CompiledNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Analysis]})
@dataclass
class CompiledHookNode(CompiledNode):
resource_type: NodeType = field(
metadata={'restrict': [NodeType.Operation]}
)
index: Optional[int] = None
@dataclass
class CompiledModelNode(CompiledNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Model]})
@dataclass
class CompiledRPCNode(CompiledNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.RPCCall]})
@dataclass
class CompiledSeedNode(CompiledNode):
# keep this in sync with ParsedSeedNode!
resource_type: NodeType = field(metadata={'restrict': [NodeType.Seed]})
config: SeedConfig = field(default_factory=SeedConfig)
@property
def empty(self):
""" Seeds are never empty"""
return False
def same_body(self, other) -> bool:
return same_seeds(self, other)
@dataclass
class CompiledSnapshotNode(CompiledNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Snapshot]})
@dataclass
class CompiledDataTestNode(CompiledNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Test]})
config: TestConfig = field(default_factory=TestConfig)
@dataclass
class CompiledSchemaTestNode(CompiledNode, HasTestMetadata):
# keep this in sync with ParsedSchemaTestNode!
resource_type: NodeType = field(metadata={'restrict': [NodeType.Test]})
column_name: Optional[str] = None
config: TestConfig = field(default_factory=TestConfig)
def same_config(self, other) -> bool:
return self.config.severity == other.config.severity
def same_column_name(self, other) -> bool:
return self.column_name == other.column_name
def same_contents(self, other) -> bool:
if other is None:
return False
return (
self.same_config(other) and
self.same_fqn(other) and
True
)
CompiledTestNode = Union[CompiledDataTestNode, CompiledSchemaTestNode]
PARSED_TYPES: Dict[Type[CompiledNode], Type[ParsedResource]] = {
CompiledAnalysisNode: ParsedAnalysisNode,
CompiledModelNode: ParsedModelNode,
CompiledHookNode: ParsedHookNode,
CompiledRPCNode: ParsedRPCNode,
CompiledSeedNode: ParsedSeedNode,
CompiledSnapshotNode: ParsedSnapshotNode,
CompiledDataTestNode: ParsedDataTestNode,
CompiledSchemaTestNode: ParsedSchemaTestNode,
}
COMPILED_TYPES: Dict[Type[ParsedResource], Type[CompiledNode]] = {
ParsedAnalysisNode: CompiledAnalysisNode,
ParsedModelNode: CompiledModelNode,
ParsedHookNode: CompiledHookNode,
ParsedRPCNode: CompiledRPCNode,
ParsedSeedNode: CompiledSeedNode,
ParsedSnapshotNode: CompiledSnapshotNode,
ParsedDataTestNode: CompiledDataTestNode,
ParsedSchemaTestNode: CompiledSchemaTestNode,
}
# for some types, the compiled type is the parsed type, so make this easy
CompiledType = Union[Type[CompiledNode], Type[ParsedResource]]
CompiledResource = Union[ParsedResource, CompiledNode]
def compiled_type_for(parsed: ParsedNode) -> CompiledType:
if type(parsed) in COMPILED_TYPES:
return COMPILED_TYPES[type(parsed)]
else:
return type(parsed)
def parsed_instance_for(compiled: CompiledNode) -> ParsedResource:
cls = PARSED_TYPES.get(type(compiled))
if cls is None:
# how???
raise ValueError('invalid resource_type: {}'
.format(compiled.resource_type))
# validate=False to allow extra keys from compiling
return cls.from_dict(compiled.to_dict(), validate=False)
NonSourceCompiledNode = Union[
CompiledAnalysisNode,
CompiledDataTestNode,
CompiledModelNode,
CompiledHookNode,
CompiledRPCNode,
CompiledSchemaTestNode,
CompiledSeedNode,
CompiledSnapshotNode,
]
NonSourceParsedNode = Union[
ParsedAnalysisNode,
ParsedDataTestNode,
ParsedHookNode,
ParsedModelNode,
ParsedRPCNode,
ParsedSchemaTestNode,
ParsedSeedNode,
ParsedSnapshotNode,
]
# This is anything that can be in manifest.nodes.
ManifestNode = Union[
NonSourceCompiledNode,
NonSourceParsedNode,
]
# We allow either parsed or compiled nodes, or parsed sources, as some
# 'compile()' calls in the runner actually just return the original parsed
# node they were given.
CompileResultNode = Union[
ManifestNode,
ParsedSourceDefinition,
]
# anything that participates in the graph: sources, exposures, manifest nodes
GraphMemberNode = Union[
CompileResultNode,
ParsedExposure,
]

View File

@@ -0,0 +1,967 @@
import abc
import enum
from dataclasses import dataclass, field
from datetime import datetime
from itertools import chain, islice
from multiprocessing.synchronize import Lock
from typing import (
Dict, List, Optional, Union, Mapping, MutableMapping, Any, Set, Tuple,
TypeVar, Callable, Iterable, Generic, cast, AbstractSet
)
from typing_extensions import Protocol
from uuid import UUID
from hologram import JsonSchemaMixin
from dbt.contracts.graph.compiled import (
CompileResultNode, ManifestNode, NonSourceCompiledNode, GraphMemberNode
)
from dbt.contracts.graph.parsed import (
ParsedMacro, ParsedDocumentation, ParsedNodePatch, ParsedMacroPatch,
ParsedSourceDefinition, ParsedExposure
)
from dbt.contracts.files import SourceFile
from dbt.contracts.util import (
Readable, Writable, Replaceable, MacroKey, SourceKey
)
from dbt.exceptions import (
raise_duplicate_resource_name, raise_compiler_error, warn_or_error,
raise_invalid_patch,
)
from dbt.helper_types import PathSet
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.node_types import NodeType
from dbt import deprecations
from dbt import flags
from dbt import tracking
import dbt.utils
NodeEdgeMap = Dict[str, List[str]]
PackageName = str
DocName = str
RefName = str
UniqueID = str
K_T = TypeVar('K_T')
V_T = TypeVar('V_T')
class PackageAwareCache(Generic[K_T, V_T]):
def __init__(self, manifest: 'Manifest'):
self.storage: Dict[K_T, Dict[PackageName, UniqueID]] = {}
self._manifest = manifest
self.populate()
@abc.abstractmethod
def populate(self):
pass
@abc.abstractmethod
def perform_lookup(self, unique_id: UniqueID) -> V_T:
pass
def find_cached_value(
self, key: K_T, package: Optional[PackageName]
) -> Optional[V_T]:
unique_id = self.find_unique_id_for_package(key, package)
if unique_id is not None:
return self.perform_lookup(unique_id)
return None
def find_unique_id_for_package(
self, key: K_T, package: Optional[PackageName]
) -> Optional[UniqueID]:
if key not in self.storage:
return None
pkg_dct: Mapping[PackageName, UniqueID] = self.storage[key]
if package is None:
if not pkg_dct:
return None
else:
return next(iter(pkg_dct.values()))
elif package in pkg_dct:
return pkg_dct[package]
else:
return None
class DocCache(PackageAwareCache[DocName, ParsedDocumentation]):
def add_doc(self, doc: ParsedDocumentation):
if doc.name not in self.storage:
self.storage[doc.name] = {}
self.storage[doc.name][doc.package_name] = doc.unique_id
def populate(self):
for doc in self._manifest.docs.values():
self.add_doc(doc)
def perform_lookup(
self, unique_id: UniqueID
) -> ParsedDocumentation:
if unique_id not in self._manifest.docs:
raise dbt.exceptions.InternalException(
f'Doc {unique_id} found in cache but not found in manifest'
)
return self._manifest.docs[unique_id]
class SourceCache(PackageAwareCache[SourceKey, ParsedSourceDefinition]):
def add_source(self, source: ParsedSourceDefinition):
key = (source.source_name, source.name)
if key not in self.storage:
self.storage[key] = {}
self.storage[key][source.package_name] = source.unique_id
def populate(self):
for source in self._manifest.sources.values():
self.add_source(source)
def perform_lookup(
self, unique_id: UniqueID
) -> ParsedSourceDefinition:
if unique_id not in self._manifest.sources:
raise dbt.exceptions.InternalException(
f'Source {unique_id} found in cache but not found in manifest'
)
return self._manifest.sources[unique_id]
class RefableCache(PackageAwareCache[RefName, ManifestNode]):
# refables are actually unique, so the Dict[PackageName, UniqueID] will
# only ever have exactly one value, but doing 3 dict lookups instead of 1
# is not a big deal at all and retains consistency
def __init__(self, manifest: 'Manifest'):
self._cached_types = set(NodeType.refable())
super().__init__(manifest)
def add_node(self, node: ManifestNode):
if node.resource_type in self._cached_types:
if node.name not in self.storage:
self.storage[node.name] = {}
self.storage[node.name][node.package_name] = node.unique_id
def populate(self):
for node in self._manifest.nodes.values():
self.add_node(node)
def perform_lookup(
self, unique_id: UniqueID
) -> ManifestNode:
if unique_id not in self._manifest.nodes:
raise dbt.exceptions.InternalException(
f'Node {unique_id} found in cache but not found in manifest'
)
return self._manifest.nodes[unique_id]
def _search_packages(
current_project: str,
node_package: str,
target_package: Optional[str] = None,
) -> List[Optional[str]]:
if target_package is not None:
return [target_package]
elif current_project == node_package:
return [current_project, None]
else:
return [current_project, node_package, None]
@dataclass
class ManifestMetadata(JsonSchemaMixin, Replaceable):
"""Metadata for the manifest."""
project_id: Optional[str] = field(
default=None,
metadata={
'description': 'A unique identifier for the project',
},
)
user_id: Optional[UUID] = field(
default=None,
metadata={
'description': 'A unique identifier for the user',
},
)
send_anonymous_usage_stats: Optional[bool] = field(
default=None,
metadata=dict(description=(
'Whether dbt is configured to send anonymous usage statistics'
)),
)
adapter_type: Optional[str] = field(
default=None,
metadata=dict(description='The type name of the adapter'),
)
def __post_init__(self):
if tracking.active_user is None:
return
if self.user_id is None:
self.user_id = tracking.active_user.id
if self.send_anonymous_usage_stats is None:
self.send_anonymous_usage_stats = (
not tracking.active_user.do_not_track
)
def _sort_values(dct):
"""Given a dictionary, sort each value. This makes output deterministic,
which helps for tests.
"""
return {k: sorted(v) for k, v in dct.items()}
def build_edges(nodes: List[ManifestNode]):
"""Build the forward and backward edges on the given list of ParsedNodes
and return them as two separate dictionaries, each mapping unique IDs to
lists of edges.
"""
backward_edges: Dict[str, List[str]] = {}
# pre-populate the forward edge dict for simplicity
forward_edges: Dict[str, List[str]] = {n.unique_id: [] for n in nodes}
for node in nodes:
backward_edges[node.unique_id] = node.depends_on_nodes[:]
for unique_id in node.depends_on_nodes:
forward_edges[unique_id].append(node.unique_id)
return _sort_values(forward_edges), _sort_values(backward_edges)
def _deepcopy(value):
return value.from_dict(value.to_dict())
class Locality(enum.IntEnum):
Core = 1
Imported = 2
Root = 3
class Specificity(enum.IntEnum):
Default = 1
Adapter = 2
@dataclass
class MacroCandidate:
locality: Locality
macro: ParsedMacro
def __eq__(self, other: object) -> bool:
if not isinstance(other, MacroCandidate):
return NotImplemented
return self.locality == other.locality
def __lt__(self, other: object) -> bool:
if not isinstance(other, MacroCandidate):
return NotImplemented
if self.locality < other.locality:
return True
if self.locality > other.locality:
return False
return False
@dataclass
class MaterializationCandidate(MacroCandidate):
specificity: Specificity
@classmethod
def from_macro(
cls, candidate: MacroCandidate, specificity: Specificity
) -> 'MaterializationCandidate':
return cls(
locality=candidate.locality,
macro=candidate.macro,
specificity=specificity,
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, MaterializationCandidate):
return NotImplemented
equal = (
self.specificity == other.specificity and
self.locality == other.locality
)
if equal:
raise_compiler_error(
'Found two materializations with the name {} (packages {} and '
'{}). dbt cannot resolve this ambiguity'
.format(self.macro.name, self.macro.package_name,
other.macro.package_name)
)
return equal
def __lt__(self, other: object) -> bool:
if not isinstance(other, MaterializationCandidate):
return NotImplemented
if self.specificity < other.specificity:
return True
if self.specificity > other.specificity:
return False
if self.locality < other.locality:
return True
if self.locality > other.locality:
return False
return False
M = TypeVar('M', bound=MacroCandidate)
class CandidateList(List[M]):
def last(self) -> Optional[ParsedMacro]:
if not self:
return None
self.sort()
return self[-1].macro
def _get_locality(
macro: ParsedMacro, root_project_name: str, internal_packages: Set[str]
) -> Locality:
if macro.package_name == root_project_name:
return Locality.Root
elif macro.package_name in internal_packages:
return Locality.Core
else:
return Locality.Imported
class Searchable(Protocol):
resource_type: NodeType
package_name: str
@property
def search_name(self) -> str:
raise NotImplementedError('search_name not implemented')
N = TypeVar('N', bound=Searchable)
@dataclass
class NameSearcher(Generic[N]):
name: str
package: Optional[str]
nodetypes: List[NodeType]
def _matches(self, model: N) -> bool:
"""Return True if the model matches the given name, package, and type.
If package is None, any package is allowed.
nodetypes should be a container of NodeTypes that implements the 'in'
operator.
"""
if model.resource_type not in self.nodetypes:
return False
if self.name != model.search_name:
return False
return self.package is None or self.package == model.package_name
def search(self, haystack: Iterable[N]) -> Optional[N]:
"""Find an entry in the given iterable by name."""
for model in haystack:
if self._matches(model):
return model
return None
D = TypeVar('D')
@dataclass
class Disabled(Generic[D]):
target: D
MaybeDocumentation = Optional[ParsedDocumentation]
MaybeParsedSource = Optional[Union[
ParsedSourceDefinition,
Disabled[ParsedSourceDefinition],
]]
MaybeNonSource = Optional[Union[
ManifestNode,
Disabled[ManifestNode]
]]
T = TypeVar('T', bound=GraphMemberNode)
def _update_into(dest: MutableMapping[str, T], new_item: T):
"""Update dest to overwrite whatever is at dest[new_item.unique_id] with
new_itme. There must be an existing value to overwrite, and they two nodes
must have the same original file path.
"""
unique_id = new_item.unique_id
if unique_id not in dest:
raise dbt.exceptions.RuntimeException(
f'got an update_{new_item.resource_type} call with an '
f'unrecognized {new_item.resource_type}: {new_item.unique_id}'
)
existing = dest[unique_id]
if new_item.original_file_path != existing.original_file_path:
raise dbt.exceptions.RuntimeException(
f'cannot update a {new_item.resource_type} to have a new file '
f'path!'
)
dest[unique_id] = new_item
@dataclass
class Manifest:
"""The manifest for the full graph, after parsing and during compilation.
"""
nodes: MutableMapping[str, ManifestNode]
sources: MutableMapping[str, ParsedSourceDefinition]
macros: MutableMapping[str, ParsedMacro]
docs: MutableMapping[str, ParsedDocumentation]
exposures: MutableMapping[str, ParsedExposure]
generated_at: datetime
disabled: List[CompileResultNode]
files: MutableMapping[str, SourceFile]
metadata: ManifestMetadata = field(default_factory=ManifestMetadata)
flat_graph: Dict[str, Any] = field(default_factory=dict)
_docs_cache: Optional[DocCache] = None
_sources_cache: Optional[SourceCache] = None
_refs_cache: Optional[RefableCache] = None
_lock: Lock = field(default_factory=flags.MP_CONTEXT.Lock)
@classmethod
def from_macros(
cls,
macros: Optional[MutableMapping[str, ParsedMacro]] = None,
files: Optional[MutableMapping[str, SourceFile]] = None,
) -> 'Manifest':
if macros is None:
macros = {}
if files is None:
files = {}
return cls(
nodes={},
sources={},
macros=macros,
docs={},
exposures={},
generated_at=datetime.utcnow(),
disabled=[],
files=files,
)
def sync_update_node(
self, new_node: NonSourceCompiledNode
) -> NonSourceCompiledNode:
"""update the node with a lock. The only time we should want to lock is
when compiling an ephemeral ancestor of a node at runtime, because
multiple threads could be just-in-time compiling the same ephemeral
dependency, and we want them to have a consistent view of the manifest.
If the existing node is not compiled, update it with the new node and
return that. If the existing node is compiled, do not update the
manifest and return the existing node.
"""
with self._lock:
existing = self.nodes[new_node.unique_id]
if getattr(existing, 'compiled', False):
# already compiled -> must be a NonSourceCompiledNode
return cast(NonSourceCompiledNode, existing)
_update_into(self.nodes, new_node)
return new_node
def update_exposure(self, new_exposure: ParsedExposure):
_update_into(self.exposures, new_exposure)
def update_node(self, new_node: ManifestNode):
_update_into(self.nodes, new_node)
def update_source(self, new_source: ParsedSourceDefinition):
_update_into(self.sources, new_source)
def build_flat_graph(self):
"""This attribute is used in context.common by each node, so we want to
only build it once and avoid any concurrency issues around it.
Make sure you don't call this until you're done with building your
manifest!
"""
self.flat_graph = {
'nodes': {
k: v.to_dict(omit_none=False) for k, v in self.nodes.items()
},
'sources': {
k: v.to_dict(omit_none=False) for k, v in self.sources.items()
}
}
def find_disabled_by_name(
self, name: str, package: Optional[str] = None
) -> Optional[ManifestNode]:
searcher: NameSearcher = NameSearcher(
name, package, NodeType.refable()
)
result = searcher.search(self.disabled)
return result
def find_disabled_source_by_name(
self, source_name: str, table_name: str, package: Optional[str] = None
) -> Optional[ParsedSourceDefinition]:
search_name = f'{source_name}.{table_name}'
searcher: NameSearcher = NameSearcher(
search_name, package, [NodeType.Source]
)
result = searcher.search(self.disabled)
if result is not None:
assert isinstance(result, ParsedSourceDefinition)
return result
def _find_macros_by_name(
self,
name: str,
root_project_name: str,
filter: Optional[Callable[[MacroCandidate], bool]] = None
) -> CandidateList:
"""Find macros by their name.
"""
# avoid an import cycle
from dbt.adapters.factory import get_adapter_package_names
candidates: CandidateList = CandidateList()
packages = set(get_adapter_package_names(self.metadata.adapter_type))
for unique_id, macro in self.macros.items():
if macro.name != name:
continue
candidate = MacroCandidate(
locality=_get_locality(macro, root_project_name, packages),
macro=macro,
)
if filter is None or filter(candidate):
candidates.append(candidate)
return candidates
def _materialization_candidates_for(
self, project_name: str,
materialization_name: str,
adapter_type: Optional[str],
) -> CandidateList:
if adapter_type is None:
specificity = Specificity.Default
else:
specificity = Specificity.Adapter
full_name = dbt.utils.get_materialization_macro_name(
materialization_name=materialization_name,
adapter_type=adapter_type,
with_prefix=False,
)
return CandidateList(
MaterializationCandidate.from_macro(m, specificity)
for m in self._find_macros_by_name(full_name, project_name)
)
def find_macro_by_name(
self, name: str, root_project_name: str, package: Optional[str]
) -> Optional[ParsedMacro]:
"""Find a macro in the graph by its name and package name, or None for
any package. The root project name is used to determine priority:
- locally defined macros come first
- then imported macros
- then macros defined in the root project
"""
filter: Optional[Callable[[MacroCandidate], bool]] = None
if package is not None:
def filter(candidate: MacroCandidate) -> bool:
return package == candidate.macro.package_name
candidates: CandidateList = self._find_macros_by_name(
name=name,
root_project_name=root_project_name,
filter=filter,
)
return candidates.last()
def find_generate_macro_by_name(
self, component: str, root_project_name: str
) -> Optional[ParsedMacro]:
"""
The `generate_X_name` macros are similar to regular ones, but ignore
imported packages.
- if there is a `generate_{component}_name` macro in the root
project, return it
- return the `generate_{component}_name` macro from the 'dbt'
internal project
"""
def filter(candidate: MacroCandidate) -> bool:
return candidate.locality != Locality.Imported
candidates: CandidateList = self._find_macros_by_name(
name=f'generate_{component}_name',
root_project_name=root_project_name,
# filter out imported packages
filter=filter,
)
return candidates.last()
def find_materialization_macro_by_name(
self, project_name: str, materialization_name: str, adapter_type: str
) -> Optional[ParsedMacro]:
candidates: CandidateList = CandidateList(chain.from_iterable(
self._materialization_candidates_for(
project_name=project_name,
materialization_name=materialization_name,
adapter_type=atype,
) for atype in (adapter_type, None)
))
return candidates.last()
def get_resource_fqns(self) -> Mapping[str, PathSet]:
resource_fqns: Dict[str, Set[Tuple[str, ...]]] = {}
all_resources = chain(self.nodes.values(), self.sources.values())
for resource in all_resources:
resource_type_plural = resource.resource_type.pluralize()
if resource_type_plural not in resource_fqns:
resource_fqns[resource_type_plural] = set()
resource_fqns[resource_type_plural].add(tuple(resource.fqn))
return resource_fqns
def add_nodes(self, new_nodes: Mapping[str, ManifestNode]):
"""Add the given dict of new nodes to the manifest."""
for unique_id, node in new_nodes.items():
if unique_id in self.nodes:
raise_duplicate_resource_name(node, self.nodes[unique_id])
self.nodes[unique_id] = node
# fixup the cache if it exists.
if self._refs_cache is not None:
if node.resource_type in NodeType.refable():
self._refs_cache.add_node(node)
def patch_macros(
self, patches: MutableMapping[MacroKey, ParsedMacroPatch]
) -> None:
for macro in self.macros.values():
key = (macro.package_name, macro.name)
patch = patches.pop(key, None)
if not patch:
continue
macro.patch(patch)
if patches:
for patch in patches.values():
warn_or_error(
f'WARNING: Found documentation for macro "{patch.name}" '
f'which was not found'
)
def patch_nodes(
self, patches: MutableMapping[str, ParsedNodePatch]
) -> None:
"""Patch nodes with the given dict of patches. Note that this consumes
the input!
This relies on the fact that all nodes have unique _name_ fields, not
just unique unique_id fields.
"""
# because we don't have any mapping from node _names_ to nodes, and we
# only have the node name in the patch, we have to iterate over all the
# nodes looking for matching names. We could use a NameSearcher if we
# were ok with doing an O(n*m) search (one nodes scan per patch)
for node in self.nodes.values():
patch = patches.pop(node.name, None)
if not patch:
continue
expected_key = node.resource_type.pluralize()
if expected_key != patch.yaml_key:
if patch.yaml_key == 'models':
deprecations.warn(
'models-key-mismatch',
patch=patch, node=node, expected_key=expected_key
)
else:
raise_invalid_patch(
node, patch.yaml_key, patch.original_file_path
)
node.patch(patch)
# log debug-level warning about nodes we couldn't find
if patches:
for patch in patches.values():
# since patches aren't nodes, we can't use the existing
# target_not_found warning
logger.debug((
'WARNING: Found documentation for resource "{}" which was '
'not found or is disabled').format(patch.name)
)
def get_used_schemas(self, resource_types=None):
return frozenset({
(node.database, node.schema) for node in
chain(self.nodes.values(), self.sources.values())
if not resource_types or node.resource_type in resource_types
})
def get_used_databases(self):
return frozenset(
x.database for x in
chain(self.nodes.values(), self.sources.values())
)
def deepcopy(self):
return Manifest(
nodes={k: _deepcopy(v) for k, v in self.nodes.items()},
sources={k: _deepcopy(v) for k, v in self.sources.items()},
macros={k: _deepcopy(v) for k, v in self.macros.items()},
docs={k: _deepcopy(v) for k, v in self.docs.items()},
exposures={k: _deepcopy(v) for k, v in self.exposures.items()},
generated_at=self.generated_at,
disabled=[_deepcopy(n) for n in self.disabled],
metadata=self.metadata,
files={k: _deepcopy(v) for k, v in self.files.items()},
)
def writable_manifest(self):
edge_members = list(chain(
self.nodes.values(),
self.sources.values(),
self.exposures.values(),
))
forward_edges, backward_edges = build_edges(edge_members)
return WritableManifest(
nodes=self.nodes,
sources=self.sources,
macros=self.macros,
docs=self.docs,
exposures=self.exposures,
generated_at=self.generated_at,
metadata=self.metadata,
disabled=self.disabled,
child_map=forward_edges,
parent_map=backward_edges,
)
def to_dict(self, omit_none=True, validate=False):
return self.writable_manifest().to_dict(
omit_none=omit_none, validate=validate
)
def write(self, path):
self.writable_manifest().write(path)
def expect(self, unique_id: str) -> GraphMemberNode:
if unique_id in self.nodes:
return self.nodes[unique_id]
elif unique_id in self.sources:
return self.sources[unique_id]
elif unique_id in self.exposures:
return self.exposures[unique_id]
else:
# something terrible has happened
raise dbt.exceptions.InternalException(
'Expected node {} not found in manifest'.format(unique_id)
)
@property
def docs_cache(self) -> DocCache:
if self._docs_cache is not None:
return self._docs_cache
cache = DocCache(self)
self._docs_cache = cache
return cache
@property
def source_cache(self) -> SourceCache:
if self._sources_cache is not None:
return self._sources_cache
cache = SourceCache(self)
self._sources_cache = cache
return cache
@property
def refs_cache(self) -> RefableCache:
if self._refs_cache is not None:
return self._refs_cache
cache = RefableCache(self)
self._refs_cache = cache
return cache
def resolve_ref(
self,
target_model_name: str,
target_model_package: Optional[str],
current_project: str,
node_package: str,
) -> MaybeNonSource:
node: Optional[ManifestNode] = None
disabled: Optional[ManifestNode] = None
candidates = _search_packages(
current_project, node_package, target_model_package
)
for pkg in candidates:
node = self.refs_cache.find_cached_value(target_model_name, pkg)
if node is not None and node.config.enabled:
return node
# it's possible that the node is disabled
if disabled is None:
disabled = self.find_disabled_by_name(
target_model_name, pkg
)
if disabled is not None:
return Disabled(disabled)
return None
def resolve_source(
self,
target_source_name: str,
target_table_name: str,
current_project: str,
node_package: str
) -> MaybeParsedSource:
key = (target_source_name, target_table_name)
candidates = _search_packages(current_project, node_package)
source: Optional[ParsedSourceDefinition] = None
disabled: Optional[ParsedSourceDefinition] = None
for pkg in candidates:
source = self.source_cache.find_cached_value(key, pkg)
if source is not None and source.config.enabled:
return source
if disabled is None:
disabled = self.find_disabled_source_by_name(
target_source_name, target_table_name, pkg
)
if disabled is not None:
return Disabled(disabled)
return None
def resolve_doc(
self,
name: str,
package: Optional[str],
current_project: str,
node_package: str,
) -> Optional[ParsedDocumentation]:
"""Resolve the given documentation. This follows the same algorithm as
resolve_ref except the is_enabled checks are unnecessary as docs are
always enabled.
"""
candidates = _search_packages(
current_project, node_package, package
)
for pkg in candidates:
result = self.docs_cache.find_cached_value(name, pkg)
if result is not None:
return result
return None
def merge_from_artifact(
self,
other: 'WritableManifest',
selected: AbstractSet[UniqueID],
) -> None:
"""Given the selected unique IDs and a writable manifest, update this
manifest by replacing any unselected nodes with their counterpart.
Only non-ephemeral refable nodes are examined.
"""
refables = set(NodeType.refable())
merged = set()
for unique_id, node in other.nodes.items():
if (
node.resource_type in refables and
not node.is_ephemeral and
unique_id not in selected
):
merged.add(unique_id)
self.nodes[unique_id] = node.replace(deferred=True)
# log up to 5 items
sample = list(islice(merged, 5))
logger.debug(
f'Merged {len(merged)} items from state (sample: {sample})'
)
# provide support for copy.deepcopy() - we jsut need to avoid the lock!
def __reduce_ex__(self, protocol):
args = (
self.nodes,
self.sources,
self.macros,
self.docs,
self.exposures,
self.generated_at,
self.disabled,
self.files,
self.metadata,
self.flat_graph,
self._docs_cache,
self._sources_cache,
self._refs_cache,
)
return self.__class__, args
@dataclass
class WritableManifest(JsonSchemaMixin, Writable, Readable):
nodes: Mapping[UniqueID, ManifestNode] = field(
metadata=dict(description=(
'The nodes defined in the dbt project and its dependencies'
))
)
sources: Mapping[UniqueID, ParsedSourceDefinition] = field(
metadata=dict(description=(
'The sources defined in the dbt project and its dependencies'
))
)
macros: Mapping[UniqueID, ParsedMacro] = field(
metadata=dict(description=(
'The macros defined in the dbt project and its dependencies'
))
)
docs: Mapping[UniqueID, ParsedDocumentation] = field(
metadata=dict(description=(
'The docs defined in the dbt project and its dependencies'
))
)
exposures: Mapping[UniqueID, ParsedExposure] = field(
metadata=dict(description=(
'The exposures defined in the dbt project and its dependencies'
))
)
disabled: Optional[List[CompileResultNode]] = field(metadata=dict(
description='A list of the disabled nodes in the target'
))
generated_at: datetime = field(metadata=dict(
description='The time at which the manifest was generated',
))
parent_map: Optional[NodeEdgeMap] = field(metadata=dict(
description='A mapping from child nodes to their dependencies',
))
child_map: Optional[NodeEdgeMap] = field(metadata=dict(
description='A mapping from parent nodes to their dependents',
))
metadata: ManifestMetadata = field(metadata=dict(
description='Metadata about the manifest',
))

View File

@@ -0,0 +1,616 @@
from dataclasses import field, Field, dataclass
from enum import Enum
from typing import (
Any, List, Optional, Dict, MutableMapping, Union, Type, NewType, Tuple,
TypeVar, Callable
)
# TODO: patch+upgrade hologram to avoid this jsonschema import
import jsonschema # type: ignore
# This is protected, but we really do want to reuse this logic, and the cache!
# It would be nice to move the custom error picking stuff into hologram!
from hologram import _validate_schema
from hologram import JsonSchemaMixin, ValidationError
from hologram.helpers import StrEnum, register_pattern
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed
from dbt.exceptions import CompilationException, InternalException
from dbt.contracts.util import Replaceable, list_str
from dbt import hooks
from dbt.node_types import NodeType
M = TypeVar('M', bound='Metadata')
def _get_meta_value(cls: Type[M], fld: Field, key: str, default: Any) -> M:
# a metadata field might exist. If it does, it might have a matching key.
# If it has both, make sure the value is valid and return it. If it
# doesn't, return the default.
if fld.metadata:
value = fld.metadata.get(key, default)
else:
value = default
try:
return cls(value)
except ValueError as exc:
raise InternalException(
f'Invalid {cls} value: {value}'
) from exc
def _set_meta_value(
obj: M, key: str, existing: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
if existing is None:
result = {}
else:
result = existing.copy()
result.update({key: obj})
return result
class Metadata(Enum):
@classmethod
def from_field(cls: Type[M], fld: Field) -> M:
default = cls.default_field()
key = cls.metadata_key()
return _get_meta_value(cls, fld, key, default)
def meta(
self, existing: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
key = self.metadata_key()
return _set_meta_value(self, key, existing)
@classmethod
def default_field(cls) -> 'Metadata':
raise NotImplementedError('Not implemented')
@classmethod
def metadata_key(cls) -> str:
raise NotImplementedError('Not implemented')
class MergeBehavior(Metadata):
Append = 1
Update = 2
Clobber = 3
@classmethod
def default_field(cls) -> 'MergeBehavior':
return cls.Clobber
@classmethod
def metadata_key(cls) -> str:
return 'merge'
class ShowBehavior(Metadata):
Show = 1
Hide = 2
@classmethod
def default_field(cls) -> 'ShowBehavior':
return cls.Show
@classmethod
def metadata_key(cls) -> str:
return 'show_hide'
@classmethod
def should_show(cls, fld: Field) -> bool:
return cls.from_field(fld) == cls.Show
class CompareBehavior(Metadata):
Include = 1
Exclude = 2
@classmethod
def default_field(cls) -> 'CompareBehavior':
return cls.Include
@classmethod
def metadata_key(cls) -> str:
return 'compare'
@classmethod
def should_include(cls, fld: Field) -> bool:
return cls.from_field(fld) == cls.Include
def metas(*metas: Metadata) -> Dict[str, Any]:
existing: Dict[str, Any] = {}
for m in metas:
existing = m.meta(existing)
return existing
def _listify(value: Any) -> List:
if isinstance(value, list):
return value[:]
else:
return [value]
def _merge_field_value(
merge_behavior: MergeBehavior,
self_value: Any,
other_value: Any,
):
if merge_behavior == MergeBehavior.Clobber:
return other_value
elif merge_behavior == MergeBehavior.Append:
return _listify(self_value) + _listify(other_value)
elif merge_behavior == MergeBehavior.Update:
if not isinstance(self_value, dict):
raise InternalException(f'expected dict, got {self_value}')
if not isinstance(other_value, dict):
raise InternalException(f'expected dict, got {other_value}')
value = self_value.copy()
value.update(other_value)
return value
else:
raise InternalException(
f'Got an invalid merge_behavior: {merge_behavior}'
)
def insensitive_patterns(*patterns: str):
lowercased = []
for pattern in patterns:
lowercased.append(
''.join('[{}{}]'.format(s.upper(), s.lower()) for s in pattern)
)
return '^({})$'.format('|'.join(lowercased))
Severity = NewType('Severity', str)
register_pattern(Severity, insensitive_patterns('warn', 'error'))
class SnapshotStrategy(StrEnum):
Timestamp = 'timestamp'
Check = 'check'
class All(StrEnum):
All = 'all'
@dataclass
class Hook(JsonSchemaMixin, Replaceable):
sql: str
transaction: bool = True
index: Optional[int] = None
T = TypeVar('T', bound='BaseConfig')
@dataclass
class BaseConfig(
AdditionalPropertiesAllowed, Replaceable, MutableMapping[str, Any]
):
# Implement MutableMapping so this config will behave as some macros expect
# during parsing (notably, syntax like `{{ node.config['schema'] }}`)
def __getitem__(self, key):
"""Handle parse-time use of `config` as a dictionary, making the extra
values available during parsing.
"""
if hasattr(self, key):
return getattr(self, key)
else:
return self._extra[key]
def __setitem__(self, key, value):
if hasattr(self, key):
setattr(self, key, value)
else:
self._extra[key] = value
def __delitem__(self, key):
if hasattr(self, key):
msg = (
'Error, tried to delete config key "{}": Cannot delete '
'built-in keys'
).format(key)
raise CompilationException(msg)
else:
del self._extra[key]
def _content_iterator(self, include_condition: Callable[[Field], bool]):
seen = set()
for fld, _ in self._get_fields():
seen.add(fld.name)
if include_condition(fld):
yield fld.name
for key in self._extra:
if key not in seen:
seen.add(key)
yield key
def __iter__(self):
yield from self._content_iterator(include_condition=lambda f: True)
def __len__(self):
return len(self._get_fields()) + len(self._extra)
def same_contents(self: T, other: T) -> bool:
"""This is like __eq__, except it ignores some fields."""
for key in self._content_iterator(
include_condition=CompareBehavior.should_include
):
try:
if self[key] != other[key]:
return False
except KeyError:
return False
return True
@classmethod
def _extract_dict(
cls, src: Dict[str, Any], data: Dict[str, Any]
) -> Dict[str, Any]:
"""Find all the items in data that match a target_field on this class,
and merge them with the data found in `src` for target_field, using the
field's specified merge behavior. Matching items will be removed from
`data` (but _not_ `src`!).
Returns a dict with the merge results.
That means this method mutates its input! Any remaining values in data
were not merged.
"""
result = {}
for fld, target_field in cls._get_fields():
if target_field not in data:
continue
data_attr = data.pop(target_field)
if target_field not in src:
result[target_field] = data_attr
continue
merge_behavior = MergeBehavior.from_field(fld)
self_attr = src[target_field]
result[target_field] = _merge_field_value(
merge_behavior=merge_behavior,
self_value=self_attr,
other_value=data_attr,
)
return result
def to_dict(
self,
omit_none: bool = True,
validate: bool = False,
*,
omit_hidden: bool = True,
) -> Dict[str, Any]:
result = super().to_dict(omit_none=omit_none, validate=validate)
if omit_hidden and not omit_none:
for fld, target_field in self._get_fields():
if target_field not in result:
continue
# if the field is not None, preserve it regardless of the
# setting. This is in line with existing behavior, but isn't
# an endorsement of it!
if result[target_field] is not None:
continue
if not ShowBehavior.should_show(fld):
del result[target_field]
return result
def update_from(
self: T, data: Dict[str, Any], adapter_type: str, validate: bool = True
) -> T:
"""Given a dict of keys, update the current config from them, validate
it, and return a new config with the updated values
"""
# sadly, this is a circular import
from dbt.adapters.factory import get_config_class_by_name
dct = self.to_dict(omit_none=False, validate=False, omit_hidden=False)
adapter_config_cls = get_config_class_by_name(adapter_type)
self_merged = self._extract_dict(dct, data)
dct.update(self_merged)
adapter_merged = adapter_config_cls._extract_dict(dct, data)
dct.update(adapter_merged)
# any remaining fields must be "clobber"
dct.update(data)
# any validation failures must have come from the update
return self.from_dict(dct, validate=validate)
def finalize_and_validate(self: T) -> T:
# from_dict will validate for us
dct = self.to_dict(omit_none=False, validate=False)
return self.from_dict(dct)
def replace(self, **kwargs):
dct = self.to_dict(validate=False)
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, validate=False)
@dataclass
class SourceConfig(BaseConfig):
enabled: bool = True
@dataclass
class NodeConfig(BaseConfig):
enabled: bool = True
materialized: str = 'view'
persist_docs: Dict[str, Any] = field(default_factory=dict)
post_hook: List[Hook] = field(
default_factory=list,
metadata=MergeBehavior.Append.meta(),
)
pre_hook: List[Hook] = field(
default_factory=list,
metadata=MergeBehavior.Append.meta(),
)
# this only applies for config v1, so it doesn't participate in comparison
vars: Dict[str, Any] = field(
default_factory=dict,
metadata=metas(CompareBehavior.Exclude, MergeBehavior.Update),
)
quoting: Dict[str, Any] = field(
default_factory=dict,
metadata=MergeBehavior.Update.meta(),
)
# This is actually only used by seeds. Should it be available to others?
# That would be a breaking change!
column_types: Dict[str, Any] = field(
default_factory=dict,
metadata=MergeBehavior.Update.meta(),
)
# these fields are included in serialized output, but are not part of
# config comparison (they are part of database_representation)
alias: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)
schema: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)
database: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)
tags: Union[List[str], str] = field(
default_factory=list_str,
metadata=metas(ShowBehavior.Hide,
MergeBehavior.Append,
CompareBehavior.Exclude),
)
full_refresh: Optional[bool] = None
@classmethod
def from_dict(cls, data, validate=True):
for key in hooks.ModelHookType:
if key in data:
data[key] = [hooks.get_hook_dict(h) for h in data[key]]
return super().from_dict(data, validate=validate)
@classmethod
def field_mapping(cls):
return {'post_hook': 'post-hook', 'pre_hook': 'pre-hook'}
@dataclass
class SeedConfig(NodeConfig):
materialized: str = 'seed'
quote_columns: Optional[bool] = None
@dataclass
class TestConfig(NodeConfig):
severity: Severity = Severity('ERROR')
SnapshotVariants = Union[
'TimestampSnapshotConfig',
'CheckSnapshotConfig',
'GenericSnapshotConfig',
]
def _relevance_without_strategy(error: jsonschema.ValidationError):
# calculate the 'relevance' of an error the normal jsonschema way, except
# if the validator is in the 'strategy' field and its conflicting with the
# 'enum'. This suppresses `"'timestamp' is not one of ['check']` and such
if 'strategy' in error.path and error.validator in {'enum', 'not'}:
length = 1
else:
length = -len(error.path)
validator = error.validator
return length, validator not in {'anyOf', 'oneOf'}
@dataclass
class SnapshotWrapper(JsonSchemaMixin):
"""This is a little wrapper to let us serialize/deserialize the
SnapshotVariants union.
"""
config: SnapshotVariants # mypy: ignore
@classmethod
def validate(cls, data: Any):
schema = _validate_schema(cls)
validator = jsonschema.Draft7Validator(schema)
error = jsonschema.exceptions.best_match(
validator.iter_errors(data),
key=_relevance_without_strategy,
)
if error is not None:
raise ValidationError.create_from(error) from error
@dataclass
class EmptySnapshotConfig(NodeConfig):
materialized: str = 'snapshot'
@dataclass(init=False)
class SnapshotConfig(EmptySnapshotConfig):
unique_key: str = field(init=False, metadata=dict(init_required=True))
target_schema: str = field(init=False, metadata=dict(init_required=True))
target_database: Optional[str] = None
def __init__(
self,
unique_key: str,
target_schema: str,
target_database: Optional[str] = None,
**kwargs
) -> None:
self.unique_key = unique_key
self.target_schema = target_schema
self.target_database = target_database
# kwargs['materialized'] = materialized
super().__init__(**kwargs)
# type hacks...
@classmethod
def _get_fields(cls) -> List[Tuple[Field, str]]: # type: ignore
fields: List[Tuple[Field, str]] = []
for old_field, name in super()._get_fields():
new_field = old_field
# tell hologram we're really an initvar
if old_field.metadata and old_field.metadata.get('init_required'):
new_field = field(init=True, metadata=old_field.metadata)
new_field.name = old_field.name
new_field.type = old_field.type
new_field._field_type = old_field._field_type # type: ignore
fields.append((new_field, name))
return fields
def finalize_and_validate(self: 'SnapshotConfig') -> SnapshotVariants:
data = self.to_dict()
return SnapshotWrapper.from_dict({'config': data}).config
@dataclass(init=False)
class GenericSnapshotConfig(SnapshotConfig):
strategy: str = field(init=False, metadata=dict(init_required=True))
def __init__(self, strategy: str, **kwargs) -> None:
self.strategy = strategy
super().__init__(**kwargs)
@classmethod
def _collect_json_schema(
cls, definitions: Dict[str, Any]
) -> Dict[str, Any]:
# this is the method you want to override in hologram if you want
# to do clever things about the json schema and have classes that
# contain instances of your JsonSchemaMixin respect the change.
schema = super()._collect_json_schema(definitions)
# Instead of just the strategy we'd calculate normally, say
# "this strategy except none of our specialization strategies".
strategies = [schema['properties']['strategy']]
for specialization in (TimestampSnapshotConfig, CheckSnapshotConfig):
strategies.append(
{'not': specialization.json_schema()['properties']['strategy']}
)
schema['properties']['strategy'] = {
'allOf': strategies
}
return schema
@dataclass(init=False)
class TimestampSnapshotConfig(SnapshotConfig):
strategy: str = field(
init=False,
metadata=dict(
restrict=[str(SnapshotStrategy.Timestamp)],
init_required=True,
),
)
updated_at: str = field(init=False, metadata=dict(init_required=True))
def __init__(
self, strategy: str, updated_at: str, **kwargs
) -> None:
self.strategy = strategy
self.updated_at = updated_at
super().__init__(**kwargs)
@dataclass(init=False)
class CheckSnapshotConfig(SnapshotConfig):
strategy: str = field(
init=False,
metadata=dict(
restrict=[str(SnapshotStrategy.Check)],
init_required=True,
),
)
# TODO: is there a way to get this to accept tuples of strings? Adding
# `Tuple[str, ...]` to the list of types results in this:
# ['email'] is valid under each of {'type': 'array', 'items':
# {'type': 'string'}}, {'type': 'array', 'items': {'type': 'string'}}
# but without it, parsing gets upset about values like `('email',)`
# maybe hologram itself should support this behavior? It's not like tuples
# are meaningful in json
check_cols: Union[All, List[str]] = field(
init=False,
metadata=dict(init_required=True),
)
def __init__(
self, strategy: str, check_cols: Union[All, List[str]],
**kwargs
) -> None:
self.strategy = strategy
self.check_cols = check_cols
super().__init__(**kwargs)
RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
NodeType.Source: SourceConfig,
NodeType.Seed: SeedConfig,
NodeType.Test: TestConfig,
NodeType.Model: NodeConfig,
NodeType.Snapshot: SnapshotConfig,
}
# base resource types are like resource types, except nothing has mandatory
# configs.
BASE_RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = RESOURCE_TYPES.copy()
BASE_RESOURCE_TYPES.update({
NodeType.Snapshot: EmptySnapshotConfig
})
def get_config_for(resource_type: NodeType, base=False) -> Type[BaseConfig]:
if base:
lookup = BASE_RESOURCE_TYPES
else:
lookup = RESOURCE_TYPES
return lookup.get(resource_type, NodeConfig)

View File

@@ -0,0 +1,705 @@
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import (
Optional,
Union,
List,
Dict,
Any,
Sequence,
Tuple,
Iterator,
TypeVar,
)
from hologram import JsonSchemaMixin
from hologram.helpers import ExtensibleJsonSchemaMixin
from dbt.clients.system import write_file
from dbt.contracts.files import FileHash, MAXIMUM_SEED_SIZE_NAME
from dbt.contracts.graph.unparsed import (
UnparsedNode, UnparsedDocumentation, Quoting, Docs,
UnparsedBaseNode, FreshnessThreshold, ExternalTable,
HasYamlMetadata, MacroArgument, UnparsedSourceDefinition,
UnparsedSourceTableDefinition, UnparsedColumn, TestDef,
ExposureOwner, ExposureType, MaturityType
)
from dbt.contracts.util import Replaceable, AdditionalPropertiesMixin
from dbt.exceptions import warn_or_error
from dbt.logger import GLOBAL_LOGGER as logger # noqa
from dbt import flags
from dbt.node_types import NodeType
from .model_config import (
NodeConfig,
SeedConfig,
TestConfig,
SourceConfig,
EmptySnapshotConfig,
SnapshotVariants,
)
# import these 3 so the SnapshotVariants forward ref works.
from .model_config import ( # noqa
TimestampSnapshotConfig,
CheckSnapshotConfig,
GenericSnapshotConfig,
)
@dataclass
class ColumnInfo(
AdditionalPropertiesMixin,
ExtensibleJsonSchemaMixin,
Replaceable
):
name: str
description: str = ''
meta: Dict[str, Any] = field(default_factory=dict)
data_type: Optional[str] = None
quote: Optional[bool] = None
tags: List[str] = field(default_factory=list)
_extra: Dict[str, Any] = field(default_factory=dict)
@dataclass
class HasFqn(JsonSchemaMixin, Replaceable):
fqn: List[str]
def same_fqn(self, other: 'HasFqn') -> bool:
return self.fqn == other.fqn
@dataclass
class HasUniqueID(JsonSchemaMixin, Replaceable):
unique_id: str
@dataclass
class MacroDependsOn(JsonSchemaMixin, Replaceable):
macros: List[str] = field(default_factory=list)
# 'in' on lists is O(n) so this is O(n^2) for # of macros
def add_macro(self, value: str):
if value not in self.macros:
self.macros.append(value)
@dataclass
class DependsOn(MacroDependsOn):
nodes: List[str] = field(default_factory=list)
def add_node(self, value: str):
if value not in self.nodes:
self.nodes.append(value)
@dataclass
class HasRelationMetadata(JsonSchemaMixin, Replaceable):
database: Optional[str]
schema: str
class ParsedNodeMixins(JsonSchemaMixin):
resource_type: NodeType
depends_on: DependsOn
config: NodeConfig
@property
def is_refable(self):
return self.resource_type in NodeType.refable()
@property
def is_ephemeral(self):
return self.config.materialized == 'ephemeral'
@property
def is_ephemeral_model(self):
return self.is_refable and self.is_ephemeral
@property
def depends_on_nodes(self):
return self.depends_on.nodes
def patch(self, patch: 'ParsedNodePatch'):
"""Given a ParsedNodePatch, add the new information to the node."""
# explicitly pick out the parts to update so we don't inadvertently
# step on the model name or anything
self.patch_path: Optional[str] = patch.original_file_path
self.description = patch.description
self.columns = patch.columns
self.meta = patch.meta
self.docs = patch.docs
if flags.STRICT_MODE:
assert isinstance(self, JsonSchemaMixin)
self.to_dict(validate=True, omit_none=False)
def get_materialization(self):
return self.config.materialized
def local_vars(self):
return self.config.vars
@dataclass
class ParsedNodeMandatory(
UnparsedNode,
HasUniqueID,
HasFqn,
HasRelationMetadata,
Replaceable
):
alias: str
checksum: FileHash
config: NodeConfig = field(default_factory=NodeConfig)
@property
def identifier(self):
return self.alias
@dataclass
class ParsedNodeDefaults(ParsedNodeMandatory):
tags: List[str] = field(default_factory=list)
refs: List[List[str]] = field(default_factory=list)
sources: List[List[Any]] = field(default_factory=list)
depends_on: DependsOn = field(default_factory=DependsOn)
description: str = field(default='')
columns: Dict[str, ColumnInfo] = field(default_factory=dict)
meta: Dict[str, Any] = field(default_factory=dict)
docs: Docs = field(default_factory=Docs)
patch_path: Optional[str] = None
build_path: Optional[str] = None
deferred: bool = False
def write_node(self, target_path: str, subdirectory: str, payload: str):
if (os.path.basename(self.path) ==
os.path.basename(self.original_file_path)):
# One-to-one relationship of nodes to files.
path = self.original_file_path
else:
# Many-to-one relationship of nodes to files.
path = os.path.join(self.original_file_path, self.path)
full_path = os.path.join(
target_path, subdirectory, self.package_name, path
)
write_file(full_path, payload)
return full_path
T = TypeVar('T', bound='ParsedNode')
@dataclass
class ParsedNode(ParsedNodeDefaults, ParsedNodeMixins):
def _persist_column_docs(self) -> bool:
return bool(self.config.persist_docs.get('columns'))
def _persist_relation_docs(self) -> bool:
return bool(self.config.persist_docs.get('relation'))
def same_body(self: T, other: T) -> bool:
return self.raw_sql == other.raw_sql
def same_persisted_description(self: T, other: T) -> bool:
# the check on configs will handle the case where we have different
# persist settings, so we only have to care about the cases where they
# are the same..
if self._persist_relation_docs():
if self.description != other.description:
return False
if self._persist_column_docs():
# assert other._persist_column_docs()
column_descriptions = {
k: v.description for k, v in self.columns.items()
}
other_column_descriptions = {
k: v.description for k, v in other.columns.items()
}
if column_descriptions != other_column_descriptions:
return False
return True
def same_database_representation(self, other: T) -> bool:
# compare the config representation, not the node's config value. This
# compares the configured value, rather than the ultimate value (so
# generate_*_name and unset values derived from the target are
# ignored)
return (
self.config.database == other.config.database and
self.config.schema == other.config.schema and
self.config.alias == other.config.alias and
True
)
def same_config(self, old: T) -> bool:
return self.config.same_contents(old.config)
def same_contents(self: T, old: Optional[T]) -> bool:
if old is None:
return False
return (
self.same_body(old) and
self.same_config(old) and
self.same_persisted_description(old) and
self.same_fqn(old) and
self.same_database_representation(old) and
True
)
@dataclass
class ParsedAnalysisNode(ParsedNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Analysis]})
@dataclass
class ParsedHookNode(ParsedNode):
resource_type: NodeType = field(
metadata={'restrict': [NodeType.Operation]}
)
index: Optional[int] = None
@dataclass
class ParsedModelNode(ParsedNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Model]})
@dataclass
class ParsedRPCNode(ParsedNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.RPCCall]})
def same_seeds(first: ParsedNode, second: ParsedNode) -> bool:
# for seeds, we check the hashes. If the hashes are different types,
# no match. If the hashes are both the same 'path', log a warning and
# assume they are the same
# if the current checksum is a path, we want to log a warning.
result = first.checksum == second.checksum
if first.checksum.name == 'path':
msg: str
if second.checksum.name != 'path':
msg = (
f'Found a seed ({first.package_name}.{first.name}) '
f'>{MAXIMUM_SEED_SIZE_NAME} in size. The previous file was '
f'<={MAXIMUM_SEED_SIZE_NAME}, so it has changed'
)
elif result:
msg = (
f'Found a seed ({first.package_name}.{first.name}) '
f'>{MAXIMUM_SEED_SIZE_NAME} in size at the same path, dbt '
f'cannot tell if it has changed: assuming they are the same'
)
elif not result:
msg = (
f'Found a seed ({first.package_name}.{first.name}) '
f'>{MAXIMUM_SEED_SIZE_NAME} in size. The previous file was in '
f'a different location, assuming it has changed'
)
else:
msg = (
f'Found a seed ({first.package_name}.{first.name}) '
f'>{MAXIMUM_SEED_SIZE_NAME} in size. The previous file had a '
f'checksum type of {second.checksum.name}, so it has changed'
)
warn_or_error(msg, node=first)
return result
@dataclass
class ParsedSeedNode(ParsedNode):
# keep this in sync with CompiledSeedNode!
resource_type: NodeType = field(metadata={'restrict': [NodeType.Seed]})
config: SeedConfig = field(default_factory=SeedConfig)
@property
def empty(self):
""" Seeds are never empty"""
return False
def same_body(self: T, other: T) -> bool:
return same_seeds(self, other)
@dataclass
class TestMetadata(JsonSchemaMixin, Replaceable):
namespace: Optional[str]
name: str
kwargs: Dict[str, Any]
@dataclass
class HasTestMetadata(JsonSchemaMixin):
test_metadata: TestMetadata
@dataclass
class ParsedDataTestNode(ParsedNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Test]})
config: TestConfig = field(default_factory=TestConfig)
@dataclass
class ParsedSchemaTestNode(ParsedNode, HasTestMetadata):
# keep this in sync with CompiledSchemaTestNode!
resource_type: NodeType = field(metadata={'restrict': [NodeType.Test]})
column_name: Optional[str] = None
config: TestConfig = field(default_factory=TestConfig)
def same_config(self, other) -> bool:
return self.config.severity == other.config.severity
def same_column_name(self, other) -> bool:
return self.column_name == other.column_name
def same_contents(self, other) -> bool:
if other is None:
return False
return (
self.same_config(other) and
self.same_fqn(other) and
True
)
@dataclass
class IntermediateSnapshotNode(ParsedNode):
# at an intermediate stage in parsing, where we've built something better
# than an unparsed node for rendering in parse mode, it's pretty possible
# that we won't have critical snapshot-related information that is only
# defined in config blocks. To fix that, we have an intermediate type that
# uses a regular node config, which the snapshot parser will then convert
# into a full ParsedSnapshotNode after rendering.
resource_type: NodeType = field(metadata={'restrict': [NodeType.Snapshot]})
config: EmptySnapshotConfig = field(default_factory=EmptySnapshotConfig)
@dataclass
class ParsedSnapshotNode(ParsedNode):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Snapshot]})
config: SnapshotVariants
@dataclass
class ParsedPatch(HasYamlMetadata, Replaceable):
name: str
description: str
meta: Dict[str, Any]
docs: Docs
# The parsed node update is only the 'patch', not the test. The test became a
# regular parsed node. Note that description and columns must be present, but
# may be empty.
@dataclass
class ParsedNodePatch(ParsedPatch):
columns: Dict[str, ColumnInfo]
@dataclass
class ParsedMacroPatch(ParsedPatch):
arguments: List[MacroArgument] = field(default_factory=list)
@dataclass
class ParsedMacro(UnparsedBaseNode, HasUniqueID):
name: str
macro_sql: str
resource_type: NodeType = field(metadata={'restrict': [NodeType.Macro]})
# TODO: can macros even have tags?
tags: List[str] = field(default_factory=list)
# TODO: is this ever populated?
depends_on: MacroDependsOn = field(default_factory=MacroDependsOn)
description: str = ''
meta: Dict[str, Any] = field(default_factory=dict)
docs: Docs = field(default_factory=Docs)
patch_path: Optional[str] = None
arguments: List[MacroArgument] = field(default_factory=list)
def local_vars(self):
return {}
def patch(self, patch: ParsedMacroPatch):
self.patch_path: Optional[str] = patch.original_file_path
self.description = patch.description
self.meta = patch.meta
self.docs = patch.docs
self.arguments = patch.arguments
if flags.STRICT_MODE:
assert isinstance(self, JsonSchemaMixin)
self.to_dict(validate=True, omit_none=False)
def same_contents(self, other: Optional['ParsedMacro']) -> bool:
if other is None:
return False
# the only thing that makes one macro different from another with the
# same name/package is its content
return self.macro_sql == other.macro_sql
@dataclass
class ParsedDocumentation(UnparsedDocumentation, HasUniqueID):
name: str
block_contents: str
@property
def search_name(self):
return self.name
def same_contents(self, other: Optional['ParsedDocumentation']) -> bool:
if other is None:
return False
# the only thing that makes one doc different from another with the
# same name/package is its content
return self.block_contents == other.block_contents
def normalize_test(testdef: TestDef) -> Dict[str, Any]:
if isinstance(testdef, str):
return {testdef: {}}
else:
return testdef
@dataclass
class UnpatchedSourceDefinition(UnparsedBaseNode, HasUniqueID, HasFqn):
source: UnparsedSourceDefinition
table: UnparsedSourceTableDefinition
resource_type: NodeType = field(metadata={'restrict': [NodeType.Source]})
patch_path: Optional[Path] = None
def get_full_source_name(self):
return f'{self.source.name}_{self.table.name}'
def get_source_representation(self):
return f'source("{self.source.name}", "{self.table.name}")'
@property
def name(self) -> str:
return self.get_full_source_name()
@property
def quote_columns(self) -> Optional[bool]:
result = None
if self.source.quoting.column is not None:
result = self.source.quoting.column
if self.table.quoting.column is not None:
result = self.table.quoting.column
return result
@property
def columns(self) -> Sequence[UnparsedColumn]:
if self.table.columns is None:
return []
else:
return self.table.columns
def get_tests(
self
) -> Iterator[Tuple[Dict[str, Any], Optional[UnparsedColumn]]]:
for test in self.tests:
yield normalize_test(test), None
for column in self.columns:
if column.tests is not None:
for test in column.tests:
yield normalize_test(test), column
@property
def tests(self) -> List[TestDef]:
if self.table.tests is None:
return []
else:
return self.table.tests
@dataclass
class ParsedSourceDefinition(
UnparsedBaseNode,
HasUniqueID,
HasRelationMetadata,
HasFqn
):
name: str
source_name: str
source_description: str
loader: str
identifier: str
resource_type: NodeType = field(metadata={'restrict': [NodeType.Source]})
quoting: Quoting = field(default_factory=Quoting)
loaded_at_field: Optional[str] = None
freshness: Optional[FreshnessThreshold] = None
external: Optional[ExternalTable] = None
description: str = ''
columns: Dict[str, ColumnInfo] = field(default_factory=dict)
meta: Dict[str, Any] = field(default_factory=dict)
source_meta: Dict[str, Any] = field(default_factory=dict)
tags: List[str] = field(default_factory=list)
config: SourceConfig = field(default_factory=SourceConfig)
patch_path: Optional[Path] = None
def same_database_representation(
self, other: 'ParsedSourceDefinition'
) -> bool:
return (
self.database == other.database and
self.schema == other.schema and
self.identifier == other.identifier and
True
)
def same_quoting(self, other: 'ParsedSourceDefinition') -> bool:
return self.quoting == other.quoting
def same_freshness(self, other: 'ParsedSourceDefinition') -> bool:
return (
self.freshness == other.freshness and
self.loaded_at_field == other.loaded_at_field and
True
)
def same_external(self, other: 'ParsedSourceDefinition') -> bool:
return self.external == other.external
def same_config(self, old: 'ParsedSourceDefinition') -> bool:
return self.config.same_contents(old.config)
def same_contents(self, old: Optional['ParsedSourceDefinition']) -> bool:
# existing when it didn't before is a change!
if old is None:
return True
# config changes are changes (because the only config is "enabled", and
# enabling a source is a change!)
# changing the database/schema/identifier is a change
# messing around with external stuff is a change (uh, right?)
# quoting changes are changes
# freshness changes are changes, I guess
# metadata/tags changes are not "changes"
# patching/description changes are not "changes"
return (
self.same_database_representation(old) and
self.same_fqn(old) and
self.same_config(old) and
self.same_quoting(old) and
self.same_freshness(old) and
self.same_external(old) and
True
)
def get_full_source_name(self):
return f'{self.source_name}_{self.name}'
def get_source_representation(self):
return f'source("{self.source.name}", "{self.table.name}")'
@property
def is_refable(self):
return False
@property
def is_ephemeral(self):
return False
@property
def is_ephemeral_model(self):
return False
@property
def depends_on_nodes(self):
return []
@property
def refs(self):
return []
@property
def sources(self):
return []
@property
def has_freshness(self):
return bool(self.freshness) and self.loaded_at_field is not None
@property
def search_name(self):
return f'{self.source_name}.{self.name}'
@dataclass
class ParsedExposure(UnparsedBaseNode, HasUniqueID, HasFqn):
name: str
type: ExposureType
owner: ExposureOwner
resource_type: NodeType = NodeType.Exposure
maturity: Optional[MaturityType] = None
url: Optional[str] = None
description: Optional[str] = None
depends_on: DependsOn = field(default_factory=DependsOn)
refs: List[List[str]] = field(default_factory=list)
sources: List[List[str]] = field(default_factory=list)
@property
def depends_on_nodes(self):
return self.depends_on.nodes
@property
def search_name(self):
return self.name
# no tags for now, but we could definitely add them
@property
def tags(self):
return []
def same_depends_on(self, old: 'ParsedExposure') -> bool:
return set(self.depends_on.nodes) == set(old.depends_on.nodes)
def same_description(self, old: 'ParsedExposure') -> bool:
return self.description == old.description
def same_maturity(self, old: 'ParsedExposure') -> bool:
return self.maturity == old.maturity
def same_owner(self, old: 'ParsedExposure') -> bool:
return self.owner == old.owner
def same_exposure_type(self, old: 'ParsedExposure') -> bool:
return self.type == old.type
def same_url(self, old: 'ParsedExposure') -> bool:
return self.url == old.url
def same_contents(self, old: Optional['ParsedExposure']) -> bool:
# existing when it didn't before is a change!
if old is None:
return True
return (
self.same_fqn(old) and
self.same_exposure_type(old) and
self.same_owner(old) and
self.same_maturity(old) and
self.same_url(old) and
self.same_description(old) and
self.same_depends_on(old) and
True
)
ParsedResource = Union[
ParsedDocumentation,
ParsedMacro,
ParsedNode,
ParsedExposure,
ParsedSourceDefinition,
]

View File

@@ -0,0 +1,421 @@
from dbt.node_types import NodeType
from dbt.contracts.util import (
AdditionalPropertiesMixin,
Mergeable,
Replaceable,
)
# trigger the PathEncoder
import dbt.helper_types # noqa:F401
from dbt.exceptions import CompilationException
from hologram import JsonSchemaMixin
from hologram.helpers import StrEnum, ExtensibleJsonSchemaMixin
from dataclasses import dataclass, field
from datetime import timedelta
from pathlib import Path
from typing import Optional, List, Union, Dict, Any, Sequence
@dataclass
class UnparsedBaseNode(JsonSchemaMixin, Replaceable):
package_name: str
root_path: str
path: str
original_file_path: str
@dataclass
class HasSQL:
raw_sql: str
@property
def empty(self):
return not self.raw_sql.strip()
@dataclass
class UnparsedMacro(UnparsedBaseNode, HasSQL):
resource_type: NodeType = field(metadata={'restrict': [NodeType.Macro]})
@dataclass
class UnparsedNode(UnparsedBaseNode, HasSQL):
name: str
resource_type: NodeType = field(metadata={'restrict': [
NodeType.Model,
NodeType.Analysis,
NodeType.Test,
NodeType.Snapshot,
NodeType.Operation,
NodeType.Seed,
NodeType.RPCCall,
]})
@property
def search_name(self):
return self.name
@dataclass
class UnparsedRunHook(UnparsedNode):
resource_type: NodeType = field(
metadata={'restrict': [NodeType.Operation]}
)
index: Optional[int] = None
@dataclass
class Docs(JsonSchemaMixin, Replaceable):
show: bool = True
@dataclass
class HasDocs(AdditionalPropertiesMixin, ExtensibleJsonSchemaMixin,
Replaceable):
name: str
description: str = ''
meta: Dict[str, Any] = field(default_factory=dict)
data_type: Optional[str] = None
docs: Docs = field(default_factory=Docs)
_extra: Dict[str, Any] = field(default_factory=dict)
TestDef = Union[Dict[str, Any], str]
@dataclass
class HasTests(HasDocs):
tests: Optional[List[TestDef]] = None
def __post_init__(self):
if self.tests is None:
self.tests = []
@dataclass
class UnparsedColumn(HasTests):
quote: Optional[bool] = None
tags: List[str] = field(default_factory=list)
@dataclass
class HasColumnDocs(JsonSchemaMixin, Replaceable):
columns: Sequence[HasDocs] = field(default_factory=list)
@dataclass
class HasColumnTests(HasColumnDocs):
columns: Sequence[UnparsedColumn] = field(default_factory=list)
@dataclass
class HasYamlMetadata(JsonSchemaMixin):
original_file_path: str
yaml_key: str
package_name: str
@dataclass
class UnparsedAnalysisUpdate(HasColumnDocs, HasDocs, HasYamlMetadata):
pass
@dataclass
class UnparsedNodeUpdate(HasColumnTests, HasTests, HasYamlMetadata):
quote_columns: Optional[bool] = None
@dataclass
class MacroArgument(JsonSchemaMixin):
name: str
type: Optional[str] = None
description: str = ''
@dataclass
class UnparsedMacroUpdate(HasDocs, HasYamlMetadata):
arguments: List[MacroArgument] = field(default_factory=list)
class TimePeriod(StrEnum):
minute = 'minute'
hour = 'hour'
day = 'day'
def plural(self) -> str:
return str(self) + 's'
@dataclass
class Time(JsonSchemaMixin, Replaceable):
count: int
period: TimePeriod
def exceeded(self, actual_age: float) -> bool:
kwargs = {self.period.plural(): self.count}
difference = timedelta(**kwargs).total_seconds()
return actual_age > difference
class FreshnessStatus(StrEnum):
Pass = 'pass'
Warn = 'warn'
Error = 'error'
@dataclass
class FreshnessThreshold(JsonSchemaMixin, Mergeable):
warn_after: Optional[Time] = None
error_after: Optional[Time] = None
filter: Optional[str] = None
def status(self, age: float) -> FreshnessStatus:
if self.error_after and self.error_after.exceeded(age):
return FreshnessStatus.Error
elif self.warn_after and self.warn_after.exceeded(age):
return FreshnessStatus.Warn
else:
return FreshnessStatus.Pass
def __bool__(self):
return self.warn_after is not None or self.error_after is not None
@dataclass
class AdditionalPropertiesAllowed(
AdditionalPropertiesMixin,
ExtensibleJsonSchemaMixin
):
_extra: Dict[str, Any] = field(default_factory=dict)
@dataclass
class ExternalPartition(AdditionalPropertiesAllowed, Replaceable):
name: str = ''
description: str = ''
data_type: str = ''
meta: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
if self.name == '' or self.data_type == '':
raise CompilationException(
'External partition columns must have names and data types'
)
@dataclass
class ExternalTable(AdditionalPropertiesAllowed, Mergeable):
location: Optional[str] = None
file_format: Optional[str] = None
row_format: Optional[str] = None
tbl_properties: Optional[str] = None
partitions: Optional[List[ExternalPartition]] = None
def __bool__(self):
return self.location is not None
@dataclass
class Quoting(JsonSchemaMixin, Mergeable):
database: Optional[bool] = None
schema: Optional[bool] = None
identifier: Optional[bool] = None
column: Optional[bool] = None
@dataclass
class UnparsedSourceTableDefinition(HasColumnTests, HasTests):
loaded_at_field: Optional[str] = None
identifier: Optional[str] = None
quoting: Quoting = field(default_factory=Quoting)
freshness: Optional[FreshnessThreshold] = field(
default_factory=FreshnessThreshold
)
external: Optional[ExternalTable] = None
tags: List[str] = field(default_factory=list)
def to_dict(self, omit_none=True, validate=False):
result = super().to_dict(omit_none=omit_none, validate=validate)
if omit_none and self.freshness is None:
result['freshness'] = None
return result
@dataclass
class UnparsedSourceDefinition(JsonSchemaMixin, Replaceable):
name: str
description: str = ''
meta: Dict[str, Any] = field(default_factory=dict)
database: Optional[str] = None
schema: Optional[str] = None
loader: str = ''
quoting: Quoting = field(default_factory=Quoting)
freshness: Optional[FreshnessThreshold] = field(
default_factory=FreshnessThreshold
)
loaded_at_field: Optional[str] = None
tables: List[UnparsedSourceTableDefinition] = field(default_factory=list)
tags: List[str] = field(default_factory=list)
@property
def yaml_key(self) -> 'str':
return 'sources'
def to_dict(self, omit_none=True, validate=False):
result = super().to_dict(omit_none=omit_none, validate=validate)
if omit_none and self.freshness is None:
result['freshness'] = None
return result
@dataclass
class SourceTablePatch(JsonSchemaMixin):
name: str
description: Optional[str] = None
meta: Optional[Dict[str, Any]] = None
data_type: Optional[str] = None
docs: Optional[Docs] = None
loaded_at_field: Optional[str] = None
identifier: Optional[str] = None
quoting: Quoting = field(default_factory=Quoting)
freshness: Optional[FreshnessThreshold] = field(
default_factory=FreshnessThreshold
)
external: Optional[ExternalTable] = None
tags: Optional[List[str]] = None
tests: Optional[List[TestDef]] = None
columns: Optional[Sequence[UnparsedColumn]] = None
def to_patch_dict(self) -> Dict[str, Any]:
dct = self.to_dict(omit_none=True)
remove_keys = ('name')
for key in remove_keys:
if key in dct:
del dct[key]
if self.freshness is None:
dct['freshness'] = None
return dct
@dataclass
class SourcePatch(JsonSchemaMixin, Replaceable):
name: str = field(
metadata=dict(description='The name of the source to override'),
)
overrides: str = field(
metadata=dict(description='The package of the source to override'),
)
path: Path = field(
metadata=dict(description='The path to the patch-defining yml file'),
)
description: Optional[str] = None
meta: Optional[Dict[str, Any]] = None
database: Optional[str] = None
schema: Optional[str] = None
loader: Optional[str] = None
quoting: Optional[Quoting] = None
freshness: Optional[Optional[FreshnessThreshold]] = field(
default_factory=FreshnessThreshold
)
loaded_at_field: Optional[str] = None
tables: Optional[List[SourceTablePatch]] = None
tags: Optional[List[str]] = None
def to_patch_dict(self) -> Dict[str, Any]:
dct = self.to_dict(omit_none=True)
remove_keys = ('name', 'overrides', 'tables', 'path')
for key in remove_keys:
if key in dct:
del dct[key]
if self.freshness is None:
dct['freshness'] = None
return dct
def get_table_named(self, name: str) -> Optional[SourceTablePatch]:
if self.tables is not None:
for table in self.tables:
if table.name == name:
return table
return None
@dataclass
class UnparsedDocumentation(JsonSchemaMixin, Replaceable):
package_name: str
root_path: str
path: str
original_file_path: str
@property
def resource_type(self):
return NodeType.Documentation
@dataclass
class UnparsedDocumentationFile(UnparsedDocumentation):
file_contents: str
# can't use total_ordering decorator here, as str provides an ordering already
# and it's not the one we want.
class Maturity(StrEnum):
low = 'low'
medium = 'medium'
high = 'high'
def __lt__(self, other):
if not isinstance(other, Maturity):
return NotImplemented
order = (Maturity.low, Maturity.medium, Maturity.high)
return order.index(self) < order.index(other)
def __gt__(self, other):
if not isinstance(other, Maturity):
return NotImplemented
return self != other and not (self < other)
def __ge__(self, other):
if not isinstance(other, Maturity):
return NotImplemented
return self == other or not (self < other)
def __le__(self, other):
if not isinstance(other, Maturity):
return NotImplemented
return self == other or self < other
class ExposureType(StrEnum):
Dashboard = 'dashboard'
Notebook = 'notebook'
Analysis = 'analysis'
ML = 'ml'
Application = 'application'
class MaturityType(StrEnum):
Low = 'low'
Medium = 'medium'
High = 'high'
@dataclass
class ExposureOwner(JsonSchemaMixin, Replaceable):
email: str
name: Optional[str] = None
@dataclass
class UnparsedExposure(JsonSchemaMixin, Replaceable):
name: str
type: ExposureType
owner: ExposureOwner
maturity: Optional[MaturityType] = None
url: Optional[str] = None
description: Optional[str] = None
depends_on: List[str] = field(default_factory=list)

View File

@@ -0,0 +1,307 @@
from dbt.contracts.util import Replaceable, Mergeable, list_str
from dbt.contracts.connection import UserConfigContract, QueryComment
from dbt.helper_types import NoValue
from dbt.logger import GLOBAL_LOGGER as logger # noqa
from dbt import tracking
from dbt import ui
from hologram import JsonSchemaMixin, ValidationError
from hologram.helpers import HyphenatedJsonSchemaMixin, register_pattern, \
ExtensibleJsonSchemaMixin
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Union, Any, NewType
PIN_PACKAGE_URL = 'https://docs.getdbt.com/docs/package-management#section-specifying-package-versions' # noqa
DEFAULT_SEND_ANONYMOUS_USAGE_STATS = True
Name = NewType('Name', str)
register_pattern(Name, r'^[^\d\W]\w*$')
# this does not support the full semver (does not allow a trailing -fooXYZ) and
# is not restrictive enough for full semver, (allows '1.0'). But it's like
# 'semver lite'.
SemverString = NewType('SemverString', str)
register_pattern(
SemverString,
r'^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(\.(?:0|[1-9]\d*))?$',
)
@dataclass
class Quoting(JsonSchemaMixin, Mergeable):
identifier: Optional[bool]
schema: Optional[bool]
database: Optional[bool]
project: Optional[bool]
@dataclass
class Package(Replaceable, HyphenatedJsonSchemaMixin):
pass
@dataclass
class LocalPackage(Package):
local: str
# `float` also allows `int`, according to PEP484 (and jsonschema!)
RawVersion = Union[str, float]
@dataclass
class GitPackage(Package):
git: str
revision: Optional[RawVersion]
warn_unpinned: Optional[bool] = None
def get_revisions(self) -> List[str]:
if self.revision is None:
return []
else:
return [str(self.revision)]
@dataclass
class RegistryPackage(Package):
package: str
version: Union[RawVersion, List[RawVersion]]
def get_versions(self) -> List[str]:
if isinstance(self.version, list):
return [str(v) for v in self.version]
else:
return [str(self.version)]
PackageSpec = Union[LocalPackage, GitPackage, RegistryPackage]
@dataclass
class PackageConfig(JsonSchemaMixin, Replaceable):
packages: List[PackageSpec]
@dataclass
class ProjectPackageMetadata:
name: str
packages: List[PackageSpec]
@classmethod
def from_project(cls, project):
return cls(name=project.project_name,
packages=project.packages.packages)
@dataclass
class Downloads(ExtensibleJsonSchemaMixin, Replaceable):
tarball: str
@dataclass
class RegistryPackageMetadata(
ExtensibleJsonSchemaMixin,
ProjectPackageMetadata,
):
downloads: Downloads
# A list of all the reserved words that packages may not have as names.
BANNED_PROJECT_NAMES = {
'_sql_results',
'adapter',
'api',
'column',
'config',
'context',
'database',
'env',
'env_var',
'exceptions',
'execute',
'flags',
'fromjson',
'fromyaml',
'graph',
'invocation_id',
'load_agate_table',
'load_result',
'log',
'model',
'modules',
'post_hooks',
'pre_hooks',
'ref',
'render',
'return',
'run_started_at',
'schema',
'source',
'sql',
'sql_now',
'store_result',
'target',
'this',
'tojson',
'toyaml',
'try_or_compiler_error',
'var',
'write',
}
@dataclass
class ProjectV1(HyphenatedJsonSchemaMixin, Replaceable):
name: Name
version: Union[SemverString, float]
project_root: Optional[str] = None
source_paths: Optional[List[str]] = None
macro_paths: Optional[List[str]] = None
data_paths: Optional[List[str]] = None
test_paths: Optional[List[str]] = None
analysis_paths: Optional[List[str]] = None
docs_paths: Optional[List[str]] = None
asset_paths: Optional[List[str]] = None
target_path: Optional[str] = None
snapshot_paths: Optional[List[str]] = None
clean_targets: Optional[List[str]] = None
profile: Optional[str] = None
log_path: Optional[str] = None
modules_path: Optional[str] = None
quoting: Optional[Quoting] = None
on_run_start: Optional[List[str]] = field(default_factory=list_str)
on_run_end: Optional[List[str]] = field(default_factory=list_str)
require_dbt_version: Optional[Union[List[str], str]] = None
models: Dict[str, Any] = field(default_factory=dict)
seeds: Dict[str, Any] = field(default_factory=dict)
snapshots: Dict[str, Any] = field(default_factory=dict)
packages: List[PackageSpec] = field(default_factory=list)
query_comment: Optional[Union[QueryComment, NoValue, str]] = NoValue()
config_version: int = 1
@classmethod
def from_dict(cls, data, validate=True) -> 'ProjectV1':
result = super().from_dict(data, validate=validate)
if result.name in BANNED_PROJECT_NAMES:
raise ValidationError(
'Invalid project name: {} is a reserved word'
.format(result.name)
)
return result
@dataclass
class ProjectV2(HyphenatedJsonSchemaMixin, Replaceable):
name: Name
version: Union[SemverString, float]
config_version: int
project_root: Optional[str] = None
source_paths: Optional[List[str]] = None
macro_paths: Optional[List[str]] = None
data_paths: Optional[List[str]] = None
test_paths: Optional[List[str]] = None
analysis_paths: Optional[List[str]] = None
docs_paths: Optional[List[str]] = None
asset_paths: Optional[List[str]] = None
target_path: Optional[str] = None
snapshot_paths: Optional[List[str]] = None
clean_targets: Optional[List[str]] = None
profile: Optional[str] = None
log_path: Optional[str] = None
modules_path: Optional[str] = None
quoting: Optional[Quoting] = None
on_run_start: Optional[List[str]] = field(default_factory=list_str)
on_run_end: Optional[List[str]] = field(default_factory=list_str)
require_dbt_version: Optional[Union[List[str], str]] = None
models: Dict[str, Any] = field(default_factory=dict)
seeds: Dict[str, Any] = field(default_factory=dict)
snapshots: Dict[str, Any] = field(default_factory=dict)
analyses: Dict[str, Any] = field(default_factory=dict)
sources: Dict[str, Any] = field(default_factory=dict)
vars: Optional[Dict[str, Any]] = field(
default=None,
metadata=dict(
description='map project names to their vars override dicts',
),
)
packages: List[PackageSpec] = field(default_factory=list)
query_comment: Optional[Union[QueryComment, NoValue, str]] = NoValue()
@classmethod
def from_dict(cls, data, validate=True) -> 'ProjectV2':
result = super().from_dict(data, validate=validate)
if result.name in BANNED_PROJECT_NAMES:
raise ValidationError(
f'Invalid project name: {result.name} is a reserved word'
)
return result
def parse_project_config(
data: Dict[str, Any], validate=True
) -> Union[ProjectV1, ProjectV2]:
config_version = data.get('config-version', 1)
if config_version == 1:
return ProjectV1.from_dict(data, validate=validate)
elif config_version == 2:
return ProjectV2.from_dict(data, validate=validate)
else:
raise ValidationError(
f'Got an unexpected config-version={config_version}, expected '
f'1 or 2'
)
@dataclass
class UserConfig(ExtensibleJsonSchemaMixin, Replaceable, UserConfigContract):
send_anonymous_usage_stats: bool = DEFAULT_SEND_ANONYMOUS_USAGE_STATS
use_colors: Optional[bool] = None
partial_parse: Optional[bool] = None
printer_width: Optional[int] = None
def set_values(self, cookie_dir):
if self.send_anonymous_usage_stats:
tracking.initialize_tracking(cookie_dir)
else:
tracking.do_not_track()
if self.use_colors is not None:
ui.use_colors(self.use_colors)
if self.printer_width:
ui.printer_width(self.printer_width)
@dataclass
class ProfileConfig(HyphenatedJsonSchemaMixin, Replaceable):
profile_name: str = field(metadata={'preserve_underscore': True})
target_name: str = field(metadata={'preserve_underscore': True})
config: UserConfig
threads: int
# TODO: make this a dynamic union of some kind?
credentials: Optional[Dict[str, Any]]
@dataclass
class ConfiguredQuoting(Quoting, Replaceable):
identifier: bool
schema: bool
database: Optional[bool]
project: Optional[bool]
@dataclass
class Configuration(ProjectV2, ProfileConfig):
cli_vars: Dict[str, Any] = field(
default_factory=dict,
metadata={'preserve_underscore': True},
)
quoting: Optional[ConfiguredQuoting] = None
@dataclass
class ProjectList(JsonSchemaMixin):
projects: Dict[str, Union[ProjectV2, ProjectV1]]

View File

@@ -0,0 +1,122 @@
from collections.abc import Mapping
from dataclasses import dataclass, fields
from typing import (
Optional, TypeVar, Generic, Dict,
)
from typing_extensions import Protocol
from hologram import JsonSchemaMixin
from hologram.helpers import StrEnum
from dbt import deprecations
from dbt.contracts.util import Replaceable
from dbt.exceptions import CompilationException
from dbt.utils import deep_merge
class RelationType(StrEnum):
Table = 'table'
View = 'view'
CTE = 'cte'
MaterializedView = 'materializedview'
External = 'external'
class ComponentName(StrEnum):
Database = 'database'
Schema = 'schema'
Identifier = 'identifier'
class HasQuoting(Protocol):
quoting: Dict[str, bool]
class FakeAPIObject(JsonSchemaMixin, Replaceable, Mapping):
# override the mapping truthiness, len is always >1
def __bool__(self):
return True
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key) from None
def __iter__(self):
deprecations.warn('not-a-dictionary', obj=self)
for _, name in self._get_fields():
yield name
def __len__(self):
deprecations.warn('not-a-dictionary', obj=self)
return len(fields(self.__class__))
def incorporate(self, **kwargs):
value = self.to_dict()
value = deep_merge(value, kwargs)
return self.from_dict(value)
T = TypeVar('T')
@dataclass
class _ComponentObject(FakeAPIObject, Generic[T]):
database: T
schema: T
identifier: T
def get_part(self, key: ComponentName) -> T:
if key == ComponentName.Database:
return self.database
elif key == ComponentName.Schema:
return self.schema
elif key == ComponentName.Identifier:
return self.identifier
else:
raise ValueError(
'Got a key of {}, expected one of {}'
.format(key, list(ComponentName))
)
def replace_dict(self, dct: Dict[ComponentName, T]):
kwargs: Dict[str, T] = {}
for k, v in dct.items():
kwargs[str(k)] = v
return self.replace(**kwargs)
@dataclass
class Policy(_ComponentObject[bool]):
database: bool = True
schema: bool = True
identifier: bool = True
@dataclass
class Path(_ComponentObject[Optional[str]]):
database: Optional[str]
schema: Optional[str]
identifier: Optional[str]
def __post_init__(self):
# handle pesky jinja2.Undefined sneaking in here and messing up rende
if not isinstance(self.database, (type(None), str)):
raise CompilationException(
'Got an invalid path database: {}'.format(self.database)
)
if not isinstance(self.schema, (type(None), str)):
raise CompilationException(
'Got an invalid path schema: {}'.format(self.schema)
)
if not isinstance(self.identifier, (type(None), str)):
raise CompilationException(
'Got an invalid path identifier: {}'.format(self.identifier)
)
def get_lowered_part(self, key: ComponentName) -> Optional[str]:
part = self.get_part(key)
if part is not None:
part = part.lower()
return part

View File

@@ -0,0 +1,301 @@
from dbt.contracts.graph.manifest import CompileResultNode
from dbt.contracts.graph.unparsed import (
Time, FreshnessStatus, FreshnessThreshold
)
from dbt.contracts.graph.parsed import ParsedSourceDefinition
from dbt.contracts.util import Writable, Replaceable
from dbt.exceptions import InternalException
from dbt.logger import (
TimingProcessor,
JsonOnly,
GLOBAL_LOGGER as logger,
)
from dbt.utils import lowercase
from hologram.helpers import StrEnum
from hologram import JsonSchemaMixin
import agate
from dataclasses import dataclass, field
from datetime import datetime
from typing import Union, Dict, List, Optional, Any, NamedTuple
@dataclass
class TimingInfo(JsonSchemaMixin):
name: str
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
def begin(self):
self.started_at = datetime.utcnow()
def end(self):
self.completed_at = datetime.utcnow()
class collect_timing_info:
def __init__(self, name: str):
self.timing_info = TimingInfo(name=name)
def __enter__(self):
self.timing_info.begin()
return self.timing_info
def __exit__(self, exc_type, exc_value, traceback):
self.timing_info.end()
with JsonOnly(), TimingProcessor(self.timing_info):
logger.debug('finished collecting timing info')
@dataclass
class PartialResult(JsonSchemaMixin, Writable):
node: CompileResultNode
error: Optional[str] = None
status: Union[None, str, int, bool] = None
execution_time: Union[str, int] = 0
thread_id: Optional[str] = None
timing: List[TimingInfo] = field(default_factory=list)
fail: Optional[bool] = None
warn: Optional[bool] = None
# if the result got to the point where it could be skipped/failed, we would
# be returning a real result, not a partial.
@property
def skipped(self):
return False
@dataclass
class WritableRunModelResult(PartialResult):
skip: bool = False
@property
def skipped(self):
return self.skip
@dataclass
class RunModelResult(WritableRunModelResult):
agate_table: Optional[agate.Table] = None
def to_dict(self, *args, **kwargs):
dct = super().to_dict(*args, **kwargs)
dct.pop('agate_table', None)
return dct
@dataclass
class ExecutionResult(JsonSchemaMixin, Writable):
results: List[Union[WritableRunModelResult, PartialResult]]
generated_at: datetime
elapsed_time: float
def __len__(self):
return len(self.results)
def __iter__(self):
return iter(self.results)
def __getitem__(self, idx):
return self.results[idx]
@dataclass
class RunOperationResult(ExecutionResult):
success: bool
# due to issues with typing.Union collapsing subclasses, this can't subclass
# PartialResult
@dataclass
class SourceFreshnessResult(JsonSchemaMixin, Writable):
node: ParsedSourceDefinition
max_loaded_at: datetime
snapshotted_at: datetime
age: float
status: FreshnessStatus
error: Optional[str] = None
execution_time: Union[str, int] = 0
thread_id: Optional[str] = None
timing: List[TimingInfo] = field(default_factory=list)
fail: Optional[bool] = None
def __post_init__(self):
self.fail = self.status == 'error'
@property
def warned(self):
return self.status == 'warn'
@property
def skipped(self):
return False
@dataclass
class FreshnessMetadata(JsonSchemaMixin):
generated_at: datetime
elapsed_time: float
@dataclass
class FreshnessExecutionResult(FreshnessMetadata):
results: List[Union[PartialResult, SourceFreshnessResult]]
def write(self, path, omit_none=True):
"""Create a new object with the desired output schema and write it."""
meta = FreshnessMetadata(
generated_at=self.generated_at,
elapsed_time=self.elapsed_time,
)
sources = {}
for result in self.results:
result_value: Union[
SourceFreshnessRuntimeError, SourceFreshnessOutput
]
unique_id = result.node.unique_id
if result.error is not None:
result_value = SourceFreshnessRuntimeError(
error=result.error,
state=FreshnessErrorEnum.runtime_error,
)
else:
# we know that this must be a SourceFreshnessResult
if not isinstance(result, SourceFreshnessResult):
raise InternalException(
'Got {} instead of a SourceFreshnessResult for a '
'non-error result in freshness execution!'
.format(type(result))
)
# if we're here, we must have a non-None freshness threshold
criteria = result.node.freshness
if criteria is None:
raise InternalException(
'Somehow evaluated a freshness result for a source '
'that has no freshness criteria!'
)
result_value = SourceFreshnessOutput(
max_loaded_at=result.max_loaded_at,
snapshotted_at=result.snapshotted_at,
max_loaded_at_time_ago_in_s=result.age,
state=result.status,
criteria=criteria,
)
sources[unique_id] = result_value
output = FreshnessRunOutput(meta=meta, sources=sources)
output.write(path, omit_none=omit_none)
def __len__(self):
return len(self.results)
def __iter__(self):
return iter(self.results)
def __getitem__(self, idx):
return self.results[idx]
def _copykeys(src, keys, **updates):
return {k: getattr(src, k) for k in keys}
@dataclass
class FreshnessCriteria(JsonSchemaMixin):
warn_after: Time
error_after: Time
class FreshnessErrorEnum(StrEnum):
runtime_error = 'runtime error'
@dataclass
class SourceFreshnessRuntimeError(JsonSchemaMixin):
error: str
state: FreshnessErrorEnum
@dataclass
class SourceFreshnessOutput(JsonSchemaMixin):
max_loaded_at: datetime
snapshotted_at: datetime
max_loaded_at_time_ago_in_s: float
state: FreshnessStatus
criteria: FreshnessThreshold
SourceFreshnessRunResult = Union[SourceFreshnessOutput,
SourceFreshnessRuntimeError]
@dataclass
class FreshnessRunOutput(JsonSchemaMixin, Writable):
meta: FreshnessMetadata
sources: Dict[str, SourceFreshnessRunResult]
Primitive = Union[bool, str, float, None]
CatalogKey = NamedTuple(
'CatalogKey',
[('database', Optional[str]), ('schema', str), ('name', str)]
)
@dataclass
class StatsItem(JsonSchemaMixin):
id: str
label: str
value: Primitive
description: Optional[str]
include: bool
StatsDict = Dict[str, StatsItem]
@dataclass
class ColumnMetadata(JsonSchemaMixin):
type: str
comment: Optional[str]
index: int
name: str
ColumnMap = Dict[str, ColumnMetadata]
@dataclass
class TableMetadata(JsonSchemaMixin):
type: str
database: Optional[str]
schema: str
name: str
comment: Optional[str]
owner: Optional[str]
@dataclass
class CatalogTable(JsonSchemaMixin, Replaceable):
metadata: TableMetadata
columns: ColumnMap
stats: StatsDict
# the same table with two unique IDs will just be listed two times
unique_id: Optional[str] = None
def key(self) -> CatalogKey:
return CatalogKey(
lowercase(self.metadata.database),
self.metadata.schema.lower(),
self.metadata.name.lower(),
)
@dataclass
class CatalogResults(JsonSchemaMixin, Writable):
nodes: Dict[str, CatalogTable]
sources: Dict[str, CatalogTable]
generated_at: datetime
errors: Optional[List[str]]
_compile_results: Optional[Any] = None

585
core/dbt/contracts/rpc.py Normal file
View File

@@ -0,0 +1,585 @@
import enum
import os
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional, Union, List, Any, Dict, Type
from hologram import JsonSchemaMixin
from hologram.helpers import StrEnum
from dbt.contracts.graph.compiled import CompileResultNode
from dbt.contracts.graph.manifest import WritableManifest
from dbt.contracts.results import (
TimingInfo,
CatalogResults,
ExecutionResult,
)
from dbt.exceptions import InternalException
from dbt.logger import LogMessage
from dbt.utils import restrict_to
TaskTags = Optional[Dict[str, Any]]
TaskID = uuid.UUID
# Inputs
@dataclass
class RPCParameters(JsonSchemaMixin):
timeout: Optional[float]
task_tags: TaskTags
@dataclass
class RPCExecParameters(RPCParameters):
name: str
sql: str
macros: Optional[str]
@dataclass
class RPCCompileParameters(RPCParameters):
threads: Optional[int] = None
models: Union[None, str, List[str]] = None
exclude: Union[None, str, List[str]] = None
selector: Optional[str] = None
@dataclass
class RPCSnapshotParameters(RPCParameters):
threads: Optional[int] = None
select: Union[None, str, List[str]] = None
exclude: Union[None, str, List[str]] = None
selector: Optional[str] = None
@dataclass
class RPCTestParameters(RPCCompileParameters):
data: bool = False
schema: bool = False
@dataclass
class RPCSeedParameters(RPCParameters):
threads: Optional[int] = None
select: Union[None, str, List[str]] = None
exclude: Union[None, str, List[str]] = None
selector: Optional[str] = None
show: bool = False
@dataclass
class RPCDocsGenerateParameters(RPCParameters):
compile: bool = True
@dataclass
class RPCCliParameters(RPCParameters):
cli: str
@dataclass
class RPCNoParameters(RPCParameters):
pass
@dataclass
class KillParameters(RPCParameters):
task_id: TaskID
@dataclass
class PollParameters(RPCParameters):
request_token: TaskID
logs: bool = True
logs_start: int = 0
@dataclass
class PSParameters(RPCParameters):
active: bool = True
completed: bool = False
@dataclass
class StatusParameters(RPCParameters):
pass
@dataclass
class GCSettings(JsonSchemaMixin):
# start evicting the longest-ago-ended tasks here
maxsize: int
# start evicting all tasks before now - auto_reap_age when we have this
# many tasks in the table
reapsize: int
# a positive timedelta indicating how far back we should go
auto_reap_age: timedelta
@dataclass
class GCParameters(RPCParameters):
"""The gc endpoint takes three arguments, any of which may be present:
- task_ids: An optional list of task ID UUIDs to try to GC
- before: If provided, should be a datetime string. All tasks that finished
before that datetime will be GCed
- settings: If provided, should be a GCSettings object in JSON form. It
will be applied to the task manager before GC starts. By default the
existing gc settings remain.
"""
task_ids: Optional[List[TaskID]] = None
before: Optional[datetime] = None
settings: Optional[GCSettings] = None
@dataclass
class RPCRunOperationParameters(RPCParameters):
macro: str
args: Dict[str, Any] = field(default_factory=dict)
@dataclass
class RPCSourceFreshnessParameters(RPCParameters):
threads: Optional[int] = None
select: Union[None, str, List[str]] = None
@dataclass
class GetManifestParameters(RPCParameters):
pass
# Outputs
@dataclass
class RemoteResult(JsonSchemaMixin):
logs: List[LogMessage]
@dataclass
class RemoteEmptyResult(RemoteResult):
pass
@dataclass
class RemoteCatalogResults(CatalogResults, RemoteResult):
pass
@dataclass
class RemoteCompileResult(RemoteResult):
raw_sql: str
compiled_sql: str
node: CompileResultNode
timing: List[TimingInfo]
@property
def error(self):
return None
@dataclass
class RemoteExecutionResult(ExecutionResult, RemoteResult):
pass
@dataclass
class ResultTable(JsonSchemaMixin):
column_names: List[str]
rows: List[Any]
@dataclass
class RemoteRunOperationResult(ExecutionResult, RemoteResult):
success: bool
@dataclass
class RemoteRunResult(RemoteCompileResult):
table: ResultTable
RPCResult = Union[
RemoteCompileResult,
RemoteExecutionResult,
RemoteCatalogResults,
RemoteEmptyResult,
RemoteRunOperationResult,
]
# GC types
class GCResultState(StrEnum):
Deleted = 'deleted' # successful GC
Missing = 'missing' # nothing to GC
Running = 'running' # can't GC
@dataclass
class GCResult(RemoteResult):
logs: List[LogMessage] = field(default_factory=list)
deleted: List[TaskID] = field(default_factory=list)
missing: List[TaskID] = field(default_factory=list)
running: List[TaskID] = field(default_factory=list)
def add_result(self, task_id: TaskID, state: GCResultState):
if state == GCResultState.Missing:
self.missing.append(task_id)
elif state == GCResultState.Running:
self.running.append(task_id)
elif state == GCResultState.Deleted:
self.deleted.append(task_id)
else:
raise InternalException(
f'Got invalid state in add_result: {state}'
)
# Task management types
class TaskHandlerState(StrEnum):
NotStarted = 'not started'
Initializing = 'initializing'
Running = 'running'
Success = 'success'
Error = 'error'
Killed = 'killed'
Failed = 'failed'
def __lt__(self, other) -> bool:
"""A logical ordering for TaskHandlerState:
NotStarted < Initializing < Running < (Success, Error, Killed, Failed)
"""
if not isinstance(other, TaskHandlerState):
raise TypeError('cannot compare to non-TaskHandlerState')
order = (self.NotStarted, self.Initializing, self.Running)
smaller = set()
for value in order:
smaller.add(value)
if self == value:
return other not in smaller
return False
def __le__(self, other) -> bool:
# so that ((Success <= Error) is True)
return ((self < other) or
(self == other) or
(self.finished and other.finished))
def __gt__(self, other) -> bool:
if not isinstance(other, TaskHandlerState):
raise TypeError('cannot compare to non-TaskHandlerState')
order = (self.NotStarted, self.Initializing, self.Running)
smaller = set()
for value in order:
smaller.add(value)
if self == value:
return other in smaller
return other in smaller
def __ge__(self, other) -> bool:
# so that ((Success <= Error) is True)
return ((self > other) or
(self == other) or
(self.finished and other.finished))
@property
def finished(self) -> bool:
return self in (self.Error, self.Success, self.Killed, self.Failed)
@dataclass
class TaskTiming(JsonSchemaMixin):
state: TaskHandlerState
start: Optional[datetime]
end: Optional[datetime]
elapsed: Optional[float]
@dataclass
class TaskRow(TaskTiming):
task_id: TaskID
request_id: Union[str, int]
request_source: str
method: str
timeout: Optional[float]
tags: TaskTags
@dataclass
class PSResult(RemoteResult):
rows: List[TaskRow]
class KillResultStatus(StrEnum):
Missing = 'missing'
NotStarted = 'not_started'
Killed = 'killed'
Finished = 'finished'
@dataclass
class KillResult(RemoteResult):
state: KillResultStatus = KillResultStatus.Missing
logs: List[LogMessage] = field(default_factory=list)
@dataclass
class GetManifestResult(RemoteResult):
manifest: Optional[WritableManifest]
# this is kind of carefuly structured: BlocksManifestTasks is implied by
# RequiresConfigReloadBefore and RequiresManifestReloadAfter
class RemoteMethodFlags(enum.Flag):
Empty = 0
BlocksManifestTasks = 1
RequiresConfigReloadBefore = 3
RequiresManifestReloadAfter = 5
Builtin = 8
# Polling types
@dataclass
class PollResult(RemoteResult, TaskTiming):
state: TaskHandlerState
tags: TaskTags
start: Optional[datetime]
end: Optional[datetime]
elapsed: Optional[float]
@dataclass
class PollRemoteEmptyCompleteResult(PollResult, RemoteEmptyResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollRemoteEmptyCompleteResult'],
base: RemoteEmptyResult,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollRemoteEmptyCompleteResult':
return cls(
logs=logs,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
@dataclass
class PollKilledResult(PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Killed),
)
@dataclass
class PollExecuteCompleteResult(RemoteExecutionResult, PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollExecuteCompleteResult'],
base: RemoteExecutionResult,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollExecuteCompleteResult':
return cls(
results=base.results,
generated_at=base.generated_at,
elapsed_time=base.elapsed_time,
logs=logs,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
@dataclass
class PollCompileCompleteResult(RemoteCompileResult, PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollCompileCompleteResult'],
base: RemoteCompileResult,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollCompileCompleteResult':
return cls(
raw_sql=base.raw_sql,
compiled_sql=base.compiled_sql,
node=base.node,
timing=base.timing,
logs=logs,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
@dataclass
class PollRunCompleteResult(RemoteRunResult, PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollRunCompleteResult'],
base: RemoteRunResult,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollRunCompleteResult':
return cls(
raw_sql=base.raw_sql,
compiled_sql=base.compiled_sql,
node=base.node,
timing=base.timing,
logs=logs,
table=base.table,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
@dataclass
class PollRunOperationCompleteResult(RemoteRunOperationResult, PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollRunOperationCompleteResult'],
base: RemoteRunOperationResult,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollRunOperationCompleteResult':
return cls(
success=base.success,
results=base.results,
generated_at=base.generated_at,
elapsed_time=base.elapsed_time,
logs=logs,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
@dataclass
class PollCatalogCompleteResult(RemoteCatalogResults, PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollCatalogCompleteResult'],
base: RemoteCatalogResults,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollCatalogCompleteResult':
return cls(
nodes=base.nodes,
sources=base.sources,
generated_at=base.generated_at,
errors=base.errors,
_compile_results=base._compile_results,
logs=logs,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
@dataclass
class PollInProgressResult(PollResult):
pass
@dataclass
class PollGetManifestResult(GetManifestResult, PollResult):
state: TaskHandlerState = field(
metadata=restrict_to(TaskHandlerState.Success,
TaskHandlerState.Failed),
)
@classmethod
def from_result(
cls: Type['PollGetManifestResult'],
base: GetManifestResult,
tags: TaskTags,
timing: TaskTiming,
logs: List[LogMessage],
) -> 'PollGetManifestResult':
return cls(
manifest=base.manifest,
logs=logs,
tags=tags,
state=timing.state,
start=timing.start,
end=timing.end,
elapsed=timing.elapsed,
)
# Manifest parsing types
class ManifestStatus(StrEnum):
Init = 'init'
Compiling = 'compiling'
Ready = 'ready'
Error = 'error'
@dataclass
class LastParse(RemoteResult):
state: ManifestStatus = ManifestStatus.Init
logs: List[LogMessage] = field(default_factory=list)
error: Optional[Dict[str, Any]] = None
timestamp: datetime = field(default_factory=datetime.utcnow)
pid: int = field(default_factory=os.getpid)

View File

@@ -0,0 +1,21 @@
from dataclasses import dataclass
from hologram import JsonSchemaMixin
from typing import List, Dict, Any, Union
@dataclass
class SelectorDefinition(JsonSchemaMixin):
name: str
definition: Union[str, Dict[str, Any]]
@dataclass
class SelectorFile(JsonSchemaMixin):
selectors: List[SelectorDefinition]
version: int = 2
# @dataclass
# class SelectorCollection:
# packages: Dict[str, List[SelectorFile]] = field(default_factory=dict)

View File

@@ -0,0 +1,13 @@
from pathlib import Path
from .graph.manifest import WritableManifest
from typing import Optional
class PreviousState:
def __init__(self, path: Path):
self.path: Path = path
self.manifest: Optional[WritableManifest] = None
manifest_path = self.path / 'manifest.json'
if manifest_path.exists() and manifest_path.is_file():
self.manifest = WritableManifest.read(str(manifest_path))

View File

@@ -0,0 +1,96 @@
import dataclasses
from typing import List, Tuple
from dbt.clients.system import write_json, read_json
from dbt.exceptions import RuntimeException
MacroKey = Tuple[str, str]
SourceKey = Tuple[str, str]
def list_str() -> List[str]:
"""Mypy gets upset about stuff like:
from dataclasses import dataclass, field
from typing import Optional, List
@dataclass
class Foo:
x: Optional[List[str]] = field(default_factory=list)
Because `list` could be any kind of list, I guess
"""
return []
class Replaceable:
def replace(self, **kwargs):
return dataclasses.replace(self, **kwargs)
class Mergeable(Replaceable):
def merged(self, *args):
"""Perform a shallow merge, where the last non-None write wins. This is
intended to merge dataclasses that are a collection of optional values.
"""
replacements = {}
cls = type(self)
for arg in args:
for field in dataclasses.fields(cls):
value = getattr(arg, field.name)
if value is not None:
replacements[field.name] = value
return self.replace(**replacements)
class Writable:
def write(self, path: str, omit_none: bool = False):
write_json(path, self.to_dict(omit_none=omit_none)) # type: ignore
class AdditionalPropertiesMixin:
"""Make this class an extensible property.
The underlying class definition must include a type definition for a field
named '_extra' that is of type `Dict[str, Any]`.
"""
ADDITIONAL_PROPERTIES = True
@classmethod
def from_dict(cls, data, validate=True):
self = super().from_dict(data=data, validate=validate)
keys = self.to_dict(validate=False, omit_none=False)
for key, value in data.items():
if key not in keys:
self.extra[key] = value
return self
def to_dict(self, omit_none=True, validate=False):
data = super().to_dict(omit_none=omit_none, validate=validate)
data.update(self.extra)
return data
def replace(self, **kwargs):
dct = self.to_dict(omit_none=False, validate=False)
dct.update(kwargs)
return self.from_dict(dct)
@property
def extra(self):
return self._extra
class Readable:
@classmethod
def read(cls, path: str):
try:
data = read_json(path)
except (EnvironmentError, ValueError) as exc:
raise RuntimeException(
f'Could not read {cls.__name__} at "{path}" as JSON: {exc}'
) from exc
return cls.from_dict(data) # type: ignore

190
core/dbt/deprecations.py Normal file
View File

@@ -0,0 +1,190 @@
from typing import Optional, Set, List, Dict, ClassVar
import dbt.exceptions
from dbt import ui
import dbt.tracking
class DBTDeprecation:
_name: ClassVar[Optional[str]] = None
_description: ClassVar[Optional[str]] = None
@property
def name(self) -> str:
if self._name is not None:
return self._name
raise NotImplementedError(
'name not implemented for {}'.format(self)
)
def track_deprecation_warn(self) -> None:
if dbt.tracking.active_user is not None:
dbt.tracking.track_deprecation_warn({
"deprecation_name": self.name
})
@property
def description(self) -> str:
if self._description is not None:
return self._description
raise NotImplementedError(
'description not implemented for {}'.format(self)
)
def show(self, *args, **kwargs) -> None:
if self.name not in active_deprecations:
desc = self.description.format(**kwargs)
msg = ui.line_wrap_message(
desc, prefix='* Deprecation Warning: '
)
dbt.exceptions.warn_or_error(msg)
self.track_deprecation_warn()
active_deprecations.add(self.name)
class MaterializationReturnDeprecation(DBTDeprecation):
_name = 'materialization-return'
_description = '''\
The materialization ("{materialization}") did not explicitly return a list
of relations to add to the cache. By default the target relation will be
added, but this behavior will be removed in a future version of dbt.
For more information, see:
https://docs.getdbt.com/v0.15/docs/creating-new-materializations#section-6-returning-relations
'''
class NotADictionaryDeprecation(DBTDeprecation):
_name = 'not-a-dictionary'
_description = '''\
The object ("{obj}") was used as a dictionary. In a future version of dbt
this capability will be removed from objects of this type.
'''
class ColumnQuotingDeprecation(DBTDeprecation):
_name = 'column-quoting-unset'
_description = '''\
The quote_columns parameter was not set for seeds, so the default value of
False was chosen. The default will change to True in a future release.
For more information, see:
https://docs.getdbt.com/v0.15/docs/seeds#section-specify-column-quoting
'''
class ModelsKeyNonModelDeprecation(DBTDeprecation):
_name = 'models-key-mismatch'
_description = '''\
"{node.name}" is a {node.resource_type} node, but it is specified in
the {patch.yaml_key} section of {patch.original_file_path}.
To fix this warning, place the `{node.name}` specification under
the {expected_key} key instead.
This warning will become an error in a future release.
'''
class DbtProjectYamlDeprecation(DBTDeprecation):
_name = 'dbt-project-yaml-v1'
_description = '''\
dbt v0.17.0 introduces a new config format for the dbt_project.yml file.
Support for the existing version 1 format will be removed in a future
release of dbt. The following packages are currently configured with
config version 1:{project_names}
For upgrading instructions, consult the documentation:
https://docs.getdbt.com/docs/guides/migration-guide/upgrading-to-0-17-0
'''
class ExecuteMacrosReleaseDeprecation(DBTDeprecation):
_name = 'execute-macro-release'
_description = '''\
The "release" argument to execute_macro is now ignored, and will be removed
in a future relase of dbt. At that time, providing a `release` argument
will result in an error.
'''
class AdapterMacroDeprecation(DBTDeprecation):
_name = 'adapter-macro'
_description = '''\
The "adapter_macro" macro has been deprecated. Instead, use the
`adapter.dispatch` method to find a macro and call the result.
adapter_macro was called for: {macro_name}
'''
_adapter_renamed_description = """\
The adapter function `adapter.{old_name}` is deprecated and will be removed in
a future release of dbt. Please use `adapter.{new_name}` instead.
Documentation for {new_name} can be found here:
https://docs.getdbt.com/docs/adapter
"""
def renamed_method(old_name: str, new_name: str):
class AdapterDeprecationWarning(DBTDeprecation):
_name = 'adapter:{}'.format(old_name)
_description = _adapter_renamed_description.format(old_name=old_name,
new_name=new_name)
dep = AdapterDeprecationWarning()
deprecations_list.append(dep)
deprecations[dep.name] = dep
def warn(name, *args, **kwargs):
if name not in deprecations:
# this should (hopefully) never happen
raise RuntimeError(
"Error showing deprecation warning: {}".format(name)
)
deprecations[name].show(*args, **kwargs)
# these are globally available
# since modules are only imported once, active_deprecations is a singleton
active_deprecations: Set[str] = set()
deprecations_list: List[DBTDeprecation] = [
MaterializationReturnDeprecation(),
NotADictionaryDeprecation(),
ColumnQuotingDeprecation(),
ModelsKeyNonModelDeprecation(),
DbtProjectYamlDeprecation(),
ExecuteMacrosReleaseDeprecation(),
AdapterMacroDeprecation(),
]
deprecations: Dict[str, DBTDeprecation] = {
d.name: d for d in deprecations_list
}
def reset_deprecations():
active_deprecations.clear()

112
core/dbt/deps/base.py Normal file
View File

@@ -0,0 +1,112 @@
import abc
import os
import tempfile
from contextlib import contextmanager
from typing import List, Optional, Generic, TypeVar
from dbt.clients import system
from dbt.contracts.project import ProjectPackageMetadata
from dbt.logger import GLOBAL_LOGGER as logger
DOWNLOADS_PATH = None
def get_downloads_path():
return DOWNLOADS_PATH
@contextmanager
def downloads_directory():
global DOWNLOADS_PATH
remove_downloads = False
# the user might have set an environment variable. Set it to that, and do
# not remove it when finished.
if DOWNLOADS_PATH is None:
DOWNLOADS_PATH = os.getenv('DBT_DOWNLOADS_DIR')
remove_downloads = False
# if we are making a per-run temp directory, remove it at the end of
# successful runs
if DOWNLOADS_PATH is None:
DOWNLOADS_PATH = tempfile.mkdtemp(prefix='dbt-downloads-')
remove_downloads = True
system.make_directory(DOWNLOADS_PATH)
logger.debug("Set downloads directory='{}'".format(DOWNLOADS_PATH))
yield DOWNLOADS_PATH
if remove_downloads:
system.rmtree(DOWNLOADS_PATH)
DOWNLOADS_PATH = None
class BasePackage(metaclass=abc.ABCMeta):
@abc.abstractproperty
def name(self) -> str:
raise NotImplementedError
def all_names(self) -> List[str]:
return [self.name]
@abc.abstractmethod
def source_type(self) -> str:
raise NotImplementedError
class PinnedPackage(BasePackage):
def __init__(self) -> None:
self._cached_metadata: Optional[ProjectPackageMetadata] = None
def __str__(self) -> str:
version = self.get_version()
if not version:
return self.name
return '{}@{}'.format(self.name, version)
@abc.abstractmethod
def get_version(self) -> Optional[str]:
raise NotImplementedError
@abc.abstractmethod
def _fetch_metadata(self, project, renderer):
raise NotImplementedError
@abc.abstractmethod
def install(self, project):
raise NotImplementedError
@abc.abstractmethod
def nice_version_name(self):
raise NotImplementedError
def fetch_metadata(self, project, renderer):
if not self._cached_metadata:
self._cached_metadata = self._fetch_metadata(project, renderer)
return self._cached_metadata
def get_project_name(self, project, renderer):
metadata = self.fetch_metadata(project, renderer)
return metadata.name
def get_installation_path(self, project, renderer):
dest_dirname = self.get_project_name(project, renderer)
return os.path.join(project.modules_path, dest_dirname)
SomePinned = TypeVar('SomePinned', bound=PinnedPackage)
SomeUnpinned = TypeVar('SomeUnpinned', bound='UnpinnedPackage')
class UnpinnedPackage(Generic[SomePinned], BasePackage):
@abc.abstractclassmethod
def from_contract(cls, contract):
raise NotImplementedError
@abc.abstractmethod
def incorporate(self: SomeUnpinned, other: SomeUnpinned) -> SomeUnpinned:
raise NotImplementedError
@abc.abstractmethod
def resolved(self) -> SomePinned:
raise NotImplementedError

145
core/dbt/deps/git.py Normal file
View File

@@ -0,0 +1,145 @@
import os
import hashlib
from typing import List
from dbt.clients import git, system
from dbt.config import Project
from dbt.contracts.project import (
ProjectPackageMetadata,
GitPackage,
)
from dbt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path
from dbt.exceptions import (
ExecutableError, warn_or_error, raise_dependency_error
)
from dbt.logger import GLOBAL_LOGGER as logger
from dbt import ui
PIN_PACKAGE_URL = 'https://docs.getdbt.com/docs/package-management#section-specifying-package-versions' # noqa
def md5sum(s: str):
return hashlib.md5(s.encode('latin-1')).hexdigest()
class GitPackageMixin:
def __init__(self, git: str) -> None:
super().__init__()
self.git = git
@property
def name(self):
return self.git
def source_type(self) -> str:
return 'git'
class GitPinnedPackage(GitPackageMixin, PinnedPackage):
def __init__(
self, git: str, revision: str, warn_unpinned: bool = True
) -> None:
super().__init__(git)
self.revision = revision
self.warn_unpinned = warn_unpinned
self._checkout_name = md5sum(self.git)
def get_version(self):
return self.revision
def nice_version_name(self):
return 'revision {}'.format(self.revision)
def _checkout(self):
"""Performs a shallow clone of the repository into the downloads
directory. This function can be called repeatedly. If the project has
already been checked out at this version, it will be a no-op. Returns
the path to the checked out directory."""
try:
dir_ = git.clone_and_checkout(
self.git, get_downloads_path(), branch=self.revision,
dirname=self._checkout_name
)
except ExecutableError as exc:
if exc.cmd and exc.cmd[0] == 'git':
logger.error(
'Make sure git is installed on your machine. More '
'information: '
'https://docs.getdbt.com/docs/package-management'
)
raise
return os.path.join(get_downloads_path(), dir_)
def _fetch_metadata(self, project, renderer) -> ProjectPackageMetadata:
path = self._checkout()
if self.revision == 'master' and self.warn_unpinned:
warn_or_error(
'The git package "{}" is not pinned.\n\tThis can introduce '
'breaking changes into your project without warning!\n\nSee {}'
.format(self.git, PIN_PACKAGE_URL),
log_fmt=ui.yellow('WARNING: {}')
)
loaded = Project.from_project_root(path, renderer)
return ProjectPackageMetadata.from_project(loaded)
def install(self, project, renderer):
dest_path = self.get_installation_path(project, renderer)
if os.path.exists(dest_path):
if system.path_is_symlink(dest_path):
system.remove_file(dest_path)
else:
system.rmdir(dest_path)
system.move(self._checkout(), dest_path)
class GitUnpinnedPackage(GitPackageMixin, UnpinnedPackage[GitPinnedPackage]):
def __init__(
self, git: str, revisions: List[str], warn_unpinned: bool = True
) -> None:
super().__init__(git)
self.revisions = revisions
self.warn_unpinned = warn_unpinned
@classmethod
def from_contract(
cls, contract: GitPackage
) -> 'GitUnpinnedPackage':
revisions = contract.get_revisions()
# we want to map None -> True
warn_unpinned = contract.warn_unpinned is not False
return cls(git=contract.git, revisions=revisions,
warn_unpinned=warn_unpinned)
def all_names(self) -> List[str]:
if self.git.endswith('.git'):
other = self.git[:-4]
else:
other = self.git + '.git'
return [self.git, other]
def incorporate(
self, other: 'GitUnpinnedPackage'
) -> 'GitUnpinnedPackage':
warn_unpinned = self.warn_unpinned and other.warn_unpinned
return GitUnpinnedPackage(
git=self.git,
revisions=self.revisions + other.revisions,
warn_unpinned=warn_unpinned,
)
def resolved(self) -> GitPinnedPackage:
requested = set(self.revisions)
if len(requested) == 0:
requested = {'master'}
elif len(requested) > 1:
raise_dependency_error(
'git dependencies should contain exactly one version. '
'{} contains: {}'.format(self.git, requested))
return GitPinnedPackage(
git=self.git, revision=requested.pop(),
warn_unpinned=self.warn_unpinned
)

84
core/dbt/deps/local.py Normal file
View File

@@ -0,0 +1,84 @@
import shutil
from dbt.clients import system
from dbt.deps.base import PinnedPackage, UnpinnedPackage
from dbt.contracts.project import (
ProjectPackageMetadata,
LocalPackage,
)
from dbt.logger import GLOBAL_LOGGER as logger
class LocalPackageMixin:
def __init__(self, local: str) -> None:
super().__init__()
self.local = local
@property
def name(self):
return self.local
def source_type(self):
return 'local'
class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
def __init__(self, local: str) -> None:
super().__init__(local)
def get_version(self):
return None
def nice_version_name(self):
return '<local @ {}>'.format(self.local)
def resolve_path(self, project):
return system.resolve_path_from_base(
self.local,
project.project_root,
)
def _fetch_metadata(self, project, renderer):
loaded = project.from_project_root(
self.resolve_path(project), renderer
)
return ProjectPackageMetadata.from_project(loaded)
def install(self, project, renderer):
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:
logger.debug(' Creating symlink to local dependency.')
system.make_symlink(src_path, dest_path)
else:
logger.debug(' Symlinks are not available on this '
'OS, copying dependency.')
shutil.copytree(src_path, dest_path)
class LocalUnpinnedPackage(
LocalPackageMixin, UnpinnedPackage[LocalPinnedPackage]
):
@classmethod
def from_contract(
cls, contract: LocalPackage
) -> 'LocalUnpinnedPackage':
return cls(local=contract.local)
def incorporate(
self, other: 'LocalUnpinnedPackage'
) -> 'LocalUnpinnedPackage':
return LocalUnpinnedPackage(local=self.local)
def resolved(self) -> LocalPinnedPackage:
return LocalPinnedPackage(local=self.local)

122
core/dbt/deps/registry.py Normal file
View File

@@ -0,0 +1,122 @@
import os
from typing import List
from dbt import semver
from dbt.clients import registry, system
from dbt.contracts.project import (
RegistryPackageMetadata,
RegistryPackage,
)
from dbt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path
from dbt.exceptions import (
package_version_not_found,
VersionsNotCompatibleException,
DependencyException,
package_not_found,
)
class RegistryPackageMixin:
def __init__(self, package: str) -> None:
super().__init__()
self.package = package
@property
def name(self):
return self.package
def source_type(self) -> str:
return 'hub'
class RegistryPinnedPackage(RegistryPackageMixin, PinnedPackage):
def __init__(self, package: str, version: str) -> None:
super().__init__(package)
self.version = version
@property
def name(self):
return self.package
def source_type(self):
return 'hub'
def get_version(self):
return self.version
def nice_version_name(self):
return 'version {}'.format(self.version)
def _fetch_metadata(self, project, renderer) -> RegistryPackageMetadata:
dct = registry.package_version(self.package, self.version)
return RegistryPackageMetadata.from_dict(dct)
def install(self, project, renderer):
metadata = self.fetch_metadata(project, renderer)
tar_name = '{}.{}.tar.gz'.format(self.package, self.version)
tar_path = os.path.realpath(
os.path.join(get_downloads_path(), tar_name)
)
system.make_directory(os.path.dirname(tar_path))
download_url = metadata.downloads.tarball
system.download(download_url, tar_path)
deps_path = project.modules_path
package_name = self.get_project_name(project, renderer)
system.untar_package(tar_path, deps_path, package_name)
class RegistryUnpinnedPackage(
RegistryPackageMixin, UnpinnedPackage[RegistryPinnedPackage]
):
def __init__(
self, package: str, versions: List[semver.VersionSpecifier]
) -> None:
super().__init__(package)
self.versions = versions
def _check_in_index(self):
index = registry.index_cached()
if self.package not in index:
package_not_found(self.package)
@classmethod
def from_contract(
cls, contract: RegistryPackage
) -> 'RegistryUnpinnedPackage':
raw_version = contract.get_versions()
versions = [
semver.VersionSpecifier.from_version_string(v)
for v in raw_version
]
return cls(package=contract.package, versions=versions)
def incorporate(
self, other: 'RegistryUnpinnedPackage'
) -> 'RegistryUnpinnedPackage':
return RegistryUnpinnedPackage(
package=self.package,
versions=self.versions + other.versions,
)
def resolved(self) -> RegistryPinnedPackage:
self._check_in_index()
try:
range_ = semver.reduce_versions(*self.versions)
except VersionsNotCompatibleException as e:
new_msg = ('Version error for package {}: {}'
.format(self.name, e))
raise DependencyException(new_msg) from e
available = registry.get_available_versions(self.package)
# for now, pick a version and then recurse. later on,
# we'll probably want to traverse multiple options
# so we can match packages. not going to make a difference
# right now.
target = semver.resolve_to_specific_version(range_, available)
if not target:
package_version_not_found(self.package, range_, available)
return RegistryPinnedPackage(package=self.package, version=target)

143
core/dbt/deps/resolver.py Normal file
View File

@@ -0,0 +1,143 @@
from dataclasses import dataclass, field
from typing import Dict, List, NoReturn, Union, Type, Iterator, Set
from dbt.exceptions import raise_dependency_error, InternalException
from dbt.context.target import generate_target_context
from dbt.config import Project, RuntimeConfig
from dbt.config.renderer import DbtProjectYamlRenderer
from dbt.deps.base import BasePackage, PinnedPackage, UnpinnedPackage
from dbt.deps.local import LocalUnpinnedPackage
from dbt.deps.git import GitUnpinnedPackage
from dbt.deps.registry import RegistryUnpinnedPackage
from dbt.contracts.project import (
LocalPackage,
GitPackage,
RegistryPackage,
)
PackageContract = Union[LocalPackage, GitPackage, RegistryPackage]
@dataclass
class PackageListing:
packages: Dict[str, UnpinnedPackage] = field(default_factory=dict)
def __len__(self):
return len(self.packages)
def __bool__(self):
return bool(self.packages)
def _pick_key(self, key: BasePackage) -> str:
for name in key.all_names():
if name in self.packages:
return name
return key.name
def __contains__(self, key: BasePackage):
for name in key.all_names():
if name in self.packages:
return True
def __getitem__(self, key: BasePackage):
key_str: str = self._pick_key(key)
return self.packages[key_str]
def __setitem__(self, key: BasePackage, value):
key_str: str = self._pick_key(key)
self.packages[key_str] = value
def _mismatched_types(
self, old: UnpinnedPackage, new: UnpinnedPackage
) -> NoReturn:
raise_dependency_error(
f'Cannot incorporate {new} ({new.__class__.__name__}) in {old} '
f'({old.__class__.__name__}): mismatched types'
)
def incorporate(self, package: UnpinnedPackage):
key: str = self._pick_key(package)
if key in self.packages:
existing: UnpinnedPackage = self.packages[key]
if not isinstance(existing, type(package)):
self._mismatched_types(existing, package)
self.packages[key] = existing.incorporate(package)
else:
self.packages[key] = package
def update_from(self, src: List[PackageContract]) -> None:
pkg: UnpinnedPackage
for contract in src:
if isinstance(contract, LocalPackage):
pkg = LocalUnpinnedPackage.from_contract(contract)
elif isinstance(contract, GitPackage):
pkg = GitUnpinnedPackage.from_contract(contract)
elif isinstance(contract, RegistryPackage):
pkg = RegistryUnpinnedPackage.from_contract(contract)
else:
raise InternalException(
'Invalid package type {}'.format(type(contract))
)
self.incorporate(pkg)
@classmethod
def from_contracts(
cls: Type['PackageListing'], src: List[PackageContract]
) -> 'PackageListing':
self = cls({})
self.update_from(src)
return self
def resolved(self) -> List[PinnedPackage]:
return [p.resolved() for p in self.packages.values()]
def __iter__(self) -> Iterator[UnpinnedPackage]:
return iter(self.packages.values())
def _check_for_duplicate_project_names(
final_deps: List[PinnedPackage],
config: Project,
renderer: DbtProjectYamlRenderer,
):
seen: Set[str] = set()
for package in final_deps:
project_name = package.get_project_name(config, renderer)
if project_name in seen:
raise_dependency_error(
f'Found duplicate project "{project_name}". This occurs when '
'a dependency has the same project name as some other '
'dependency.'
)
elif project_name == config.project_name:
raise_dependency_error(
'Found a dependency with the same name as the root project '
f'"{project_name}". Package names must be unique in a project.'
' Please rename one of these packages.'
)
seen.add(project_name)
def resolve_packages(
packages: List[PackageContract], config: RuntimeConfig
) -> List[PinnedPackage]:
pending = PackageListing.from_contracts(packages)
final = PackageListing()
ctx = generate_target_context(config, config.cli_vars)
renderer = DbtProjectYamlRenderer(ctx, config.config_version)
while pending:
next_pending = PackageListing()
# resolve the dependency in question
for package in pending:
final.incorporate(package)
target = final[package].resolved().fetch_metadata(config, renderer)
next_pending.update_from(target.packages)
pending = next_pending
resolved = final.resolved()
_check_for_duplicate_project_names(resolved, config, renderer)
return resolved

989
core/dbt/exceptions.py Normal file
View File

@@ -0,0 +1,989 @@
import builtins
import functools
from typing import NoReturn, Optional, Mapping, Any
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.node_types import NodeType
from dbt import flags
from dbt.ui import line_wrap_message
import hologram
def validator_error_message(exc):
"""Given a hologram.ValidationError (which is basically a
jsonschema.ValidationError), return the relevant parts as a string
"""
if not isinstance(exc, hologram.ValidationError):
return str(exc)
path = "[%s]" % "][".join(map(repr, exc.relative_path))
return 'at path {}: {}'.format(path, exc.message)
class Exception(builtins.Exception):
CODE = -32000
MESSAGE = "Server Error"
def data(self):
# if overriding, make sure the result is json-serializable.
return {
'type': self.__class__.__name__,
'message': str(self),
}
class MacroReturn(builtins.BaseException):
"""
Hack of all hacks
"""
def __init__(self, value):
self.value = value
class InternalException(Exception):
pass
class RuntimeException(RuntimeError, Exception):
CODE = 10001
MESSAGE = "Runtime error"
def __init__(self, msg, node=None):
self.stack = []
self.node = node
self.msg = msg
def add_node(self, node=None):
if node is not None and node is not self.node:
if self.node is not None:
self.stack.append(self.node)
self.node = node
@property
def type(self):
return 'Runtime'
def node_to_string(self, node):
if node is None:
return "<Unknown>"
if not hasattr(node, 'name'):
# we probably failed to parse a block, so we can't know the name
return '{} ({})'.format(
node.resource_type,
node.original_file_path
)
if hasattr(node, 'contents'):
# handle FileBlocks. They aren't really nodes but we want to render
# out the path we know at least. This indicates an error during
# block parsing.
return '{}'.format(node.path.original_file_path)
return "{} {} ({})".format(
node.resource_type,
node.name,
node.original_file_path)
def process_stack(self):
lines = []
stack = self.stack + [self.node]
first = True
if len(stack) > 1:
lines.append("")
for item in stack:
msg = 'called by'
if first:
msg = 'in'
first = False
lines.append("> {} {}".format(
msg,
self.node_to_string(item)))
return lines
def __str__(self, prefix="! "):
node_string = ""
if self.node is not None:
node_string = " in {}".format(self.node_to_string(self.node))
if hasattr(self.msg, 'split'):
split_msg = self.msg.split("\n")
else:
split_msg = str(self.msg).split("\n")
lines = ["{}{}".format(self.type + ' Error',
node_string)] + split_msg
lines += self.process_stack()
return lines[0] + "\n" + "\n".join(
[" " + line for line in lines[1:]])
def data(self):
result = Exception.data(self)
if self.node is None:
return result
result.update({
'raw_sql': self.node.raw_sql,
# the node isn't always compiled, but if it is, include that!
'compiled_sql': getattr(self.node, 'injected_sql', None),
})
return result
class RPCFailureResult(RuntimeException):
CODE = 10002
MESSAGE = "RPC execution error"
class RPCTimeoutException(RuntimeException):
CODE = 10008
MESSAGE = 'RPC timeout error'
def __init__(self, timeout):
super().__init__(self.MESSAGE)
self.timeout = timeout
def data(self):
result = super().data()
result.update({
'timeout': self.timeout,
'message': 'RPC timed out after {}s'.format(self.timeout),
})
return result
class RPCKilledException(RuntimeException):
CODE = 10009
MESSAGE = 'RPC process killed'
def __init__(self, signum):
self.signum = signum
self.message = 'RPC process killed by signal {}'.format(self.signum)
super().__init__(self.message)
def data(self):
return {
'signum': self.signum,
'message': self.message,
}
class RPCCompiling(RuntimeException):
CODE = 10010
MESSAGE = (
'RPC server is compiling the project, call the "status" method for'
' compile status'
)
def __init__(self, msg=None, node=None):
if msg is None:
msg = 'compile in progress'
super().__init__(msg, node)
class RPCLoadException(RuntimeException):
CODE = 10011
MESSAGE = (
'RPC server failed to compile project, call the "status" method for'
' compile status'
)
def __init__(self, cause):
self.cause = cause
self.message = '{}: {}'.format(self.MESSAGE, self.cause['message'])
super().__init__(self.message)
def data(self):
return {
'cause': self.cause,
'message': self.message
}
class DatabaseException(RuntimeException):
CODE = 10003
MESSAGE = "Database Error"
def process_stack(self):
lines = []
if hasattr(self.node, 'build_path') and self.node.build_path:
lines.append("compiled SQL at {}".format(self.node.build_path))
return lines + RuntimeException.process_stack(self)
@property
def type(self):
return 'Database'
class CompilationException(RuntimeException):
CODE = 10004
MESSAGE = "Compilation Error"
@property
def type(self):
return 'Compilation'
class RecursionException(RuntimeException):
pass
class ValidationException(RuntimeException):
CODE = 10005
MESSAGE = "Validation Error"
class JSONValidationException(ValidationException):
def __init__(self, typename, errors):
self.typename = typename
self.errors = errors
self.errors_message = ', '.join(errors)
msg = 'Invalid arguments passed to "{}" instance: {}'.format(
self.typename, self.errors_message
)
super().__init__(msg)
def __reduce__(self):
# see https://stackoverflow.com/a/36342588 for why this is necessary
return (JSONValidationException, (self.typename, self.errors))
class JinjaRenderingException(CompilationException):
pass
class UnknownAsyncIDException(Exception):
CODE = 10012
MESSAGE = 'RPC server got an unknown async ID'
def __init__(self, task_id):
self.task_id = task_id
def __str__(self):
return '{}: {}'.format(self.MESSAGE, self.task_id)
class AliasException(ValidationException):
pass
class DependencyException(Exception):
# this can happen due to raise_dependency_error and its callers
CODE = 10006
MESSAGE = "Dependency Error"
class DbtConfigError(RuntimeException):
CODE = 10007
MESSAGE = "DBT Configuration Error"
def __init__(
self, message, project=None, result_type='invalid_project', path=None
):
self.project = project
super().__init__(message)
self.result_type = result_type
self.path = path
def __str__(self, prefix='! ') -> str:
msg = super().__str__(prefix)
if self.path is None:
return msg
else:
return f'{msg}\n\nError encountered in {self.path}'
class FailFastException(RuntimeException):
CODE = 10013
MESSAGE = 'FailFast Error'
def __init__(self, message, result=None, node=None):
super().__init__(msg=message, node=node)
self.result = result
@property
def type(self):
return 'FailFast'
class DbtProjectError(DbtConfigError):
pass
class DbtSelectorsError(DbtConfigError):
pass
class DbtProfileError(DbtConfigError):
pass
class SemverException(Exception):
def __init__(self, msg=None):
self.msg = msg
if msg is not None:
super().__init__(msg)
else:
super().__init__()
class VersionsNotCompatibleException(SemverException):
pass
class NotImplementedException(Exception):
pass
class FailedToConnectException(DatabaseException):
pass
class CommandError(RuntimeException):
def __init__(self, cwd, cmd, message='Error running command'):
super().__init__(message)
self.cwd = cwd
self.cmd = cmd
self.args = (cwd, cmd, message)
def __str__(self):
if len(self.cmd) == 0:
return '{}: No arguments given'.format(self.msg)
return '{}: "{}"'.format(self.msg, self.cmd[0])
class ExecutableError(CommandError):
def __init__(self, cwd, cmd, message):
super().__init__(cwd, cmd, message)
class WorkingDirectoryError(CommandError):
def __init__(self, cwd, cmd, message):
super().__init__(cwd, cmd, message)
def __str__(self):
return '{}: "{}"'.format(self.msg, self.cwd)
class CommandResultError(CommandError):
def __init__(self, cwd, cmd, returncode, stdout, stderr,
message='Got a non-zero returncode'):
super().__init__(cwd, cmd, message)
self.returncode = returncode
self.stdout = stdout
self.stderr = stderr
self.args = (cwd, cmd, returncode, stdout, stderr, message)
def __str__(self):
return '{} running: {}'.format(self.msg, self.cmd)
class InvalidConnectionException(RuntimeException):
def __init__(self, thread_id, known, node=None):
self.thread_id = thread_id
self.known = known
super().__init__(
msg='connection never acquired for thread {}, have {}'
.format(self.thread_id, self.known)
)
class InvalidSelectorException(RuntimeException):
def __init__(self, name: str):
self.name = name
super().__init__(name)
def raise_compiler_error(msg, node=None) -> NoReturn:
raise CompilationException(msg, node)
def raise_database_error(msg, node=None) -> NoReturn:
raise DatabaseException(msg, node)
def raise_dependency_error(msg) -> NoReturn:
raise DependencyException(msg)
def invalid_type_error(method_name, arg_name, got_value, expected_type,
version='0.13.0') -> NoReturn:
"""Raise a CompilationException when an adapter method available to macros
has changed.
"""
got_type = type(got_value)
msg = ("As of {version}, 'adapter.{method_name}' expects argument "
"'{arg_name}' to be of type '{expected_type}', instead got "
"{got_value} ({got_type})")
raise_compiler_error(msg.format(version=version, method_name=method_name,
arg_name=arg_name, expected_type=expected_type,
got_value=got_value, got_type=got_type))
def ref_invalid_args(model, args) -> NoReturn:
raise_compiler_error(
"ref() takes at most two arguments ({} given)".format(len(args)),
model)
def ref_bad_context(model, args) -> NoReturn:
ref_args = ', '.join("'{}'".format(a) for a in args)
ref_string = '{{{{ ref({}) }}}}'.format(ref_args)
base_error_msg = """dbt was unable to infer all dependencies for the model "{model_name}".
This typically happens when ref() is placed within a conditional block.
To fix this, add the following hint to the top of the model "{model_name}":
-- depends_on: {ref_string}"""
# This explicitly references model['name'], instead of model['alias'], for
# better error messages. Ex. If models foo_users and bar_users are aliased
# to 'users', in their respective schemas, then you would want to see
# 'bar_users' in your error messge instead of just 'users'.
if isinstance(model, dict): # TODO: remove this path
model_name = model['name']
model_path = model['path']
else:
model_name = model.name
model_path = model.path
error_msg = base_error_msg.format(
model_name=model_name,
model_path=model_path,
ref_string=ref_string
)
raise_compiler_error(error_msg, model)
def doc_invalid_args(model, args) -> NoReturn:
raise_compiler_error(
"doc() takes at most two arguments ({} given)".format(len(args)),
model)
def doc_target_not_found(
model, target_doc_name: str, target_doc_package: Optional[str]
) -> NoReturn:
target_package_string = ''
if target_doc_package is not None:
target_package_string = "in package '{}' ".format(target_doc_package)
msg = (
"Documentation for '{}' depends on doc '{}' {} which was not found"
).format(
model.unique_id,
target_doc_name,
target_package_string
)
raise_compiler_error(msg, model)
def _get_target_failure_msg(
model, target_name: str, target_model_package: Optional[str],
include_path: bool, reason: str, target_kind: str
) -> str:
target_package_string = ''
if target_model_package is not None:
target_package_string = "in package '{}' ".format(target_model_package)
source_path_string = ''
if include_path:
source_path_string = ' ({})'.format(model.original_file_path)
return "{} '{}'{} depends on a {} named '{}' {}which {}".format(
model.resource_type.title(),
model.unique_id,
source_path_string,
target_kind,
target_name,
target_package_string,
reason
)
def get_target_not_found_or_disabled_msg(
model, target_model_name: str, target_model_package: Optional[str],
disabled: Optional[bool] = None,
) -> str:
if disabled is None:
reason = 'was not found or is disabled'
elif disabled is True:
reason = 'is disabled'
else:
reason = 'was not found'
return _get_target_failure_msg(
model, target_model_name, target_model_package, include_path=True,
reason=reason, target_kind='node'
)
def ref_target_not_found(
model,
target_model_name: str,
target_model_package: Optional[str],
disabled: Optional[bool] = None,
) -> NoReturn:
msg = get_target_not_found_or_disabled_msg(
model, target_model_name, target_model_package, disabled
)
raise_compiler_error(msg, model)
def get_source_not_found_or_disabled_msg(
model,
target_name: str,
target_table_name: str,
disabled: Optional[bool] = None,
) -> str:
full_name = f'{target_name}.{target_table_name}'
if disabled is None:
reason = 'was not found or is disabled'
elif disabled is True:
reason = 'is disabled'
else:
reason = 'was not found'
return _get_target_failure_msg(
model, full_name, None, include_path=True,
reason=reason, target_kind='source'
)
def source_target_not_found(
model,
target_name: str,
target_table_name: str,
disabled: Optional[bool] = None
) -> NoReturn:
msg = get_source_not_found_or_disabled_msg(
model, target_name, target_table_name, disabled
)
raise_compiler_error(msg, model)
def ref_disabled_dependency(model, target_model):
raise_compiler_error(
"Model '{}' depends on model '{}' which is disabled in "
"the project config".format(model.unique_id,
target_model.unique_id),
model)
def dependency_not_found(model, target_model_name):
raise_compiler_error(
"'{}' depends on '{}' which is not in the graph!"
.format(model.unique_id, target_model_name),
model)
def macro_not_found(model, target_macro_id):
raise_compiler_error(
model,
"'{}' references macro '{}' which is not defined!"
.format(model.unique_id, target_macro_id))
def materialization_not_available(model, adapter_type):
materialization = model.get_materialization()
raise_compiler_error(
"Materialization '{}' is not available for {}!"
.format(materialization, adapter_type),
model)
def missing_materialization(model, adapter_type):
materialization = model.get_materialization()
valid_types = "'default'"
if adapter_type != 'default':
valid_types = "'default' and '{}'".format(adapter_type)
raise_compiler_error(
"No materialization '{}' was found for adapter {}! (searched types {})"
.format(materialization, adapter_type, valid_types),
model)
def bad_package_spec(repo, spec, error_message):
raise InternalException(
"Error checking out spec='{}' for repo {}\n{}".format(
spec, repo, error_message))
def raise_cache_inconsistent(message):
raise InternalException('Cache inconsistency detected: {}'.format(message))
def missing_config(model, name):
raise_compiler_error(
"Model '{}' does not define a required config parameter '{}'."
.format(model.unique_id, name),
model)
def missing_relation(relation, model=None):
raise_compiler_error(
"Relation {} not found!".format(relation),
model)
def relation_wrong_type(relation, expected_type, model=None):
raise_compiler_error(
('Trying to create {expected_type} {relation}, '
'but it currently exists as a {current_type}. Either '
'drop {relation} manually, or run dbt with '
'`--full-refresh` and dbt will drop it for you.')
.format(relation=relation,
current_type=relation.type,
expected_type=expected_type),
model)
def package_not_found(package_name):
raise_dependency_error(
"Package {} was not found in the package index".format(package_name))
def package_version_not_found(package_name, version_range, available_versions):
base_msg = ('Could not find a matching version for package {}\n'
' Requested range: {}\n'
' Available versions: {}')
raise_dependency_error(base_msg.format(package_name,
version_range,
available_versions))
def invalid_materialization_argument(name, argument):
raise_compiler_error(
"materialization '{}' received unknown argument '{}'."
.format(name, argument))
def system_error(operation_name):
raise_compiler_error(
"dbt encountered an error when attempting to {}. "
"If this error persists, please create an issue at: \n\n"
"https://github.com/fishtown-analytics/dbt"
.format(operation_name))
class RegistryException(Exception):
pass
def raise_dep_not_found(node, node_description, required_pkg):
raise_compiler_error(
'Error while parsing {}.\nThe required package "{}" was not found. '
'Is the package installed?\nHint: You may need to run '
'`dbt deps`.'.format(node_description, required_pkg), node=node)
def multiple_matching_relations(kwargs, matches):
raise_compiler_error(
'get_relation returned more than one relation with the given args. '
'Please specify a database or schema to narrow down the result set.'
'\n{}\n\n{}'
.format(kwargs, matches))
def get_relation_returned_multiple_results(kwargs, matches):
multiple_matching_relations(kwargs, matches)
def approximate_relation_match(target, relation):
raise_compiler_error(
'When searching for a relation, dbt found an approximate match. '
'Instead of guessing \nwhich relation to use, dbt will move on. '
'Please delete {relation}, or rename it to be less ambiguous.'
'\nSearched for: {target}\nFound: {relation}'
.format(target=target,
relation=relation))
def raise_duplicate_macro_name(node_1, node_2, namespace) -> NoReturn:
duped_name = node_1.name
if node_1.package_name != node_2.package_name:
extra = (
' ("{}" and "{}" are both in the "{}" namespace)'
.format(node_1.package_name, node_2.package_name, namespace)
)
else:
extra = ''
raise_compiler_error(
'dbt found two macros with the name "{}" in the namespace "{}"{}. '
'Since these macros have the same name and exist in the same '
'namespace, dbt will be unable to decide which to call. To fix this, '
'change the name of one of these macros:\n- {} ({})\n- {} ({})'
.format(
duped_name, namespace, extra,
node_1.unique_id, node_1.original_file_path,
node_2.unique_id, node_2.original_file_path
)
)
def raise_duplicate_resource_name(node_1, node_2):
duped_name = node_1.name
if node_1.resource_type in NodeType.refable():
get_func = 'ref("{}")'.format(duped_name)
elif node_1.resource_type == NodeType.Source:
duped_name = node_1.get_full_source_name()
get_func = node_1.get_source_representation()
elif node_1.resource_type == NodeType.Documentation:
get_func = 'doc("{}")'.format(duped_name)
elif node_1.resource_type == NodeType.Test and 'schema' in node_1.tags:
return
else:
get_func = '"{}"'.format(duped_name)
raise_compiler_error(
'dbt found two resources with the name "{}". Since these resources '
'have the same name,\ndbt will be unable to find the correct resource '
'when {} is used. To fix this,\nchange the name of one of '
'these resources:\n- {} ({})\n- {} ({})'.format(
duped_name,
get_func,
node_1.unique_id, node_1.original_file_path,
node_2.unique_id, node_2.original_file_path))
def raise_ambiguous_alias(node_1, node_2, duped_name=None):
if duped_name is None:
duped_name = f"{node_1.database}.{node_1.schema}.{node_1.alias}"
raise_compiler_error(
'dbt found two resources with the database representation "{}".\ndbt '
'cannot create two resources with identical database representations. '
'To fix this,\nchange the configuration of one of these resources:'
'\n- {} ({})\n- {} ({})'.format(
duped_name,
node_1.unique_id, node_1.original_file_path,
node_2.unique_id, node_2.original_file_path))
def raise_ambiguous_catalog_match(unique_id, match_1, match_2):
def get_match_string(match):
return "{}.{}".format(
match.get('metadata', {}).get('schema'),
match.get('metadata', {}).get('name'))
raise_compiler_error(
'dbt found two relations in your warehouse with similar database '
'identifiers. dbt\nis unable to determine which of these relations '
'was created by the model "{unique_id}".\nIn order for dbt to '
'correctly generate the catalog, one of the following relations must '
'be deleted or renamed:\n\n - {match_1_s}\n - {match_2_s}'.format(
unique_id=unique_id,
match_1_s=get_match_string(match_1),
match_2_s=get_match_string(match_2),
))
def raise_patch_targets_not_found(patches):
patch_list = '\n\t'.join(
'model {} (referenced in path {})'.format(p.name, p.original_file_path)
for p in patches.values()
)
raise_compiler_error(
'dbt could not find models for the following patches:\n\t{}'.format(
patch_list
)
)
def _fix_dupe_msg(path_1: str, path_2: str, name: str, type_name: str) -> str:
if path_1 == path_2:
return (
f'remove one of the {type_name} entries for {name} in this file:\n'
f' - {path_1!s}\n'
)
else:
return (
f'remove the {type_name} entry for {name} in one of these files:\n'
f' - {path_1!s}\n{path_2!s}'
)
def raise_duplicate_patch_name(patch_1, patch_2):
name = patch_1.name
fix = _fix_dupe_msg(
patch_1.original_file_path,
patch_2.original_file_path,
name,
'resource',
)
raise_compiler_error(
f'dbt found two schema.yml entries for the same resource named '
f'{name}. Resources and their associated columns may only be '
f'described a single time. To fix this, {fix}'
)
def raise_duplicate_macro_patch_name(patch_1, patch_2):
package_name = patch_1.package_name
name = patch_1.name
fix = _fix_dupe_msg(
patch_1.original_file_path,
patch_2.original_file_path,
name,
'macros'
)
raise_compiler_error(
f'dbt found two schema.yml entries for the same macro in package '
f'{package_name} named {name}. Macros may only be described a single '
f'time. To fix this, {fix}'
)
def raise_duplicate_source_patch_name(patch_1, patch_2):
name = f'{patch_1.overrides}.{patch_1.name}'
fix = _fix_dupe_msg(
patch_1.path,
patch_2.path,
name,
'sources',
)
raise_compiler_error(
f'dbt found two schema.yml entries for the same source named '
f'{patch_1.name} in package {patch_1.overrides}. Sources may only be '
f'overridden a single time. To fix this, {fix}'
)
def raise_invalid_schema_yml_version(path, issue):
raise_compiler_error(
'The schema file at {} is invalid because {}. Please consult the '
'documentation for more information on schema.yml syntax:\n\n'
'https://docs.getdbt.com/docs/schemayml-files'
.format(path, issue)
)
def raise_unrecognized_credentials_type(typename, supported_types):
raise_compiler_error(
'Unrecognized credentials type "{}" - supported types are ({})'
.format(typename, ', '.join('"{}"'.format(t) for t in supported_types))
)
def raise_invalid_patch(
node, patch_section: str, patch_path: str,
) -> NoReturn:
msg = line_wrap_message(
f'''\
'{node.name}' is a {node.resource_type} node, but it is
specified in the {patch_section} section of
{patch_path}.
To fix this error, place the `{node.name}`
specification under the {node.resource_type.pluralize()} key instead.
'''
)
raise_compiler_error(msg, node)
def raise_not_implemented(msg):
raise NotImplementedException(
"ERROR: {}"
.format(msg))
def raise_duplicate_alias(
kwargs: Mapping[str, Any], aliases: Mapping[str, str], canonical_key: str
) -> NoReturn:
# dupe found: go through the dict so we can have a nice-ish error
key_names = ', '.join(
"{}".format(k) for k in kwargs if
aliases.get(k) == canonical_key
)
raise AliasException(
f'Got duplicate keys: ({key_names}) all map to "{canonical_key}"'
)
def warn_or_error(msg, node=None, log_fmt=None):
if flags.WARN_ERROR:
raise_compiler_error(msg, node)
else:
if log_fmt is not None:
msg = log_fmt.format(msg)
logger.warning(msg)
def warn_or_raise(exc, log_fmt=None):
if flags.WARN_ERROR:
raise exc
else:
msg = str(exc)
if log_fmt is not None:
msg = log_fmt.format(msg)
logger.warning(msg)
def warn(msg, node=None):
# there's no reason to expose log_fmt to macros - it's only useful for
# handling colors
warn_or_error(msg, node=node)
return ""
# Update this when a new function should be added to the
# dbt context's `exceptions` key!
CONTEXT_EXPORTS = {
fn.__name__: fn
for fn in
[
warn,
missing_config,
missing_materialization,
missing_relation,
raise_ambiguous_alias,
raise_ambiguous_catalog_match,
raise_cache_inconsistent,
raise_compiler_error,
raise_database_error,
raise_dep_not_found,
raise_dependency_error,
raise_duplicate_patch_name,
raise_duplicate_resource_name,
raise_invalid_schema_yml_version,
raise_not_implemented,
relation_wrong_type,
]
}
def wrapper(model):
def wrap(func):
@functools.wraps(func)
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except RuntimeException as exc:
exc.add_node(model)
raise exc
return inner
return wrap
def wrapped_exports(model):
wrap = wrapper(model)
return {
name: wrap(export) for name, export in CONTEXT_EXPORTS.items()
}

93
core/dbt/flags.py Normal file
View File

@@ -0,0 +1,93 @@
import os
import multiprocessing
from pathlib import Path
from typing import Optional
# initially all flags are set to None, the on-load call of reset() will set
# them for their first time.
STRICT_MODE = None
FULL_REFRESH = None
USE_CACHE = None
WARN_ERROR = None
TEST_NEW_PARSER = None
WRITE_JSON = None
PARTIAL_PARSE = None
USE_COLORS = None
def env_set_truthy(key: str) -> Optional[str]:
"""Return the value if it was set to a "truthy" string value, or None
otherwise.
"""
value = os.getenv(key)
if not value or value.lower() in ('0', 'false', 'f'):
return None
return value
def env_set_path(key: str) -> Optional[Path]:
value = os.getenv(key)
if value is None:
return value
else:
return Path(value)
SINGLE_THREADED_WEBSERVER = env_set_truthy('DBT_SINGLE_THREADED_WEBSERVER')
SINGLE_THREADED_HANDLER = env_set_truthy('DBT_SINGLE_THREADED_HANDLER')
MACRO_DEBUGGING = env_set_truthy('DBT_MACRO_DEBUGGING')
DEFER_MODE = env_set_truthy('DBT_DEFER_TO_STATE')
ARTIFACT_STATE_PATH = env_set_path('DBT_ARTIFACT_STATE_PATH')
def _get_context():
# TODO: change this back to use fork() on linux when we have made that safe
return multiprocessing.get_context('spawn')
MP_CONTEXT = _get_context()
def reset():
global STRICT_MODE, FULL_REFRESH, USE_CACHE, WARN_ERROR, TEST_NEW_PARSER, \
WRITE_JSON, PARTIAL_PARSE, MP_CONTEXT, USE_COLORS
STRICT_MODE = False
FULL_REFRESH = False
USE_CACHE = True
WARN_ERROR = False
TEST_NEW_PARSER = False
WRITE_JSON = True
PARTIAL_PARSE = False
MP_CONTEXT = _get_context()
USE_COLORS = True
def set_from_args(args):
global STRICT_MODE, FULL_REFRESH, USE_CACHE, WARN_ERROR, TEST_NEW_PARSER, \
WRITE_JSON, PARTIAL_PARSE, MP_CONTEXT, USE_COLORS
USE_CACHE = getattr(args, 'use_cache', USE_CACHE)
FULL_REFRESH = getattr(args, 'full_refresh', FULL_REFRESH)
STRICT_MODE = getattr(args, 'strict', STRICT_MODE)
WARN_ERROR = (
STRICT_MODE or
getattr(args, 'warn_error', STRICT_MODE or WARN_ERROR)
)
TEST_NEW_PARSER = getattr(args, 'test_new_parser', TEST_NEW_PARSER)
WRITE_JSON = getattr(args, 'write_json', WRITE_JSON)
PARTIAL_PARSE = getattr(args, 'partial_parse', None)
MP_CONTEXT = _get_context()
# The use_colors attribute will always have a value because it is assigned
# None by default from the add_mutually_exclusive_group function
use_colors_override = getattr(args, 'use_colors')
if use_colors_override is not None:
USE_COLORS = use_colors_override
# initialize everything to the defaults on module load
reset()

View File

@@ -0,0 +1,18 @@
from .selector_spec import ( # noqa: F401
SelectionUnion,
SelectionSpec,
SelectionIntersection,
SelectionDifference,
SelectionCriteria,
)
from .selector import ( # noqa: F401
ResourceTypeSelector,
NodeSelector,
)
from .cli import ( # noqa: F401
parse_difference,
parse_test_selectors,
parse_from_selectors_definition,
)
from .queue import GraphQueue # noqa: F401
from .graph import Graph, UniqueId # noqa: F401

268
core/dbt/graph/cli.py Normal file
View File

@@ -0,0 +1,268 @@
# special support for CLI argument parsing.
import itertools
from typing import (
Dict, List, Optional, Tuple, Any, Union
)
from dbt.contracts.selection import SelectorDefinition, SelectorFile
from dbt.exceptions import InternalException, ValidationException
from .selector_spec import (
SelectionUnion,
SelectionSpec,
SelectionIntersection,
SelectionDifference,
SelectionCriteria,
)
INTERSECTION_DELIMITER = ','
DEFAULT_INCLUDES: List[str] = ['fqn:*', 'source:*', 'exposure:*']
DEFAULT_EXCLUDES: List[str] = []
DATA_TEST_SELECTOR: str = 'test_type:data'
SCHEMA_TEST_SELECTOR: str = 'test_type:schema'
def parse_union(
components: List[str], expect_exists: bool
) -> SelectionUnion:
# turn ['a b', 'c'] -> ['a', 'b', 'c']
raw_specs = itertools.chain.from_iterable(
r.split(' ') for r in components
)
union_components: List[SelectionSpec] = []
# ['a', 'b', 'c,d'] -> union('a', 'b', intersection('c', 'd'))
for raw_spec in raw_specs:
intersection_components: List[SelectionSpec] = [
SelectionCriteria.from_single_spec(part)
for part in raw_spec.split(INTERSECTION_DELIMITER)
]
union_components.append(SelectionIntersection(
components=intersection_components,
expect_exists=expect_exists,
raw=raw_spec,
))
return SelectionUnion(
components=union_components,
expect_exists=False,
raw=components,
)
def parse_union_from_default(
raw: Optional[List[str]], default: List[str]
) -> SelectionUnion:
components: List[str]
expect_exists: bool
if raw is None:
return parse_union(components=default, expect_exists=False)
else:
return parse_union(components=raw, expect_exists=True)
def parse_difference(
include: Optional[List[str]], exclude: Optional[List[str]]
) -> SelectionDifference:
included = parse_union_from_default(include, DEFAULT_INCLUDES)
excluded = parse_union_from_default(exclude, DEFAULT_EXCLUDES)
return SelectionDifference(components=[included, excluded])
def parse_test_selectors(
data: bool, schema: bool, base: SelectionSpec
) -> SelectionSpec:
union_components = []
if data:
union_components.append(
SelectionCriteria.from_single_spec(DATA_TEST_SELECTOR)
)
if schema:
union_components.append(
SelectionCriteria.from_single_spec(SCHEMA_TEST_SELECTOR)
)
intersect_with: SelectionSpec
if not union_components:
return base
elif len(union_components) == 1:
intersect_with = union_components[0]
else: # data and schema tests
intersect_with = SelectionUnion(
components=union_components,
expect_exists=True,
raw=[DATA_TEST_SELECTOR, SCHEMA_TEST_SELECTOR],
)
return SelectionIntersection(
components=[base, intersect_with], expect_exists=True
)
RawDefinition = Union[str, Dict[str, Any]]
def _get_list_dicts(
dct: Dict[str, Any], key: str
) -> List[RawDefinition]:
result: List[RawDefinition] = []
if key not in dct:
raise InternalException(
f'Expected to find key {key} in dict, only found {list(dct)}'
)
values = dct[key]
if not isinstance(values, list):
raise ValidationException(
f'Invalid value type {type(values)} in key "{key}" '
f'(value "{values}")'
)
for value in values:
if isinstance(value, dict):
for value_key in value:
if not isinstance(value_key, str):
raise ValidationException(
f'Expected all keys to "{key}" dict to be strings, '
f'but "{value_key}" is a "{type(value_key)}"'
)
result.append(value)
elif isinstance(value, str):
result.append(value)
else:
raise ValidationException(
f'Invalid value type {type(value)} in key "{key}", expected '
f'dict or str (value: {value}).'
)
return result
def _parse_exclusions(definition) -> Optional[SelectionSpec]:
exclusions = _get_list_dicts(definition, 'exclude')
parsed_exclusions = [
parse_from_definition(excl) for excl in exclusions
]
if len(parsed_exclusions) == 1:
return parsed_exclusions[0]
elif len(parsed_exclusions) > 1:
return SelectionUnion(
components=parsed_exclusions,
raw=exclusions
)
else:
return None
def _parse_include_exclude_subdefs(
definitions: List[RawDefinition]
) -> Tuple[List[SelectionSpec], Optional[SelectionSpec]]:
include_parts: List[SelectionSpec] = []
diff_arg: Optional[SelectionSpec] = None
for definition in definitions:
if isinstance(definition, dict) and 'exclude' in definition:
# do not allow multiple exclude: defs at the same level
if diff_arg is not None:
raise ValidationException(
f'Got multiple exclusion definitions in definition list '
f'{definitions}'
)
diff_arg = _parse_exclusions(definition)
else:
include_parts.append(parse_from_definition(definition))
return (include_parts, diff_arg)
def parse_union_definition(definition: Dict[str, Any]) -> SelectionSpec:
union_def_parts = _get_list_dicts(definition, 'union')
include, exclude = _parse_include_exclude_subdefs(union_def_parts)
union = SelectionUnion(components=include)
if exclude is None:
union.raw = definition
return union
else:
return SelectionDifference(
components=[union, exclude],
raw=definition
)
def parse_intersection_definition(
definition: Dict[str, Any]
) -> SelectionSpec:
intersection_def_parts = _get_list_dicts(definition, 'intersection')
include, exclude = _parse_include_exclude_subdefs(intersection_def_parts)
intersection = SelectionIntersection(components=include)
if exclude is None:
intersection.raw = definition
return intersection
else:
return SelectionDifference(
components=[intersection, exclude],
raw=definition
)
def parse_dict_definition(definition: Dict[str, Any]) -> SelectionSpec:
diff_arg: Optional[SelectionSpec] = None
if len(definition) == 1:
key = list(definition)[0]
value = definition[key]
if not isinstance(key, str):
raise ValidationException(
f'Expected definition key to be a "str", got one of type '
f'"{type(key)}" ({key})'
)
dct = {
'method': key,
'value': value,
}
elif 'method' in definition and 'value' in definition:
dct = definition
if 'exclude' in definition:
diff_arg = _parse_exclusions(definition)
dct = {k: v for k, v in dct.items() if k != 'exclude'}
else:
raise ValidationException(
f'Expected exactly 1 key in the selection definition or "method" '
f'and "value" keys, but got {list(definition)}'
)
# if key isn't a valid method name, this will raise
base = SelectionCriteria.from_dict(definition, dct)
if diff_arg is None:
return base
else:
return SelectionDifference(components=[base, diff_arg])
def parse_from_definition(definition: RawDefinition) -> SelectionSpec:
if isinstance(definition, str):
return SelectionCriteria.from_single_spec(definition)
elif 'union' in definition:
return parse_union_definition(definition)
elif 'intersection' in definition:
return parse_intersection_definition(definition)
elif isinstance(definition, dict):
return parse_dict_definition(definition)
else:
raise ValidationException(
f'Expected to find str or dict, instead found '
f'{type(definition)}: {definition}'
)
def parse_from_selectors_definition(
source: SelectorFile
) -> Dict[str, SelectionSpec]:
result: Dict[str, SelectionSpec] = {}
selector: SelectorDefinition
for selector in source.selectors:
result[selector.name] = parse_from_definition(selector.definition)
return result

105
core/dbt/graph/graph.py Normal file
View File

@@ -0,0 +1,105 @@
from typing import (
Set, Iterable, Iterator, Optional, NewType
)
import networkx as nx # type: ignore
from dbt.exceptions import InternalException
UniqueId = NewType('UniqueId', str)
class Graph:
"""A wrapper around the networkx graph that understands SelectionCriteria
and how they interact with the graph.
"""
def __init__(self, graph):
self.graph = graph
def nodes(self) -> Set[UniqueId]:
return set(self.graph.nodes())
def edges(self):
return self.graph.edges()
def __iter__(self) -> Iterator[UniqueId]:
return iter(self.graph.nodes())
def ancestors(
self, node: UniqueId, max_depth: Optional[int] = None
) -> Set[UniqueId]:
"""Returns all nodes having a path to `node` in `graph`"""
if not self.graph.has_node(node):
raise InternalException(f'Node {node} not found in the graph!')
with nx.utils.reversed(self.graph):
anc = nx.single_source_shortest_path_length(G=self.graph,
source=node,
cutoff=max_depth)\
.keys()
return anc - {node}
def descendants(
self, node: UniqueId, max_depth: Optional[int] = None
) -> Set[UniqueId]:
"""Returns all nodes reachable from `node` in `graph`"""
if not self.graph.has_node(node):
raise InternalException(f'Node {node} not found in the graph!')
des = nx.single_source_shortest_path_length(G=self.graph,
source=node,
cutoff=max_depth)\
.keys()
return des - {node}
def select_childrens_parents(
self, selected: Set[UniqueId]
) -> Set[UniqueId]:
ancestors_for = self.select_children(selected) | selected
return self.select_parents(ancestors_for) | ancestors_for
def select_children(
self, selected: Set[UniqueId], max_depth: Optional[int] = None
) -> Set[UniqueId]:
descendants: Set[UniqueId] = set()
for node in selected:
descendants.update(self.descendants(node, max_depth))
return descendants
def select_parents(
self, selected: Set[UniqueId], max_depth: Optional[int] = None
) -> Set[UniqueId]:
ancestors: Set[UniqueId] = set()
for node in selected:
ancestors.update(self.ancestors(node, max_depth))
return ancestors
def select_successors(self, selected: Set[UniqueId]) -> Set[UniqueId]:
successors: Set[UniqueId] = set()
for node in selected:
successors.update(self.graph.successors(node))
return successors
def get_subset_graph(self, selected: Iterable[UniqueId]) -> 'Graph':
"""Create and return a new graph that is a shallow copy of the graph,
but with only the nodes in include_nodes. Transitive edges across
removed nodes are preserved as explicit new edges.
"""
new_graph = nx.algorithms.transitive_closure(self.graph)
include_nodes = set(selected)
for node in self:
if node not in include_nodes:
new_graph.remove_node(node)
for node in include_nodes:
if node not in new_graph:
raise ValueError(
"Couldn't find model '{}' -- does it exist or is "
"it disabled?".format(node)
)
return Graph(new_graph)
def subgraph(self, nodes: Iterable[UniqueId]) -> 'Graph':
return Graph(self.graph.subgraph(nodes))
def get_dependent_nodes(self, node: UniqueId):
return nx.descendants(self.graph, node)

181
core/dbt/graph/queue.py Normal file
View File

@@ -0,0 +1,181 @@
import threading
from queue import PriorityQueue
from typing import (
Dict, Set, Optional
)
import networkx as nx # type: ignore
from .graph import UniqueId
from dbt.contracts.graph.parsed import ParsedSourceDefinition, ParsedExposure
from dbt.contracts.graph.compiled import GraphMemberNode
from dbt.contracts.graph.manifest import Manifest
from dbt.node_types import NodeType
class GraphQueue:
"""A fancy queue that is backed by the dependency graph.
Note: this will mutate input!
This queue is thread-safe for `mark_done` calls, though you must ensure
that separate threads do not call `.empty()` or `__len__()` and `.get()` at
the same time, as there is an unlocked race!
"""
def __init__(
self, graph: nx.DiGraph, manifest: Manifest, selected: Set[UniqueId]
):
self.graph = graph
self.manifest = manifest
self._selected = selected
# store the queue as a priority queue.
self.inner: PriorityQueue = PriorityQueue()
# things that have been popped off the queue but not finished
# and worker thread reservations
self.in_progress: Set[UniqueId] = set()
# things that are in the queue
self.queued: Set[UniqueId] = set()
# this lock controls most things
self.lock = threading.Lock()
# store the 'score' of each node as a number. Lower is higher priority.
self._scores = self._calculate_scores()
# populate the initial queue
self._find_new_additions()
# awaits after task end
self.some_task_done = threading.Condition(self.lock)
def get_selected_nodes(self) -> Set[UniqueId]:
return self._selected.copy()
def _include_in_cost(self, node_id: UniqueId) -> bool:
node = self.manifest.expect(node_id)
if node.resource_type != NodeType.Model:
return False
# must be a Model - tell mypy this won't be a Source or Exposure
assert not isinstance(node, (ParsedSourceDefinition, ParsedExposure))
if node.is_ephemeral:
return False
return True
def _calculate_scores(self) -> Dict[UniqueId, int]:
"""Calculate the 'value' of each node in the graph based on how many
blocking descendants it has. We use this score for the internal
priority queue's ordering, so the quality of this metric is important.
The score is stored as a negative number because the internal
PriorityQueue picks lowest values first.
We could do this in one pass over the graph instead of len(self.graph)
passes but this is easy. For large graphs this may hurt performance.
This operates on the graph, so it would require a lock if called from
outside __init__.
:return Dict[str, int]: The score dict, mapping unique IDs to integer
scores. Lower scores are higher priority.
"""
scores = {}
for node in self.graph.nodes():
score = -1 * len([
d for d in nx.descendants(self.graph, node)
if self._include_in_cost(d)
])
scores[node] = score
return scores
def get(
self, block: bool = True, timeout: Optional[float] = None
) -> GraphMemberNode:
"""Get a node off the inner priority queue. By default, this blocks.
This takes the lock, but only for part of it.
:param block: If True, block until the inner queue has data
:param timeout: If set, block for timeout seconds waiting for data.
:return: The node as present in the manifest.
See `queue.PriorityQueue` for more information on `get()` behavior and
exceptions.
"""
_, node_id = self.inner.get(block=block, timeout=timeout)
with self.lock:
self._mark_in_progress(node_id)
return self.manifest.expect(node_id)
def __len__(self) -> int:
"""The length of the queue is the number of tasks left for the queue to
give out, regardless of where they are. Incomplete tasks are not part
of the length.
This takes the lock.
"""
with self.lock:
return len(self.graph) - len(self.in_progress)
def empty(self) -> bool:
"""The graph queue is 'empty' if it all remaining nodes in the graph
are in progress.
This takes the lock.
"""
return len(self) == 0
def _already_known(self, node: UniqueId) -> bool:
"""Decide if a node is already known (either handed out as a task, or
in the queue).
Callers must hold the lock.
:param str node: The node ID to check
:returns bool: If the node is in progress/queued.
"""
return node in self.in_progress or node in self.queued
def _find_new_additions(self) -> None:
"""Find any nodes in the graph that need to be added to the internal
queue and add them.
Callers must hold the lock.
"""
for node, in_degree in self.graph.in_degree():
if not self._already_known(node) and in_degree == 0:
self.inner.put((self._scores[node], node))
self.queued.add(node)
def mark_done(self, node_id: UniqueId) -> None:
"""Given a node's unique ID, mark it as done.
This method takes the lock.
:param str node_id: The node ID to mark as complete.
"""
with self.lock:
self.in_progress.remove(node_id)
self.graph.remove_node(node_id)
self._find_new_additions()
self.inner.task_done()
self.some_task_done.notify_all()
def _mark_in_progress(self, node_id: UniqueId) -> None:
"""Mark the node as 'in progress'.
Callers must hold the lock.
:param str node_id: The node ID to mark as in progress.
"""
self.queued.remove(node_id)
self.in_progress.add(node_id)
def join(self) -> None:
"""Join the queue. Blocks until all tasks are marked as done.
Make sure not to call this before the queue reports that it is empty.
"""
self.inner.join()
def wait_until_something_was_done(self) -> int:
"""Block until a task is done, then return the number of unfinished
tasks.
"""
with self.lock:
self.some_task_done.wait()
return self.inner.unfinished_tasks

211
core/dbt/graph/selector.py Normal file
View File

@@ -0,0 +1,211 @@
from typing import Set, List, Optional
from .graph import Graph, UniqueId
from .queue import GraphQueue
from .selector_methods import MethodManager
from .selector_spec import SelectionCriteria, SelectionSpec
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.node_types import NodeType
from dbt.exceptions import (
InternalException,
InvalidSelectorException,
warn_or_error,
)
from dbt.contracts.graph.compiled import GraphMemberNode
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.state import PreviousState
def get_package_names(nodes):
return set([node.split(".")[1] for node in nodes])
def alert_non_existence(raw_spec, nodes):
if len(nodes) == 0:
warn_or_error(
f"The selector '{str(raw_spec)}' does not match any nodes and will"
f" be ignored"
)
class NodeSelector(MethodManager):
"""The node selector is aware of the graph and manifest,
"""
def __init__(
self,
graph: Graph,
manifest: Manifest,
previous_state: Optional[PreviousState] = None,
):
super().__init__(manifest, previous_state)
self.full_graph = graph
# build a subgraph containing only non-empty, enabled nodes and enabled
# sources.
graph_members = {
unique_id for unique_id in self.full_graph.nodes()
if self._is_graph_member(unique_id)
}
self.graph = self.full_graph.subgraph(graph_members)
def select_included(
self, included_nodes: Set[UniqueId], spec: SelectionCriteria,
) -> Set[UniqueId]:
"""Select the explicitly included nodes, using the given spec. Return
the selected set of unique IDs.
"""
method = self.get_method(spec.method, spec.method_arguments)
return set(method.search(included_nodes, spec.value))
def get_nodes_from_criteria(
self,
spec: SelectionCriteria,
) -> Set[UniqueId]:
"""Get all nodes specified by the single selection criteria.
- collect the directly included nodes
- find their specified relatives
- perform any selector-specific expansion
"""
nodes = self.graph.nodes()
try:
collected = self.select_included(nodes, spec)
except InvalidSelectorException:
valid_selectors = ", ".join(self.SELECTOR_METHODS)
logger.info(
f"The '{spec.method}' selector specified in {spec.raw} is "
f"invalid. Must be one of [{valid_selectors}]"
)
return set()
extras = self.collect_specified_neighbors(spec, collected)
result = self.expand_selection(collected | extras)
return result
def collect_specified_neighbors(
self, spec: SelectionCriteria, selected: Set[UniqueId]
) -> Set[UniqueId]:
"""Given the set of models selected by the explicit part of the
selector (like "tag:foo"), apply the modifiers on the spec ("+"/"@").
Return the set of additional nodes that should be collected (which may
overlap with the selected set).
"""
additional: Set[UniqueId] = set()
if spec.childrens_parents:
additional.update(self.graph.select_childrens_parents(selected))
if spec.parents:
depth = spec.parents_depth
additional.update(self.graph.select_parents(selected, depth))
if spec.children:
depth = spec.children_depth
additional.update(self.graph.select_children(selected, depth))
return additional
def select_nodes(self, spec: SelectionSpec) -> Set[UniqueId]:
"""Select the nodes in the graph according to the spec.
If the spec is a composite spec (a union, difference, or intersection),
recurse into its selections and combine them. If the spec is a concrete
selection criteria, resolve that using the given graph.
"""
if isinstance(spec, SelectionCriteria):
result = self.get_nodes_from_criteria(spec)
else:
node_selections = [
self.select_nodes(component)
for component in spec
]
result = spec.combined(node_selections)
if spec.expect_exists:
alert_non_existence(spec.raw, result)
return result
def _is_graph_member(self, unique_id: UniqueId) -> bool:
if unique_id in self.manifest.sources:
source = self.manifest.sources[unique_id]
return source.config.enabled
elif unique_id in self.manifest.exposures:
return True
node = self.manifest.nodes[unique_id]
return not node.empty and node.config.enabled
def node_is_match(self, node: GraphMemberNode) -> bool:
"""Determine if a node is a match for the selector. Non-match nodes
will be excluded from results during filtering.
"""
return True
def _is_match(self, unique_id: UniqueId) -> bool:
node: GraphMemberNode
if unique_id in self.manifest.nodes:
node = self.manifest.nodes[unique_id]
elif unique_id in self.manifest.sources:
node = self.manifest.sources[unique_id]
elif unique_id in self.manifest.exposures:
node = self.manifest.exposures[unique_id]
else:
raise InternalException(
f'Node {unique_id} not found in the manifest!'
)
return self.node_is_match(node)
def filter_selection(self, selected: Set[UniqueId]) -> Set[UniqueId]:
"""Return the subset of selected nodes that is a match for this
selector.
"""
return {
unique_id for unique_id in selected if self._is_match(unique_id)
}
def expand_selection(self, selected: Set[UniqueId]) -> Set[UniqueId]:
"""Perform selector-specific expansion."""
return selected
def get_selected(self, spec: SelectionSpec) -> Set[UniqueId]:
"""get_selected runs trhough the node selection process:
- node selection. Based on the include/exclude sets, the set
of matched unique IDs is returned
- expand the graph at each leaf node, before combination
- selectors might override this. for example, this is where
tests are added
- filtering:
- selectors can filter the nodes after all of them have been
selected
"""
selected_nodes = self.select_nodes(spec)
filtered_nodes = self.filter_selection(selected_nodes)
return filtered_nodes
def get_graph_queue(self, spec: SelectionSpec) -> GraphQueue:
"""Returns a queue over nodes in the graph that tracks progress of
dependecies.
"""
selected_nodes = self.get_selected(spec)
new_graph = self.full_graph.get_subset_graph(selected_nodes)
# should we give a way here for consumers to mutate the graph?
return GraphQueue(new_graph.graph, self.manifest, selected_nodes)
class ResourceTypeSelector(NodeSelector):
def __init__(
self,
graph: Graph,
manifest: Manifest,
previous_state: Optional[PreviousState],
resource_types: List[NodeType],
):
super().__init__(
graph=graph,
manifest=manifest,
previous_state=previous_state,
)
self.resource_types: Set[NodeType] = set(resource_types)
def node_is_match(self, node):
return node.resource_type in self.resource_types

View File

@@ -0,0 +1,523 @@
import abc
from itertools import chain
from pathlib import Path
from typing import Set, List, Dict, Iterator, Tuple, Any, Union, Type, Optional
from hologram.helpers import StrEnum
from .graph import UniqueId
from dbt.contracts.graph.compiled import (
CompiledDataTestNode,
CompiledSchemaTestNode,
CompileResultNode,
ManifestNode,
)
from dbt.contracts.graph.manifest import Manifest, WritableManifest
from dbt.contracts.graph.parsed import (
HasTestMetadata,
ParsedDataTestNode,
ParsedExposure,
ParsedSchemaTestNode,
ParsedSourceDefinition,
)
from dbt.contracts.state import PreviousState
from dbt.logger import GLOBAL_LOGGER as logger
from dbt.exceptions import (
InternalException,
RuntimeException,
)
from dbt.node_types import NodeType
from dbt.ui import warning_tag
SELECTOR_GLOB = '*'
SELECTOR_DELIMITER = ':'
class MethodName(StrEnum):
FQN = 'fqn'
Tag = 'tag'
Source = 'source'
Path = 'path'
Package = 'package'
Config = 'config'
TestName = 'test_name'
TestType = 'test_type'
ResourceType = 'resource_type'
State = 'state'
Exposure = 'exposure'
def is_selected_node(real_node, node_selector):
for i, selector_part in enumerate(node_selector):
is_last = (i == len(node_selector) - 1)
# if we hit a GLOB, then this node is selected
if selector_part == SELECTOR_GLOB:
return True
# match package.node_name or package.dir.node_name
elif is_last and selector_part == real_node[-1]:
return True
elif len(real_node) <= i:
return False
elif real_node[i] == selector_part:
continue
else:
return False
# if we get all the way down here, then the node is a match
return True
SelectorTarget = Union[ParsedSourceDefinition, ManifestNode, ParsedExposure]
class SelectorMethod(metaclass=abc.ABCMeta):
def __init__(
self,
manifest: Manifest,
previous_state: Optional[PreviousState],
arguments: List[str]
):
self.manifest: Manifest = manifest
self.previous_state = previous_state
self.arguments: List[str] = arguments
def parsed_nodes(
self,
included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, ManifestNode]]:
for key, node in self.manifest.nodes.items():
unique_id = UniqueId(key)
if unique_id not in included_nodes:
continue
yield unique_id, node
def source_nodes(
self,
included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, ParsedSourceDefinition]]:
for key, source in self.manifest.sources.items():
unique_id = UniqueId(key)
if unique_id not in included_nodes:
continue
yield unique_id, source
def exposure_nodes(
self,
included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, ParsedExposure]]:
for key, exposure in self.manifest.exposures.items():
unique_id = UniqueId(key)
if unique_id not in included_nodes:
continue
yield unique_id, exposure
def all_nodes(
self,
included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, SelectorTarget]]:
yield from chain(self.parsed_nodes(included_nodes),
self.source_nodes(included_nodes),
self.exposure_nodes(included_nodes))
def configurable_nodes(
self,
included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, CompileResultNode]]:
yield from chain(self.parsed_nodes(included_nodes),
self.source_nodes(included_nodes))
def non_source_nodes(
self,
included_nodes: Set[UniqueId],
) -> Iterator[Tuple[UniqueId, Union[ParsedExposure, ManifestNode]]]:
yield from chain(self.parsed_nodes(included_nodes),
self.exposure_nodes(included_nodes))
@abc.abstractmethod
def search(
self,
included_nodes: Set[UniqueId],
selector: str,
) -> Iterator[UniqueId]:
raise NotImplementedError('subclasses should implement this')
class QualifiedNameSelectorMethod(SelectorMethod):
def node_is_match(
self,
qualified_name: List[str],
package_names: Set[str],
fqn: List[str],
) -> bool:
"""Determine if a qualfied name matches an fqn, given the set of package
names in the graph.
:param List[str] qualified_name: The components of the selector or node
name, split on '.'.
:param Set[str] package_names: The set of pacakge names in the graph.
:param List[str] fqn: The node's fully qualified name in the graph.
"""
if len(qualified_name) == 1 and fqn[-1] == qualified_name[0]:
return True
if qualified_name[0] in package_names:
if is_selected_node(fqn, qualified_name):
return True
for package_name in package_names:
local_qualified_node_name = [package_name] + qualified_name
if is_selected_node(fqn, local_qualified_node_name):
return True
return False
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
"""Yield all nodes in the graph that match the selector.
:param str selector: The selector or node name
"""
qualified_name = selector.split(".")
parsed_nodes = list(self.parsed_nodes(included_nodes))
package_names = {n.package_name for _, n in parsed_nodes}
for node, real_node in parsed_nodes:
if self.node_is_match(
qualified_name,
package_names,
real_node.fqn,
):
yield node
class TagSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
""" yields nodes from included that have the specified tag """
for node, real_node in self.all_nodes(included_nodes):
if selector in real_node.tags:
yield node
class SourceSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
"""yields nodes from included are the specified source."""
parts = selector.split('.')
target_package = SELECTOR_GLOB
if len(parts) == 1:
target_source, target_table = parts[0], None
elif len(parts) == 2:
target_source, target_table = parts
elif len(parts) == 3:
target_package, target_source, target_table = parts
else: # len(parts) > 3 or len(parts) == 0
msg = (
'Invalid source selector value "{}". Sources must be of the '
'form `${{source_name}}`, '
'`${{source_name}}.${{target_name}}`, or '
'`${{package_name}}.${{source_name}}.${{target_name}}'
).format(selector)
raise RuntimeException(msg)
for node, real_node in self.source_nodes(included_nodes):
if target_package not in (real_node.package_name, SELECTOR_GLOB):
continue
if target_source not in (real_node.source_name, SELECTOR_GLOB):
continue
if target_table not in (None, real_node.name, SELECTOR_GLOB):
continue
yield node
class ExposureSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
parts = selector.split('.')
target_package = SELECTOR_GLOB
if len(parts) == 1:
target_name = parts[0]
elif len(parts) == 2:
target_package, target_name = parts
else:
msg = (
'Invalid exposure selector value "{}". Exposures must be of '
'the form ${{exposure_name}} or '
'${{exposure_package.exposure_name}}'
).format(selector)
raise RuntimeException(msg)
for node, real_node in self.exposure_nodes(included_nodes):
if target_package not in (real_node.package_name, SELECTOR_GLOB):
continue
if target_name not in (real_node.name, SELECTOR_GLOB):
continue
yield node
class PathSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
"""Yields nodes from inclucded that match the given path.
"""
# use '.' and not 'root' for easy comparison
root = Path.cwd()
paths = set(p.relative_to(root) for p in root.glob(selector))
for node, real_node in self.all_nodes(included_nodes):
if Path(real_node.root_path) != root:
continue
ofp = Path(real_node.original_file_path)
if ofp in paths:
yield node
elif any(parent in paths for parent in ofp.parents):
yield node
class PackageSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
"""Yields nodes from included that have the specified package"""
for node, real_node in self.all_nodes(included_nodes):
if real_node.package_name == selector:
yield node
def _getattr_descend(obj: Any, attrs: List[str]) -> Any:
value = obj
for attr in attrs:
try:
value = getattr(value, attr)
except AttributeError:
# if it implements getitem (dict, list, ...), use that. On failure,
# raise an attribute error instead of the KeyError, TypeError, etc.
# that arbitrary getitem calls might raise
try:
value = value[attr]
except Exception as exc:
raise AttributeError(
f"'{type(value)}' object has no attribute '{attr}'"
) from exc
return value
class CaseInsensitive(str):
def __eq__(self, other):
if isinstance(other, str):
return self.upper() == other.upper()
else:
return self.upper() == other
class ConfigSelectorMethod(SelectorMethod):
def search(
self,
included_nodes: Set[UniqueId],
selector: Any,
) -> Iterator[UniqueId]:
parts = self.arguments
# special case: if the user wanted to compare test severity,
# make the comparison case-insensitive
if parts == ['severity']:
selector = CaseInsensitive(selector)
# search sources is kind of useless now source configs only have
# 'enabled', which you can't really filter on anyway, but maybe we'll
# add more someday, so search them anyway.
for node, real_node in self.configurable_nodes(included_nodes):
try:
value = _getattr_descend(real_node.config, parts)
except AttributeError:
continue
else:
if selector == value:
yield node
class ResourceTypeSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
try:
resource_type = NodeType(selector)
except ValueError as exc:
raise RuntimeException(
f'Invalid resource_type selector "{selector}"'
) from exc
for node, real_node in self.parsed_nodes(included_nodes):
if real_node.resource_type == resource_type:
yield node
class TestNameSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
for node, real_node in self.parsed_nodes(included_nodes):
if isinstance(real_node, HasTestMetadata):
if real_node.test_metadata.name == selector:
yield node
class TestTypeSelectorMethod(SelectorMethod):
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
search_types: Tuple[Type, ...]
if selector == 'schema':
search_types = (ParsedSchemaTestNode, CompiledSchemaTestNode)
elif selector == 'data':
search_types = (ParsedDataTestNode, CompiledDataTestNode)
else:
raise RuntimeException(
f'Invalid test type selector {selector}: expected "data" or '
'"schema"'
)
for node, real_node in self.parsed_nodes(included_nodes):
if isinstance(real_node, search_types):
yield node
class StateSelectorMethod(SelectorMethod):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.macros_were_modified: Optional[List[str]] = None
def _macros_modified(self) -> List[str]:
# we checked in the caller!
if self.previous_state is None or self.previous_state.manifest is None:
raise InternalException(
'No comparison manifest in _macros_modified'
)
old_macros = self.previous_state.manifest.macros
new_macros = self.manifest.macros
modified = []
for uid, macro in new_macros.items():
name = f'{macro.package_name}.{macro.name}'
if uid in old_macros:
old_macro = old_macros[uid]
if macro.macro_sql != old_macro.macro_sql:
modified.append(f'{name} changed')
else:
modified.append(f'{name} added')
for uid, macro in old_macros.items():
if uid not in new_macros:
modified.append(f'{macro.package_name}.{macro.name} removed')
return modified[:3]
def check_modified(
self,
old: Optional[SelectorTarget],
new: SelectorTarget,
) -> bool:
# check if there are any changes in macros, if so, log a warning the
# first time
if self.macros_were_modified is None:
self.macros_were_modified = self._macros_modified()
if self.macros_were_modified:
log_str = ', '.join(self.macros_were_modified)
logger.warning(warning_tag(
f'During a state comparison, dbt detected a change in '
f'macros. This will not be marked as a modification. Some '
f'macros: {log_str}'
))
return not new.same_contents(old) # type: ignore
def check_new(
self,
old: Optional[SelectorTarget],
new: SelectorTarget,
) -> bool:
return old is None
def search(
self, included_nodes: Set[UniqueId], selector: str
) -> Iterator[UniqueId]:
if self.previous_state is None or self.previous_state.manifest is None:
raise RuntimeException(
'Got a state selector method, but no comparison manifest'
)
state_checks = {
'modified': self.check_modified,
'new': self.check_new,
}
if selector in state_checks:
checker = state_checks[selector]
else:
raise RuntimeException(
f'Got an invalid selector "{selector}", expected one of '
f'"{list(state_checks)}"'
)
manifest: WritableManifest = self.previous_state.manifest
for node, real_node in self.all_nodes(included_nodes):
previous_node: Optional[SelectorTarget] = None
if node in manifest.nodes:
previous_node = manifest.nodes[node]
elif node in manifest.sources:
previous_node = manifest.sources[node]
elif node in manifest.exposures:
previous_node = manifest.exposures[node]
if checker(previous_node, real_node):
yield node
class MethodManager:
SELECTOR_METHODS: Dict[MethodName, Type[SelectorMethod]] = {
MethodName.FQN: QualifiedNameSelectorMethod,
MethodName.Tag: TagSelectorMethod,
MethodName.Source: SourceSelectorMethod,
MethodName.Path: PathSelectorMethod,
MethodName.Package: PackageSelectorMethod,
MethodName.Config: ConfigSelectorMethod,
MethodName.TestName: TestNameSelectorMethod,
MethodName.TestType: TestTypeSelectorMethod,
MethodName.State: StateSelectorMethod,
MethodName.Exposure: ExposureSelectorMethod,
}
def __init__(
self,
manifest: Manifest,
previous_state: Optional[PreviousState],
):
self.manifest = manifest
self.previous_state = previous_state
def get_method(
self, method: MethodName, method_arguments: List[str]
) -> SelectorMethod:
if method not in self.SELECTOR_METHODS:
raise InternalException(
f'Method name "{method}" is a valid node selection '
f'method name, but it is not handled'
)
cls: Type[SelectorMethod] = self.SELECTOR_METHODS[method]
return cls(self.manifest, self.previous_state, method_arguments)

View File

@@ -0,0 +1,186 @@
import os
import re
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from typing import (
Set, Iterator, List, Optional, Dict, Union, Any, Iterable, Tuple
)
from .graph import UniqueId
from .selector_methods import MethodName
from dbt.exceptions import RuntimeException, InvalidSelectorException
RAW_SELECTOR_PATTERN = re.compile(
r'\A'
r'(?P<childrens_parents>(\@))?'
r'(?P<parents>((?P<parents_depth>(\d*))\+))?'
r'((?P<method>([\w.]+)):)?(?P<value>(.*?))'
r'(?P<children>(\+(?P<children_depth>(\d*))))?'
r'\Z'
)
SELECTOR_METHOD_SEPARATOR = '.'
def _probably_path(value: str):
"""Decide if value is probably a path. Windows has two path separators, so
we should check both sep ('\\') and altsep ('/') there.
"""
if os.path.sep in value:
return True
elif os.path.altsep is not None and os.path.altsep in value:
return True
else:
return False
def _match_to_int(match: Dict[str, str], key: str) -> Optional[int]:
raw = match.get(key)
# turn the empty string into None, too.
if not raw:
return None
try:
return int(raw)
except ValueError as exc:
raise RuntimeException(
f'Invalid node spec - could not handle parent depth {raw}'
) from exc
SelectionSpec = Union[
'SelectionCriteria',
'SelectionIntersection',
'SelectionDifference',
'SelectionUnion',
]
@dataclass
class SelectionCriteria:
raw: Any
method: MethodName
method_arguments: List[str]
value: Any
childrens_parents: bool
parents: bool
parents_depth: Optional[int]
children: bool
children_depth: Optional[int]
def __post_init__(self):
if self.children and self.childrens_parents:
raise RuntimeException(
f'Invalid node spec {self.raw} - "@" prefix and "+" suffix '
'are incompatible'
)
@classmethod
def default_method(cls, value: str) -> MethodName:
if _probably_path(value):
return MethodName.Path
else:
return MethodName.FQN
@classmethod
def parse_method(
cls, groupdict: Dict[str, Any]
) -> Tuple[MethodName, List[str]]:
raw_method = groupdict.get('method')
if raw_method is None:
return cls.default_method(groupdict['value']), []
method_parts: List[str] = raw_method.split(SELECTOR_METHOD_SEPARATOR)
try:
method_name = MethodName(method_parts[0])
except ValueError as exc:
raise InvalidSelectorException(method_parts[0]) from exc
method_arguments: List[str] = method_parts[1:]
return method_name, method_arguments
@classmethod
def from_dict(cls, raw: Any, dct: Dict[str, Any]) -> 'SelectionCriteria':
if 'value' not in dct:
raise RuntimeException(
f'Invalid node spec "{raw}" - no search value!'
)
method_name, method_arguments = cls.parse_method(dct)
parents_depth = _match_to_int(dct, 'parents_depth')
children_depth = _match_to_int(dct, 'children_depth')
return cls(
raw=raw,
method=method_name,
method_arguments=method_arguments,
value=dct['value'],
childrens_parents=bool(dct.get('childrens_parents')),
parents=bool(dct.get('parents')),
parents_depth=parents_depth,
children=bool(dct.get('children')),
children_depth=children_depth,
)
@classmethod
def from_single_spec(cls, raw: str) -> 'SelectionCriteria':
result = RAW_SELECTOR_PATTERN.match(raw)
if result is None:
# bad spec!
raise RuntimeException(f'Invalid selector spec "{raw}"')
return cls.from_dict(raw, result.groupdict())
class BaseSelectionGroup(Iterable[SelectionSpec], metaclass=ABCMeta):
def __init__(
self,
components: Iterable[SelectionSpec],
expect_exists: bool = False,
raw: Any = None,
):
self.components: List[SelectionSpec] = list(components)
self.expect_exists = expect_exists
self.raw = raw
def __iter__(self) -> Iterator[SelectionSpec]:
for component in self.components:
yield component
@abstractmethod
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
raise NotImplementedError(
'_combine_selections not implemented!'
)
def combined(self, selections: List[Set[UniqueId]]) -> Set[UniqueId]:
if not selections:
return set()
return self.combine_selections(selections)
class SelectionIntersection(BaseSelectionGroup):
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
return set.intersection(*selections)
class SelectionDifference(BaseSelectionGroup):
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
return set.difference(*selections)
class SelectionUnion(BaseSelectionGroup):
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
return set.union(*selections)

82
core/dbt/helper_types.py Normal file
View File

@@ -0,0 +1,82 @@
# never name this package "types", or mypy will crash in ugly ways
from dataclasses import dataclass
from datetime import timedelta
from pathlib import Path
from typing import NewType, Tuple, AbstractSet
from hologram import (
FieldEncoder, JsonSchemaMixin, JsonDict, ValidationError
)
from hologram.helpers import StrEnum
Port = NewType('Port', int)
class PortEncoder(FieldEncoder):
@property
def json_schema(self):
return {'type': 'integer', 'minimum': 0, 'maximum': 65535}
class TimeDeltaFieldEncoder(FieldEncoder[timedelta]):
"""Encodes timedeltas to dictionaries"""
def to_wire(self, value: timedelta) -> float:
return value.total_seconds()
def to_python(self, value) -> timedelta:
if isinstance(value, timedelta):
return value
try:
return timedelta(seconds=value)
except TypeError:
raise ValidationError(
'cannot encode {} into timedelta'.format(value)
) from None
@property
def json_schema(self) -> JsonDict:
return {'type': 'number'}
class PathEncoder(FieldEncoder):
def to_wire(self, value: Path) -> str:
return str(value)
def to_python(self, value) -> Path:
if isinstance(value, Path):
return value
try:
return Path(value)
except TypeError:
raise ValidationError(
'cannot encode {} into timedelta'.format(value)
) from None
@property
def json_schema(self) -> JsonDict:
return {'type': 'string'}
class NVEnum(StrEnum):
novalue = 'novalue'
def __eq__(self, other):
return isinstance(other, NVEnum)
@dataclass
class NoValue(JsonSchemaMixin):
"""Sometimes, you want a way to say none that isn't None"""
novalue: NVEnum = NVEnum.novalue
JsonSchemaMixin.register_field_encoders({
Port: PortEncoder(),
timedelta: TimeDeltaFieldEncoder(),
Path: PathEncoder(),
})
FQNPath = Tuple[str, ...]
PathSet = AbstractSet[FQNPath]

21
core/dbt/hooks.py Normal file
View File

@@ -0,0 +1,21 @@
from hologram.helpers import StrEnum
import json
from typing import Union, Dict, Any
class ModelHookType(StrEnum):
PreHook = 'pre-hook'
PostHook = 'post-hook'
def get_hook_dict(source: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
"""From a source string-or-dict, get a dictionary that can be passed to
Hook.from_dict
"""
if isinstance(source, dict):
return source
try:
return json.loads(source)
except ValueError:
return {'sql': source}

View File

@@ -0,0 +1,7 @@
import os
PACKAGE_PATH = os.path.dirname(__file__)
PROJECT_NAME = 'dbt'
DOCS_INDEX_FILE_PATH = os.path.normpath(
os.path.join(PACKAGE_PATH, '..', "index.html"))

View File

@@ -1,4 +1,4 @@
config-version: 2
name: dbt
version: 1.0

View File

@@ -27,7 +27,7 @@ button at the top-right of this lineage pane, you'll be able to see all of the m
or are built from, the model you're exploring.
Once expanded, you'll be able to use the `--models` and `--exclude` model selection syntax to filter the
models in the graph. For more information on model selection, check out the [dbt docs](https://docs.getdbt.com/reference#section-specifying-models-to-run).
models in the graph. For more information on model selection, check out the [dbt docs](https://docs.getdbt.com/docs/model-selection-syntax).
Note that you can also right-click on models to interactively filter and explore the graph.
@@ -38,6 +38,6 @@ Note that you can also right-click on models to interactively filter and explore
- [What is dbt](https://docs.getdbt.com/docs/overview)?
- Read the [dbt viewpoint](https://docs.getdbt.com/docs/viewpoint)
- [Installation](https://docs.getdbt.com/docs/installation)
- Join the [chat](https://slack.getdbt.com/) on Slack for live questions and support.
- Join the [chat](https://community.getdbt.com/) on Slack for live questions and support.
{% enddocs %}

View File

@@ -0,0 +1,290 @@
{% macro get_columns_in_query(select_sql) -%}
{{ return(adapter.dispatch('get_columns_in_query')(select_sql)) }}
{% endmacro %}
{% macro default__get_columns_in_query(select_sql) %}
{% call statement('get_columns_in_query', fetch_result=True, auto_begin=False) -%}
select * from (
{{ select_sql }}
) as __dbt_sbq
where false
limit 0
{% endcall %}
{{ return(load_result('get_columns_in_query').table.columns | map(attribute='name') | list) }}
{% endmacro %}
{% macro create_schema(relation) -%}
{{ adapter.dispatch('create_schema')(relation) }}
{% endmacro %}
{% macro default__create_schema(relation) -%}
{%- call statement('create_schema') -%}
create schema if not exists {{ relation.without_identifier() }}
{% endcall %}
{% endmacro %}
{% macro drop_schema(relation) -%}
{{ adapter.dispatch('drop_schema')(relation) }}
{% endmacro %}
{% macro default__drop_schema(relation) -%}
{%- call statement('drop_schema') -%}
drop schema if exists {{ relation.without_identifier() }} cascade
{% endcall %}
{% endmacro %}
{% macro create_table_as(temporary, relation, sql) -%}
{{ adapter.dispatch('create_table_as')(temporary, relation, sql) }}
{%- endmacro %}
{% macro default__create_table_as(temporary, relation, sql) -%}
{%- set sql_header = config.get('sql_header', none) -%}
{{ sql_header if sql_header is not none }}
create {% if temporary: -%}temporary{%- endif %} table
{{ relation.include(database=(not temporary), schema=(not temporary)) }}
as (
{{ sql }}
);
{% endmacro %}
{% macro create_view_as(relation, sql) -%}
{{ adapter.dispatch('create_view_as')(relation, sql) }}
{%- endmacro %}
{% macro default__create_view_as(relation, sql) -%}
{%- set sql_header = config.get('sql_header', none) -%}
{{ sql_header if sql_header is not none }}
create view {{ relation }} as (
{{ sql }}
);
{% endmacro %}
{% macro get_catalog(information_schema, schemas) -%}
{{ return(adapter.dispatch('get_catalog')(information_schema, schemas)) }}
{%- endmacro %}
{% macro default__get_catalog(information_schema, schemas) -%}
{% set typename = adapter.type() %}
{% set msg -%}
get_catalog not implemented for {{ typename }}
{%- endset %}
{{ exceptions.raise_compiler_error(msg) }}
{% endmacro %}
{% macro get_columns_in_relation(relation) -%}
{{ return(adapter.dispatch('get_columns_in_relation')(relation)) }}
{% endmacro %}
{% macro sql_convert_columns_in_relation(table) -%}
{% set columns = [] %}
{% for row in table %}
{% do columns.append(api.Column(*row)) %}
{% endfor %}
{{ return(columns) }}
{% endmacro %}
{% macro default__get_columns_in_relation(relation) -%}
{{ exceptions.raise_not_implemented(
'get_columns_in_relation macro not implemented for adapter '+adapter.type()) }}
{% endmacro %}
{% macro alter_column_type(relation, column_name, new_column_type) -%}
{{ return(adapter.dispatch('alter_column_type')(relation, column_name, new_column_type)) }}
{% endmacro %}
{% macro alter_column_comment(relation, column_dict) -%}
{{ return(adapter.dispatch('alter_column_comment')(relation, column_dict)) }}
{% endmacro %}
{% macro default__alter_column_comment(relation, column_dict) -%}
{{ exceptions.raise_not_implemented(
'alter_column_comment macro not implemented for adapter '+adapter.type()) }}
{% endmacro %}
{% macro alter_relation_comment(relation, relation_comment) -%}
{{ return(adapter.dispatch('alter_relation_comment')(relation, relation_comment)) }}
{% endmacro %}
{% macro default__alter_relation_comment(relation, relation_comment) -%}
{{ exceptions.raise_not_implemented(
'alter_relation_comment macro not implemented for adapter '+adapter.type()) }}
{% endmacro %}
{% macro persist_docs(relation, model, for_relation=true, for_columns=true) -%}
{{ return(adapter.dispatch('persist_docs')(relation, model, for_relation, for_columns)) }}
{% endmacro %}
{% macro default__persist_docs(relation, model, for_relation, for_columns) -%}
{% if for_relation and config.persist_relation_docs() and model.description %}
{% do run_query(alter_relation_comment(relation, model.description)) %}
{% endif %}
{% if for_columns and config.persist_column_docs() and model.columns %}
{% do run_query(alter_column_comment(relation, model.columns)) %}
{% endif %}
{% endmacro %}
{% macro default__alter_column_type(relation, column_name, new_column_type) -%}
{#
1. Create a new column (w/ temp name and correct type)
2. Copy data over to it
3. Drop the existing column (cascade!)
4. Rename the new column to existing column
#}
{%- set tmp_column = column_name + "__dbt_alter" -%}
{% call statement('alter_column_type') %}
alter table {{ relation }} add column {{ adapter.quote(tmp_column) }} {{ new_column_type }};
update {{ relation }} set {{ adapter.quote(tmp_column) }} = {{ adapter.quote(column_name) }};
alter table {{ relation }} drop column {{ adapter.quote(column_name) }} cascade;
alter table {{ relation }} rename column {{ adapter.quote(tmp_column) }} to {{ adapter.quote(column_name) }}
{% endcall %}
{% endmacro %}
{% macro drop_relation(relation) -%}
{{ return(adapter.dispatch('drop_relation')(relation)) }}
{% endmacro %}
{% macro default__drop_relation(relation) -%}
{% call statement('drop_relation', auto_begin=False) -%}
drop {{ relation.type }} if exists {{ relation }} cascade
{%- endcall %}
{% endmacro %}
{% macro truncate_relation(relation) -%}
{{ return(adapter.dispatch('truncate_relation')(relation)) }}
{% endmacro %}
{% macro default__truncate_relation(relation) -%}
{% call statement('truncate_relation') -%}
truncate table {{ relation }}
{%- endcall %}
{% endmacro %}
{% macro rename_relation(from_relation, to_relation) -%}
{{ return(adapter.dispatch('rename_relation')(from_relation, to_relation)) }}
{% endmacro %}
{% macro default__rename_relation(from_relation, to_relation) -%}
{% set target_name = adapter.quote_as_configured(to_relation.identifier, 'identifier') %}
{% call statement('rename_relation') -%}
alter table {{ from_relation }} rename to {{ target_name }}
{%- endcall %}
{% endmacro %}
{% macro information_schema_name(database) %}
{{ return(adapter.dispatch('information_schema_name')(database)) }}
{% endmacro %}
{% macro default__information_schema_name(database) -%}
{%- if database -%}
{{ database }}.INFORMATION_SCHEMA
{%- else -%}
INFORMATION_SCHEMA
{%- endif -%}
{%- endmacro %}
{% macro list_schemas(database) -%}
{{ return(adapter.dispatch('list_schemas')(database)) }}
{% endmacro %}
{% macro default__list_schemas(database) -%}
{% set sql %}
select distinct schema_name
from {{ information_schema_name(database) }}.SCHEMATA
where catalog_name ilike '{{ database }}'
{% endset %}
{{ return(run_query(sql)) }}
{% endmacro %}
{% macro check_schema_exists(information_schema, schema) -%}
{{ return(adapter.dispatch('check_schema_exists')(information_schema, schema)) }}
{% endmacro %}
{% macro default__check_schema_exists(information_schema, schema) -%}
{% set sql -%}
select count(*)
from {{ information_schema.replace(information_schema_view='SCHEMATA') }}
where catalog_name='{{ information_schema.database }}'
and schema_name='{{ schema }}'
{%- endset %}
{{ return(run_query(sql)) }}
{% endmacro %}
{% macro list_relations_without_caching(schema_relation) %}
{{ return(adapter.dispatch('list_relations_without_caching')(schema_relation)) }}
{% endmacro %}
{% macro default__list_relations_without_caching(schema_relation) %}
{{ exceptions.raise_not_implemented(
'list_relations_without_caching macro not implemented for adapter '+adapter.type()) }}
{% endmacro %}
{% macro current_timestamp() -%}
{{ adapter.dispatch('current_timestamp')() }}
{%- endmacro %}
{% macro default__current_timestamp() -%}
{{ exceptions.raise_not_implemented(
'current_timestamp macro not implemented for adapter '+adapter.type()) }}
{%- endmacro %}
{% macro collect_freshness(source, loaded_at_field, filter) %}
{{ return(adapter.dispatch('collect_freshness')(source, loaded_at_field, filter))}}
{% endmacro %}
{% macro default__collect_freshness(source, loaded_at_field, filter) %}
{% call statement('collect_freshness', fetch_result=True, auto_begin=False) -%}
select
max({{ loaded_at_field }}) as max_loaded_at,
{{ current_timestamp() }} as snapshotted_at
from {{ source }}
{% if filter %}
where {{ filter }}
{% endif %}
{% endcall %}
{{ return(load_result('collect_freshness').table) }}
{% endmacro %}
{% macro make_temp_relation(base_relation, suffix='__dbt_tmp') %}
{{ return(adapter.dispatch('make_temp_relation')(base_relation, suffix))}}
{% endmacro %}
{% macro default__make_temp_relation(base_relation, suffix) %}
{% set tmp_identifier = base_relation.identifier ~ suffix %}
{% set tmp_relation = base_relation.incorporate(
path={"identifier": tmp_identifier}) -%}
{% do return(tmp_relation) %}
{% endmacro %}
{% macro set_sql_header(config) -%}
{{ config.set('sql_header', caller()) }}
{%- endmacro %}

View File

@@ -1,6 +1,6 @@
{% macro statement(name=None, fetch_result=False, auto_begin=True) -%}
{%- if execute: -%}
{%- set sql = render(caller()) -%}
{%- set sql = caller() -%}
{%- if name == 'main' -%}
{{ log('Writing runtime SQL for node "{}"'.format(model['unique_id'])) }}
@@ -16,7 +16,7 @@
{%- endmacro %}
{% macro noop_statement(name=None, status=None, res=None) -%}
{%- set sql = render(caller()) -%}
{%- set sql = caller() -%}
{%- if name == 'main' -%}
{{ log('Writing runtime SQL for node "{}"'.format(model['unique_id'])) }}

View File

@@ -48,9 +48,13 @@
{% set start_date = partition_range[0] %}
{% set end_date = partition_range[1] %}
{% else %}
{{ dbt.exceptions.raise_compiler_error("Invalid partition time. Expected format: {Start Date}[,{End Date}]. Got: " ~ raw_partition_date) }}
{{ exceptions.raise_compiler_error("Invalid partition time. Expected format: {Start Date}[,{End Date}]. Got: " ~ raw_partition_date) }}
{% endif %}
{{ return(dates_in_range(start_date, end_date, in_fmt=date_fmt)) }}
{% endmacro %}
{% macro py_current_timestring() %}
{% set dt = modules.datetime.datetime.now() %}
{% do return(dt.strftime("%Y%m%d%H%M%S%f")) %}
{% endmacro %}

View File

@@ -0,0 +1,27 @@
{#
Renders a alias name given a custom alias name. If the custom
alias name is none, then the resulting alias is just the filename of the
model. If an alias override is specified, then that is used.
This macro can be overriden in projects to define different semantics
for rendering a alias name.
Arguments:
custom_alias_name: The custom alias name specified for a model, or none
node: The available node that an alias is being generated for, or none
#}
{% macro generate_alias_name(custom_alias_name=none, node=none) -%}
{%- if custom_alias_name is none -%}
{{ node.name }}
{%- else -%}
{{ custom_alias_name | trim }}
{%- endif -%}
{%- endmacro %}

View File

@@ -0,0 +1,32 @@
{#
Renders a database name given a custom database name. If the custom
database name is none, then the resulting database is just the "database"
value in the specified target. If a database override is specified, then
the resulting database is the default database concatenated with the
custom database.
This macro can be overriden in projects to define different semantics
for rendering a database name.
Arguments:
custom_database_name: The custom database name specified for a model, or none
node: The node the database is being generated for
#}
{% macro generate_database_name(custom_database_name=none, node=none) -%}
{% do return(adapter.dispatch('generate_database_name')(custom_database_name, node)) %}
{%- endmacro %}
{% macro default__generate_database_name(custom_database_name=none, node=none) -%}
{%- set default_database = target.database -%}
{%- if custom_database_name is none -%}
{{ default_database }}
{%- else -%}
{{ custom_database_name }}
{%- endif -%}
{%- endmacro %}

View File

@@ -3,7 +3,7 @@
Renders a schema name given a custom schema name. If the custom
schema name is none, then the resulting schema is just the "schema"
value in the specified target. If a schema override is specified, then
the resulting schema is the default schema concatenated with the
the resulting schema is the default schema concatenated with the
custom schema.
This macro can be overriden in projects to define different semantics
@@ -11,9 +11,10 @@
Arguments:
custom_schema_name: The custom schema name specified for a model, or none
node: The node the schema is being generated for
#}
{% macro generate_schema_name(custom_schema_name=none) -%}
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if custom_schema_name is none -%}
@@ -36,9 +37,10 @@
Arguments:
custom_schema_name: The custom schema name specified for a model, or none
node: The node the schema is being generated for
#}
{% macro generate_schema_name_for_env(custom_schema_name=none) -%}
{% macro generate_schema_name_for_env(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if target.name == 'prod' and custom_schema_name is not none -%}

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