Repository: dbt-labs/dbt-core
Pull request: Fix dbt.config.get default values  (python-model)
resolves #6309

<!---
  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.
-->

### Description

After this, will be possible to use default values for dbt.config.get

<!---
  Describe the Pull Request here. Add any references and info to help reviewers
  understand your changes. Include any tradeoffs you considered.
-->

### Checklist

- [x] I have read [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md) and understand what's expected of me
- [x] I have signed the [CLA](https://docs.getdbt.com/docs/contributor-license-agreements)
- [x] I have run this code in development and it appears to resolve the stated issue
- [x] This PR includes tests, or tests are not required/relevant for this PR
- [x] I have [opened an issue to add/update docs](https://github.com/dbt-labs/docs.getdbt.com/issues/new/choose), or docs changes are not required/relevant for this PR
- [x] I have run `changie new` to [create a changelog entry](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-a-changelog-entry)


Problem statement:
[CT-1545] [Bug] dbt.config.get returns Undefined instead of the default value in Python model
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

`dbt.config.get("missing_key", "default_value")` default value dosen't work.  This behavior creates an Undefined variable inside the context after the compilation like this.

```python
config_dict = {'KEY_AVAILABLE': 'everthing_is_ok',  'MISSING_KEY': Undefined}
```





### Expected Behavior

```python
config_dict = {'KEY_AVAILABLE': 'everthing_is_ok',  'MISSING_KEY': "default_value"}
```


### Steps To Reproduce

Define a model like:

```python
def model(dbt, session):

    CONFIG_KEY = dbt.config.get("KEY_EXIST", "DEFAULT_VALUE")
    MISSING_KEY = dbt.config.get("MISSING_KEY", "DEFAULT_VALUE")

    df = dbt.ref("orders")

    return df
```
With configs like:

```yaml
version: 2
models:
  - name: something
    config:
      CONFIG_KEY:  everthing_is_ok
```
Run dbt compile. 

The compiled output will create a `config_dict` like this

```python
config_dict = {'CONFIG_KEY': 'everthing_is_ok',  'MISSING_KEY: Undefined}
```

In that case, Undefined is a undeclared variable in the context. Because that, this will create an exception. 

### Relevant log output

_No response_

### Environment

```markdown
- OS: Linux/Ubuntu
- Python:3.9.12
- dbt-core==1.3.0 
- dbt-databricks==1.3.0
```


### Which database adapter are you using with dbt?

spark

### Additional Context

_No response_

Hints from issue discussion:
Related to https://github.com/dbt-labs/dbt-core/issues/5846. I can work in a PR to fix that.
@devmessias Good catch, thanks for opening!

This feels like a relatively simple & self-contained fix, so we'd welcome a contribution for it. Let us know if it turns out to be trickier than expected.
> @devmessias Good catch, thanks for opening!
> 
> This feels like a relatively simple & self-contained fix, so we'd welcome a contribution for it. Let us know if it turns out to be trickier than expected.

You're right! It was a quite simple fix

Commit message: docs: changie yaml

Patch to review:
diff --git a/.changes/unreleased/Fixes-20221124-163419.yaml b/.changes/unreleased/Fixes-20221124-163419.yaml
new file mode 100644
index 00000000000..010a073269a
--- /dev/null
+++ b/.changes/unreleased/Fixes-20221124-163419.yaml
@@ -0,0 +1,7 @@
+kind: Fixes
+body: After this, will be possible to use default values for dbt.config.get
+time: 2022-11-24T16:34:19.039512764-03:00
+custom:
+  Author: devmessias
+  Issue: "6309"
+  PR: "6317"
diff --git a/core/dbt/include/global_project/macros/python_model/python.sql b/core/dbt/include/global_project/macros/python_model/python.sql
index 2155662987e..c56ff7f31c8 100644
--- a/core/dbt/include/global_project/macros/python_model/python.sql
+++ b/core/dbt/include/global_project/macros/python_model/python.sql
@@ -30,12 +30,13 @@ def source(*args, dbt_load_df_function):
 
 {% macro build_config_dict(model) %}
     {%- set config_dict = {} -%}
-    {%- for key in model.config.config_keys_used -%}
+    {% set config_dbt_used = zip(model.config.config_keys_used, model.config.config_keys_defaults) | list %}
+    {%- for key, default in config_dbt_used -%}
         {# weird type testing with enum, would be much easier to write this logic in Python! #}
         {%- if key == 'language' -%}
           {%- set value = 'python' -%}
         {%- endif -%}
-        {%- set value = model.config[key] -%}
+        {%- set value = model.config.get(key, default) -%}
         {%- do config_dict.update({key: value}) -%}
     {%- endfor -%}
 config_dict = {{ config_dict }}
diff --git a/core/dbt/parser/models.py b/core/dbt/parser/models.py
index aaf6a0d016e..a561a755c1a 100644
--- a/core/dbt/parser/models.py
+++ b/core/dbt/parser/models.py
@@ -207,15 +207,33 @@ def parse_python_model(self, node, config, context):
         dbtParser = PythonParseVisitor(node)
         dbtParser.visit(tree)
         config_keys_used = []
+        config_keys_defaults = []
         for (func, args, kwargs) in dbtParser.dbt_function_calls:
             if func == "get":
-                config_keys_used.append(args[0])
+                num_args = len(args)
+                if num_args == 0:
+                    raise ParsingException(
+                        "dbt.config.get() requires at least one argument",
+                        node=node,
+                    )
+                if num_args > 2:
+                    raise ParsingException(
+                        f"dbt.config.get() takes at most  arguments ({num_args} given)",
+                        node=node,
+                    )
+                key = args[0]
+                default_value = args[1] if num_args == 2 else None
+                config_keys_used.append(key)
+                config_keys_defaults.append(default_value)
                 continue
 
             context[func](*args, **kwargs)
         if config_keys_used:
             # this is being used in macro build_config_dict
-            context["config"](config_keys_used=config_keys_used)
+            context["config"](
+                config_keys_used=config_keys_used,
+                config_keys_defaults=config_keys_defaults,
+            )
 
     def render_update(self, node: ParsedModelNode, config: ContextConfig) -> None:
         self.manifest._parsing_info.static_analysis_path_count += 1
diff --git a/test/unit/test_parser.py b/test/unit/test_parser.py
index 1ae9e3917ed..ac539f69fc3 100644
--- a/test/unit/test_parser.py
+++ b/test/unit/test_parser.py
@@ -608,6 +608,25 @@ def model(dbt, session):
         node = list(self.parser.manifest.nodes.values())[0]
         self.assertEqual(node.config.to_dict()["config_keys_used"], ["param_1", "param_2"])
 
+    def test_python_model_config_default(self):
+            py_code = """
+def model(dbt, session):
+    dbt.config.get("param_None", None)
+    dbt.config.get("param_Str", "default")
+    dbt.config.get("param_List", [1, 2])
+    return df
+            """
+            block = self.file_block_for(py_code, 'nested/py_model.py')
+            self.parser.manifest.files[block.file.file_id] = block.file
+
+            self.parser.parse_file(block)
+            node = list(self.parser.manifest.nodes.values())[0]
+            default_values = node.config.to_dict()["config_keys_defaults"]
+            self.assertIsNone(default_values[0])
+            self.assertEqual(default_values[1], "default")
+            self.assertEqual(default_values[2], [1, 2])
+
+
     def test_wrong_python_model_def_miss_session(self):
         py_code = """
 def model(dbt):
@@ -1319,5 +1338,3 @@ def test_basic(self):
         file_id = 'snowplow://' +  normalize('analyses/nested/analysis_1.sql')
         self.assertIn(file_id, self.parser.manifest.files)
         self.assertEqual(self.parser.manifest.files[file_id].nodes, ['analysis.snowplow.analysis_1'])
-
-


Code review comments:
diff_hunk: @@ -207,15 +207,33 @@ def parse_python_model(self, node, config, context):
         dbtParser = PythonParseVisitor(node)
         dbtParser.visit(tree)
         config_keys_used = []
+        config_keys_defaults = []
         for (func, args, kwargs) in dbtParser.dbt_function_calls:
             if func == "get":
-                config_keys_used.append(args[0])
+                num_args = len(args)
+                if num_args == 0:
+                    raise ParsingException(
+                        "dbt.config.get() requires at least one argument",
+                        node=node,
+                    )
+                if num_args > 2:
+                    raise ParsingException(
+                        f"dbt.config.get() takes at most  arguments ({num_args} given)",
path: core/dbt/parser/models.py
text: @user1:
```suggestion
                        f"dbt.config.get() takes at most 2 arguments ({num_args} given)",
```

@author:
thanks for this catch @user1