dbt-autofix 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbt_autofix-0.1.0/PKG-INFO +69 -0
- dbt_autofix-0.1.0/README.md +49 -0
- dbt_autofix-0.1.0/dbt_autofix/__init__.py +0 -0
- dbt_autofix-0.1.0/dbt_autofix/duplicate_keys.py +127 -0
- dbt_autofix-0.1.0/dbt_autofix/fields_properties_configs.py +328 -0
- dbt_autofix-0.1.0/dbt_autofix/main.py +60 -0
- dbt_autofix-0.1.0/dbt_autofix/refactor.py +701 -0
- dbt_autofix-0.1.0/dbt_autofix/retrieve_schemas.py +134 -0
- dbt_autofix-0.1.0/pyproject.toml +83 -0
- dbt_autofix-0.1.0/tests/integration_tests/.gitignore +1 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/.gitignore +4 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/README.md +15 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/analyses/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/dbt_project.yml +36 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/macros/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/models/example/my_first_dbt_model.sql +27 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/models/example/my_second_dbt_model.sql +6 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/models/example/yml_with_anchors.yml +21 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/models/how_to.md +14 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/seeds/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/snapshots/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1/tests/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/.gitignore +4 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/README.md +15 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/analyses/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/dbt_project.yml +36 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/macros/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/models/example/my_first_dbt_model.sql +27 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/models/example/my_second_dbt_model.sql +6 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/models/example/yml_with_anchors.yml +21 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/models/how_to.md +14 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/seeds/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/snapshots/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/dbt_projects/project1_expected/tests/.gitkeep +0 -0
- dbt_autofix-0.1.0/tests/integration_tests/test_full_dbt_projects.py +75 -0
- dbt_autofix-0.1.0/tests/unit_tests/test_check_fields_no_overlap.py +21 -0
- dbt_autofix-0.1.0/tests/unit_tests/test_duplicate_keys.py +139 -0
- dbt_autofix-0.1.0/tests/unit_tests/test_refactor.py +690 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dbt-autofix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI to autofix deprecations in dbt projects
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
7
|
+
Requires-Dist: typer>=0.15.2
|
|
8
|
+
Requires-Dist: yamllint>=1.37.0
|
|
9
|
+
Requires-Dist: rich>=13.7.0
|
|
10
|
+
Requires-Dist: click<8.2.0
|
|
11
|
+
Requires-Dist: ruamel-yaml>=0.18.10
|
|
12
|
+
Requires-Dist: ruamel-yaml-string>=0.1.1
|
|
13
|
+
Requires-Dist: httpx>=0.28.1
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=7.2.0; extra == "test"
|
|
16
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
|
|
17
|
+
Requires-Dist: pre-commit>=4.2.0; extra == "test"
|
|
18
|
+
Requires-Dist: nox; extra == "test"
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# dbt-autofix (previously dbt-cleanup)
|
|
22
|
+
|
|
23
|
+
This tool can help teams clean up their dbt projects so that it conforms with dbt configuration best practices and update deprecated config:
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
| Deprecation Code in dbt Core | Files | Description |
|
|
27
|
+
| ---------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| `CustomKeyInObjectDeprecation` (*) | YAML files | Move all models configs under `config:` in YAML files |
|
|
29
|
+
| `CustomKeyInObjectDeprecation` (*) | YAML files | Move all models extra config (not valid or custom) under `meta:` and `meta` under `config:` |
|
|
30
|
+
| `DuplicateYAMLKeysDeprecation` | YAML files | Remove duplicate keys in YAML files, keeping the second one to keep the same behaviour |
|
|
31
|
+
| `UnexpectedJinjaBlockDeprecation` | SQL files | Remove extra `{% endmacro %}` and `{% endif %}` that don't have corresponding opening statements |
|
|
32
|
+
| - | `dbt_project.yml` | Prefix all configs for modeles/tests etc... with a `+` |
|
|
33
|
+
| `ConfigDataPathDeprecation` | `dbt_project.yml` | Remove deprecated config for data path (now seed) |
|
|
34
|
+
| `ConfigLogPathDeprecation` | `dbt_project.yml` | Remove deprecated config for log path |
|
|
35
|
+
| `ConfigSourcePathDeprecation` | `dbt_project.yml` | Remove deprecated config for source path |
|
|
36
|
+
| `ConfigTargetPathDeprecation` | `dbt_project.yml` | Remove deprecated config for target path |
|
|
37
|
+
|
|
38
|
+
(*) : those autofix rules are currently deactivated in `main`
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
To run it from the git repo directly, install `uv` and then
|
|
43
|
+
|
|
44
|
+
run the tool directly
|
|
45
|
+
```sh
|
|
46
|
+
uvx --from git+https://github.com/dbt-labs/dbt-autofix.git dbt-autofix --help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
or install it so that it can be run with `dbt-cleanup` in the future
|
|
50
|
+
```sh
|
|
51
|
+
uv tool install --from git+https://github.com/dbt-labs/dbt-autofix.git dbt-autofix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
- `dbt-autofix deprecations`: refactor YAML and SQL files to fix some deprecations
|
|
57
|
+
- add `--path <mypath>` to configure the path of the dbt project (defaults to `.`)
|
|
58
|
+
- add `--dry-run` for running in dry run mode
|
|
59
|
+
- add `--json` to get resulting data in a JSONL format
|
|
60
|
+
|
|
61
|
+
Each JSON object will have the following keys:
|
|
62
|
+
|
|
63
|
+
- "mode": "applied" or "dry_run"
|
|
64
|
+
- "file_path": the full path of the file modified. Each file will appear only once
|
|
65
|
+
- "refactors": the list of refactoring rules applied
|
|
66
|
+
|
|
67
|
+
Calling `deprecations` without `--dry-run` should be safe if your dbt code is part of a git repo.
|
|
68
|
+
|
|
69
|
+
Please review the suggested changes to your dbt project before merging to `main` and make those changes go through your typical CI/CD process.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# dbt-autofix (previously dbt-cleanup)
|
|
2
|
+
|
|
3
|
+
This tool can help teams clean up their dbt projects so that it conforms with dbt configuration best practices and update deprecated config:
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
| Deprecation Code in dbt Core | Files | Description |
|
|
7
|
+
| ---------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
|
|
8
|
+
| `CustomKeyInObjectDeprecation` (*) | YAML files | Move all models configs under `config:` in YAML files |
|
|
9
|
+
| `CustomKeyInObjectDeprecation` (*) | YAML files | Move all models extra config (not valid or custom) under `meta:` and `meta` under `config:` |
|
|
10
|
+
| `DuplicateYAMLKeysDeprecation` | YAML files | Remove duplicate keys in YAML files, keeping the second one to keep the same behaviour |
|
|
11
|
+
| `UnexpectedJinjaBlockDeprecation` | SQL files | Remove extra `{% endmacro %}` and `{% endif %}` that don't have corresponding opening statements |
|
|
12
|
+
| - | `dbt_project.yml` | Prefix all configs for modeles/tests etc... with a `+` |
|
|
13
|
+
| `ConfigDataPathDeprecation` | `dbt_project.yml` | Remove deprecated config for data path (now seed) |
|
|
14
|
+
| `ConfigLogPathDeprecation` | `dbt_project.yml` | Remove deprecated config for log path |
|
|
15
|
+
| `ConfigSourcePathDeprecation` | `dbt_project.yml` | Remove deprecated config for source path |
|
|
16
|
+
| `ConfigTargetPathDeprecation` | `dbt_project.yml` | Remove deprecated config for target path |
|
|
17
|
+
|
|
18
|
+
(*) : those autofix rules are currently deactivated in `main`
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
To run it from the git repo directly, install `uv` and then
|
|
23
|
+
|
|
24
|
+
run the tool directly
|
|
25
|
+
```sh
|
|
26
|
+
uvx --from git+https://github.com/dbt-labs/dbt-autofix.git dbt-autofix --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
or install it so that it can be run with `dbt-cleanup` in the future
|
|
30
|
+
```sh
|
|
31
|
+
uv tool install --from git+https://github.com/dbt-labs/dbt-autofix.git dbt-autofix
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
- `dbt-autofix deprecations`: refactor YAML and SQL files to fix some deprecations
|
|
37
|
+
- add `--path <mypath>` to configure the path of the dbt project (defaults to `.`)
|
|
38
|
+
- add `--dry-run` for running in dry run mode
|
|
39
|
+
- add `--json` to get resulting data in a JSONL format
|
|
40
|
+
|
|
41
|
+
Each JSON object will have the following keys:
|
|
42
|
+
|
|
43
|
+
- "mode": "applied" or "dry_run"
|
|
44
|
+
- "file_path": the full path of the file modified. Each file will appear only once
|
|
45
|
+
- "refactors": the list of refactoring rules applied
|
|
46
|
+
|
|
47
|
+
Calling `deprecations` without `--dry-run` should be safe if your dbt code is part of a git repo.
|
|
48
|
+
|
|
49
|
+
Please review the suggested changes to your dbt project before merging to `main` and make those changes go through your typical CI/CD process.
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
import yamllint.config
|
|
7
|
+
import yamllint.linter
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from dbt_autofix.refactor import DbtYAML
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
config = """
|
|
15
|
+
rules:
|
|
16
|
+
key-duplicates: enable
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
yaml_config = yamllint.config.YamlLintConfig(config)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class DuplicateFound:
|
|
24
|
+
file: Path
|
|
25
|
+
line: int
|
|
26
|
+
key: str
|
|
27
|
+
value: str
|
|
28
|
+
|
|
29
|
+
def __str__(self):
|
|
30
|
+
return f"{self.file}:{self.line} -- {self.value}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_duplicate_keys(
|
|
34
|
+
root_dir: Path,
|
|
35
|
+
dry_run: bool = False,
|
|
36
|
+
) -> Tuple[List[DuplicateFound], List[DuplicateFound]]:
|
|
37
|
+
"""
|
|
38
|
+
Find duplicate keys in the project and packages.
|
|
39
|
+
"""
|
|
40
|
+
project_duplicates: List[DuplicateFound] = []
|
|
41
|
+
package_duplicates: List[DuplicateFound] = []
|
|
42
|
+
|
|
43
|
+
yml_files = set(root_dir.glob("**/*.yml")).union(set(root_dir.glob("**/*.yaml")))
|
|
44
|
+
yml_files_target = set((root_dir / "target").glob("**/*.yml")).union(set((root_dir / "target").glob("**/*.yaml")))
|
|
45
|
+
|
|
46
|
+
packages_path = yaml.safe_load((root_dir / "dbt_project.yml").read_text()).get(
|
|
47
|
+
"packages-install-path", "dbt_packages"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
yml_files_packages = set((root_dir / packages_path).glob("**/*.yml")).union(
|
|
51
|
+
set((root_dir / packages_path).glob("**/*.yaml"))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# this is a hack to avoid checking integration_tests. it won't work everywhere but it's good enough for now
|
|
55
|
+
yml_files_packages_integration_tests = set((root_dir / packages_path).glob("**/integration_tests/**/*.yml")).union(
|
|
56
|
+
set((root_dir / packages_path).glob("**/integration_tests/**/*.yaml"))
|
|
57
|
+
)
|
|
58
|
+
yml_files_packages_not_integration_tests = yml_files_packages - yml_files_packages_integration_tests
|
|
59
|
+
|
|
60
|
+
yml_files_not_target_or_packages = yml_files - yml_files_target - yml_files_packages
|
|
61
|
+
|
|
62
|
+
# Check project YML files
|
|
63
|
+
for file in yml_files_not_target_or_packages:
|
|
64
|
+
file_with_duplicate = False
|
|
65
|
+
file_content = file.read_text()
|
|
66
|
+
for p in yamllint.linter.run(file_content, yaml_config):
|
|
67
|
+
if p.rule == "key-duplicates":
|
|
68
|
+
file_with_duplicate = True
|
|
69
|
+
project_duplicates.append(
|
|
70
|
+
DuplicateFound(
|
|
71
|
+
file=file,
|
|
72
|
+
line=p.line,
|
|
73
|
+
key=p.desc.split('"')[1] if '"' in p.desc else "", # Extract key from description
|
|
74
|
+
value=p.desc,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
if file_with_duplicate and not dry_run:
|
|
78
|
+
without_duplicates = yaml.safe_load(file_content)
|
|
79
|
+
ruamel_yaml = DbtYAML()
|
|
80
|
+
ruamel_yaml.dump_to_string(without_duplicates) # type: ignore
|
|
81
|
+
|
|
82
|
+
# Check package YML files
|
|
83
|
+
for file in yml_files_packages_not_integration_tests:
|
|
84
|
+
file_content = file.read_text()
|
|
85
|
+
for p in yamllint.linter.run(file_content, yaml_config):
|
|
86
|
+
if p.rule == "key-duplicates":
|
|
87
|
+
package_duplicates.append(
|
|
88
|
+
DuplicateFound(
|
|
89
|
+
file=file,
|
|
90
|
+
line=p.line,
|
|
91
|
+
key=p.desc.split('"')[1] if '"' in p.desc else "", # Extract key from description
|
|
92
|
+
value=p.desc,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return project_duplicates, package_duplicates
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def print_duplicate_keys(project_duplicates: List[DuplicateFound], package_duplicates: List[DuplicateFound]) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Print duplicate keys in the project and packages as well as instructions to fix them.
|
|
102
|
+
"""
|
|
103
|
+
if not project_duplicates and not package_duplicates:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
if project_duplicates:
|
|
107
|
+
console.print("\nThere are issues in your project YML files", style="bold red")
|
|
108
|
+
console.print(
|
|
109
|
+
(
|
|
110
|
+
"Please remove duplicates by hand. dbt's default behavior is to keep the last occurence of a key.\n"
|
|
111
|
+
"If you want to keep the same behaviour remove or comments lines found for the same key and before in the file.\n"
|
|
112
|
+
"Once you have done all the changes in the files, run the tool again.\n"
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
for dup in project_duplicates:
|
|
116
|
+
console.print(str(dup))
|
|
117
|
+
|
|
118
|
+
if package_duplicates:
|
|
119
|
+
console.print(
|
|
120
|
+
(
|
|
121
|
+
"\nThose packages might have issues. If those are not maintained by you, check if there are updates available. "
|
|
122
|
+
"If they are private packages, remove duplicates in their own repository and publish a new version.\n"
|
|
123
|
+
),
|
|
124
|
+
style="bold red",
|
|
125
|
+
)
|
|
126
|
+
for dup in package_duplicates:
|
|
127
|
+
console.print(str(dup))
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from rich import print
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class AllowedConfig:
|
|
9
|
+
allowed_config_fields: set[str]
|
|
10
|
+
allowed_properties: set[str]
|
|
11
|
+
|
|
12
|
+
def __post_init__(self):
|
|
13
|
+
self.allowed_config_fields_without_meta = self.allowed_config_fields - {"meta"}
|
|
14
|
+
# in case we forgot to remove meta from the allowed properties
|
|
15
|
+
self.allowed_properties = self.allowed_properties - {"meta"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
models_allowed_config = AllowedConfig(
|
|
19
|
+
allowed_config_fields=set(
|
|
20
|
+
[
|
|
21
|
+
# model specific
|
|
22
|
+
"materialized",
|
|
23
|
+
"sql_header",
|
|
24
|
+
"on_configuration_change",
|
|
25
|
+
"unique_key",
|
|
26
|
+
"incremental_strategy", # was missing at first
|
|
27
|
+
"on_schema_change", # was missing at first
|
|
28
|
+
"batch_size",
|
|
29
|
+
"begin",
|
|
30
|
+
"lookback",
|
|
31
|
+
"concurrent_batches",
|
|
32
|
+
# general
|
|
33
|
+
"enabled",
|
|
34
|
+
"tags",
|
|
35
|
+
"pre_hook",
|
|
36
|
+
"post_hook",
|
|
37
|
+
"database",
|
|
38
|
+
"schema",
|
|
39
|
+
"alias",
|
|
40
|
+
"persist_docs",
|
|
41
|
+
"meta",
|
|
42
|
+
"grants",
|
|
43
|
+
"contract",
|
|
44
|
+
"event_time",
|
|
45
|
+
# Snowflake: https://github.com/dbt-labs/dbt-adapters/blob/af33935b119347cc021554ea854884bce986ef8d/dbt-snowflake/src/dbt/adapters/snowflake/impl.py#L42-L58
|
|
46
|
+
"transient",
|
|
47
|
+
"cluster_by",
|
|
48
|
+
"automatic_clustering",
|
|
49
|
+
"secure",
|
|
50
|
+
"copy_grants",
|
|
51
|
+
"snowflake_warehouse",
|
|
52
|
+
"query_tag",
|
|
53
|
+
"tmp_relation_type",
|
|
54
|
+
"merge_update_columns",
|
|
55
|
+
"target_lag",
|
|
56
|
+
"table_format",
|
|
57
|
+
"external_volume",
|
|
58
|
+
"base_location_root",
|
|
59
|
+
"base_location_subpath",
|
|
60
|
+
# BQ: https://github.com/dbt-labs/dbt-adapters/blob/af33935b119347cc021554ea854884bce986ef8d/dbt-bigquery/src/dbt/adapters/bigquery/impl.py#L98
|
|
61
|
+
"dataset",
|
|
62
|
+
"project",
|
|
63
|
+
"cluster_by",
|
|
64
|
+
"partition_by",
|
|
65
|
+
"kms_key_name",
|
|
66
|
+
"labels",
|
|
67
|
+
"partitions",
|
|
68
|
+
"grant_access_to",
|
|
69
|
+
"hours_to_expiration",
|
|
70
|
+
"require_partition_filter",
|
|
71
|
+
"partition_expiration_days",
|
|
72
|
+
"merge_update_columns",
|
|
73
|
+
"enable_refresh",
|
|
74
|
+
"refresh_interval_minutes",
|
|
75
|
+
"max_staleness",
|
|
76
|
+
"enable_list_inference",
|
|
77
|
+
"intermediate_format",
|
|
78
|
+
"submission_method",
|
|
79
|
+
# Postgres
|
|
80
|
+
"unlogged",
|
|
81
|
+
"indexes",
|
|
82
|
+
# Redshift
|
|
83
|
+
"sort_type",
|
|
84
|
+
"dist",
|
|
85
|
+
"sort",
|
|
86
|
+
"bind",
|
|
87
|
+
"backup",
|
|
88
|
+
"auto_refresh",
|
|
89
|
+
# DBX: https://github.com/databricks/dbt-databricks/blob/fde68588d45e8a299a83318acdca6c97081088e8/dbt/adapters/databricks/impl.py#L105
|
|
90
|
+
"file_format",
|
|
91
|
+
"table_format",
|
|
92
|
+
"location_root",
|
|
93
|
+
"include_full_name_in_path",
|
|
94
|
+
"partition_by",
|
|
95
|
+
"clustered_by",
|
|
96
|
+
"liquid_clustered_by",
|
|
97
|
+
"auto_liquid_cluster",
|
|
98
|
+
"buckets",
|
|
99
|
+
"options",
|
|
100
|
+
"merge_update_columns",
|
|
101
|
+
"merge_exclude_columns",
|
|
102
|
+
"databricks_tags",
|
|
103
|
+
"tblproperties",
|
|
104
|
+
"zorder",
|
|
105
|
+
"unique_tmp_table_suffix",
|
|
106
|
+
"skip_non_matched_step",
|
|
107
|
+
"skip_matched_step",
|
|
108
|
+
"matched_condition",
|
|
109
|
+
"not_matched_condition",
|
|
110
|
+
"not_matched_by_source_action",
|
|
111
|
+
"not_matched_by_source_condition",
|
|
112
|
+
"target_alias",
|
|
113
|
+
"source_alias",
|
|
114
|
+
"merge_with_schema_evolution",
|
|
115
|
+
# moved from property
|
|
116
|
+
"docs",
|
|
117
|
+
"access",
|
|
118
|
+
"group",
|
|
119
|
+
# new
|
|
120
|
+
"freshness",
|
|
121
|
+
]
|
|
122
|
+
),
|
|
123
|
+
allowed_properties=set(
|
|
124
|
+
[
|
|
125
|
+
"name",
|
|
126
|
+
"description",
|
|
127
|
+
"latest_version",
|
|
128
|
+
"deprecation_date",
|
|
129
|
+
"config",
|
|
130
|
+
"constraints",
|
|
131
|
+
"data_tests",
|
|
132
|
+
"tests",
|
|
133
|
+
"columns",
|
|
134
|
+
"time_spine",
|
|
135
|
+
"versions",
|
|
136
|
+
]
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
sources_allowed_config = AllowedConfig(
|
|
140
|
+
allowed_config_fields=set(["enabled", "event_time", "meta", "freshness", "tags"]),
|
|
141
|
+
allowed_properties=set(
|
|
142
|
+
[
|
|
143
|
+
"name",
|
|
144
|
+
"description",
|
|
145
|
+
"database",
|
|
146
|
+
"schema",
|
|
147
|
+
"loader",
|
|
148
|
+
"loaded_at_field",
|
|
149
|
+
"quoting",
|
|
150
|
+
"tables",
|
|
151
|
+
"config",
|
|
152
|
+
]
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
snapshots_allowed_config = AllowedConfig(
|
|
156
|
+
allowed_config_fields=set(
|
|
157
|
+
[
|
|
158
|
+
# snapshot specific
|
|
159
|
+
"database",
|
|
160
|
+
"schema",
|
|
161
|
+
"unique_key",
|
|
162
|
+
"strategy",
|
|
163
|
+
"updated_at",
|
|
164
|
+
"check_cols",
|
|
165
|
+
"snapshot_meta_column_names",
|
|
166
|
+
"hard_deletes",
|
|
167
|
+
"dbt_valid_to_current",
|
|
168
|
+
# general
|
|
169
|
+
"enabled",
|
|
170
|
+
"tags",
|
|
171
|
+
"alias",
|
|
172
|
+
"pre_hook",
|
|
173
|
+
"post_hook",
|
|
174
|
+
"persist_docs",
|
|
175
|
+
"grants",
|
|
176
|
+
"event_time",
|
|
177
|
+
# moved from property
|
|
178
|
+
"docs",
|
|
179
|
+
# not in docs for config
|
|
180
|
+
"meta",
|
|
181
|
+
]
|
|
182
|
+
),
|
|
183
|
+
allowed_properties=set(
|
|
184
|
+
[
|
|
185
|
+
"name",
|
|
186
|
+
"description",
|
|
187
|
+
"config",
|
|
188
|
+
"tests",
|
|
189
|
+
"columns",
|
|
190
|
+
]
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
seeds_allowed_config = AllowedConfig(
|
|
194
|
+
allowed_config_fields=set(
|
|
195
|
+
[
|
|
196
|
+
# seed specific
|
|
197
|
+
"quote_columns",
|
|
198
|
+
"column_types",
|
|
199
|
+
"delimiter",
|
|
200
|
+
# general
|
|
201
|
+
"enabled",
|
|
202
|
+
"tags",
|
|
203
|
+
"pre_hook",
|
|
204
|
+
"post_hook",
|
|
205
|
+
"database",
|
|
206
|
+
"schema",
|
|
207
|
+
"alias",
|
|
208
|
+
"persist_docs",
|
|
209
|
+
"full_refresh",
|
|
210
|
+
"meta",
|
|
211
|
+
"grants",
|
|
212
|
+
"event_time",
|
|
213
|
+
# moved from property
|
|
214
|
+
"docs",
|
|
215
|
+
]
|
|
216
|
+
),
|
|
217
|
+
allowed_properties=set(
|
|
218
|
+
[
|
|
219
|
+
"name",
|
|
220
|
+
"description",
|
|
221
|
+
"config",
|
|
222
|
+
"tests",
|
|
223
|
+
"columns",
|
|
224
|
+
]
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
tests_allowed_config = AllowedConfig(
|
|
229
|
+
allowed_config_fields=set(
|
|
230
|
+
[
|
|
231
|
+
# general
|
|
232
|
+
"enabled",
|
|
233
|
+
"tags",
|
|
234
|
+
"meta",
|
|
235
|
+
"database",
|
|
236
|
+
"schema",
|
|
237
|
+
"alias",
|
|
238
|
+
# data tests
|
|
239
|
+
"fail_calc",
|
|
240
|
+
"limit",
|
|
241
|
+
"severity",
|
|
242
|
+
"error_if",
|
|
243
|
+
"warn_if",
|
|
244
|
+
"store_failures",
|
|
245
|
+
"where",
|
|
246
|
+
]
|
|
247
|
+
),
|
|
248
|
+
allowed_properties=set(
|
|
249
|
+
[
|
|
250
|
+
"name",
|
|
251
|
+
"description",
|
|
252
|
+
"config",
|
|
253
|
+
]
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
fields_per_node_type = {
|
|
258
|
+
"models": models_allowed_config,
|
|
259
|
+
"seeds": seeds_allowed_config,
|
|
260
|
+
"tests": tests_allowed_config,
|
|
261
|
+
"sources": sources_allowed_config,
|
|
262
|
+
"snapshots": snapshots_allowed_config,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def print_matrix(): # noqa: PLR0912
|
|
267
|
+
from dbt_autofix.retrieve_schemas import yaml_specs_per_node_type
|
|
268
|
+
|
|
269
|
+
results = dict()
|
|
270
|
+
for node_type, fields_config in fields_per_node_type.items():
|
|
271
|
+
allowed_config_fields = fields_config.allowed_config_fields
|
|
272
|
+
for field in allowed_config_fields:
|
|
273
|
+
if field not in results:
|
|
274
|
+
results[field] = {f"{node_type}-bestguess": "config"}
|
|
275
|
+
else:
|
|
276
|
+
results[field].update({f"{node_type}-bestguess": "config"})
|
|
277
|
+
|
|
278
|
+
allowed_properties = fields_config.allowed_properties
|
|
279
|
+
for property in allowed_properties:
|
|
280
|
+
if property not in results:
|
|
281
|
+
results[property] = {f"{node_type}-bestguess": "property"}
|
|
282
|
+
else:
|
|
283
|
+
results[property].update({f"{node_type}-bestguess": "property"})
|
|
284
|
+
|
|
285
|
+
for node_type, fields_config in yaml_specs_per_node_type.items():
|
|
286
|
+
allowed_config_fields = fields_config.allowed_config_fields
|
|
287
|
+
for field in allowed_config_fields:
|
|
288
|
+
if field not in results:
|
|
289
|
+
results[field] = {f"{node_type}-fusion": "config"}
|
|
290
|
+
else:
|
|
291
|
+
results[field].update({f"{node_type}-fusion": "config"})
|
|
292
|
+
|
|
293
|
+
allowed_properties = fields_config.allowed_properties
|
|
294
|
+
for property in allowed_properties:
|
|
295
|
+
if property not in results:
|
|
296
|
+
results[property] = {f"{node_type}-fusion": "property"}
|
|
297
|
+
else:
|
|
298
|
+
results[property].update({f"{node_type}-fusion": "property"})
|
|
299
|
+
|
|
300
|
+
# Assuming your dictionary is named 'results'
|
|
301
|
+
with open("config_resources.csv", "w", newline="") as csvfile:
|
|
302
|
+
# Determine all possible resource types from the data
|
|
303
|
+
resource_types = sorted(
|
|
304
|
+
set(resource_type for config_values in results.values() for resource_type in config_values.keys())
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Create fieldnames with 'field_name' as the first column followed by all resource types
|
|
308
|
+
fieldnames = ["field_name", *resource_types]
|
|
309
|
+
|
|
310
|
+
# Create CSV writer
|
|
311
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
312
|
+
|
|
313
|
+
# Write the header
|
|
314
|
+
writer.writeheader()
|
|
315
|
+
|
|
316
|
+
# Sort field_names alphabetically
|
|
317
|
+
sorted_field_names = sorted(results.keys())
|
|
318
|
+
|
|
319
|
+
# Write each row
|
|
320
|
+
for field_name in sorted_field_names:
|
|
321
|
+
resources = results[field_name]
|
|
322
|
+
row = {"field_name": field_name}
|
|
323
|
+
# Add values for each resource type
|
|
324
|
+
for resource_type in resource_types:
|
|
325
|
+
row[resource_type] = resources.get(resource_type, "")
|
|
326
|
+
writer.writerow(row)
|
|
327
|
+
|
|
328
|
+
print("CSV file 'config_resources.csv' has been created successfully.")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich import print
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from typing_extensions import Annotated
|
|
7
|
+
|
|
8
|
+
from dbt_autofix.duplicate_keys import find_duplicate_keys, print_duplicate_keys
|
|
9
|
+
from dbt_autofix.fields_properties_configs import print_matrix
|
|
10
|
+
from dbt_autofix.refactor import apply_changesets, changeset_all_sql_yml_files
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
error_console = Console(stderr=True)
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(
|
|
16
|
+
help="A tool to help clean up dbt projects",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
add_completion=False,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
current_dir = Path.cwd()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command(name="list-yaml-duplicates")
|
|
25
|
+
def identify_duplicate_keys(
|
|
26
|
+
path: Annotated[Path, typer.Option("--path", "-p", help="The path to the dbt project")] = current_dir,
|
|
27
|
+
):
|
|
28
|
+
print(f"[green]Identifying duplicates in {path}[/green]\n")
|
|
29
|
+
project_duplicates, package_duplicates = find_duplicate_keys(path)
|
|
30
|
+
print_duplicate_keys(project_duplicates, package_duplicates)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command(name="deprecations")
|
|
34
|
+
def refactor_yml(
|
|
35
|
+
path: Annotated[Path, typer.Option("--path", "-p", help="The path to the dbt project")] = current_dir,
|
|
36
|
+
dry_run: Annotated[bool, typer.Option("--dry-run", "-d", help="In dry run mode, do not apply changes")] = False,
|
|
37
|
+
json_output: Annotated[bool, typer.Option("--json", "-j", help="Output in JSON format")] = False,
|
|
38
|
+
):
|
|
39
|
+
changesets = changeset_all_sql_yml_files(path, dry_run)
|
|
40
|
+
yaml_results, sql_results = changesets
|
|
41
|
+
if dry_run:
|
|
42
|
+
if not json_output:
|
|
43
|
+
error_console.print("[red]-- Dry run mode, not applying changes --[/red]")
|
|
44
|
+
for changeset in yaml_results:
|
|
45
|
+
if changeset.refactored:
|
|
46
|
+
changeset.print_to_console(json_output)
|
|
47
|
+
for changeset in sql_results:
|
|
48
|
+
if changeset.refactored:
|
|
49
|
+
changeset.print_to_console(json_output)
|
|
50
|
+
else:
|
|
51
|
+
apply_changesets(yaml_results, sql_results, json_output)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command(hidden=True)
|
|
55
|
+
def print_fields_matrix():
|
|
56
|
+
print_matrix()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
app()
|