Repository: dbt-labs/dbt-core

Issue:
[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_


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.

Resolving patch:
diff --git a/core/dbt/parser/models.py b/core/dbt/parser/models.py
--- 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 2 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


Test patch:
diff --git a/test/unit/test_parser.py b/test/unit/test_parser.py
--- 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'])
-
-