Repository: dbt-labs/dbt-core

Issue:
[CT-1326] [Bug] Python model with no arguments in the function raises index our of range exception
### 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

Creating a Python model with a `model` function without any arguments ends in an `list index out of range` exception rather than a helpful message.

```
def model():
  ...
```

### Expected Behavior

It should give a helpful message about how Python model definitions should look.

### Steps To Reproduce

1. Write a Python model with no arguments in the function
```
def model():
  import pandas as pd
  return pd.DataFrame.from_dict({'a': [1,2,3], 'b': ['some', None, None]})
```
2. `dbt run`


### Relevant log output

```shell
def model(dbt):
    import pandas as pd


❯ dbt run
21:26:46  [WARNING]: Deprecated functionality
The `source-paths` config has been renamed to `model-paths`. Please update your
`dbt_project.yml` configuration to reflect this change.
21:26:46  [WARNING]: Deprecated functionality
The `data-paths` config has been renamed to `seed-paths`. Please update your
`dbt_project.yml` configuration to reflect this change.
21:26:46  Running with dbt=1.4.0-a1
21:26:46  Encountered an error:
Parsing Error in model model_c (models/staging/model_c.py)
  model function should have two args, `dbt` and a session to current warehouse


*****

def model():
    import pandas as pd

❯ dbt run
21:29:26  [WARNING]: Deprecated functionality
The `source-paths` config has been renamed to `model-paths`. Please update your
`dbt_project.yml` configuration to reflect this change.
21:29:26  [WARNING]: Deprecated functionality
The `data-paths` config has been renamed to `seed-paths`. Please update your
`dbt_project.yml` configuration to reflect this change.
21:29:26  Running with dbt=1.4.0-a1
21:29:27  Encountered an error:
list index out of range
21:29:27  Traceback (most recent call last):
  File ".../Projects/fal-ai/dbt-core/core/dbt/main.py", line 135, in main
    results, succeeded = handle_and_check(args)
  File ".../Projects/fal-ai/dbt-core/core/dbt/main.py", line 198, in handle_and_check
    task, res = run_from_args(parsed)
  File ".../Projects/fal-ai/dbt-core/core/dbt/main.py", line 245, in run_from_args
    results = task.run()
  File ".../Projects/fal-ai/dbt-core/core/dbt/task/runnable.py", line 453, in run
    self._runtime_initialize()
  File ".../Projects/fal-ai/dbt-core/core/dbt/task/runnable.py", line 161, in _runtime_initialize
    super()._runtime_initialize()
  File ".../Projects/fal-ai/dbt-core/core/dbt/task/runnable.py", line 94, in _runtime_initialize
    self.load_manifest()
  File ".../Projects/fal-ai/dbt-core/core/dbt/task/runnable.py", line 81, in load_manifest
    self.manifest = ManifestLoader.get_full_manifest(self.config)
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/manifest.py", line 219, in get_full_manifest
    manifest = loader.load()
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/manifest.py", line 349, in load
    self.parse_project(
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/manifest.py", line 477, in parse_project
    parser.parse_file(block)
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/base.py", line 414, in parse_file
    self.parse_node(file_block)
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/base.py", line 388, in parse_node
    self.render_update(node, config)
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/models.py", line 227, in render_update
    self.parse_python_model(node, config, context)
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/models.py", line 204, in parse_python_model
    dbtValidator.visit(tree)
  File ".../.pyenv/versions/3.8.13/lib/python3.8/ast.py", line 371, in visit
    return visitor(node)
  File ".../.pyenv/versions/3.8.13/lib/python3.8/ast.py", line 379, in generic_visit
    self.visit(item)
  File ".../.pyenv/versions/3.8.13/lib/python3.8/ast.py", line 371, in visit
    return visitor(node)
  File ".../Projects/fal-ai/dbt-core/core/dbt/parser/models.py", line 52, in visit_FunctionDef
    if not node.args.args[0].arg == "dbt":
IndexError: list index out of range
```


### Environment

```markdown
- OS: macOS 12.6
- Python: Python 3.8.13
- dbt:
  Core:
    - installed: 1.4.0-a1
    - latest:    1.2.2    - Ahead of latest version!
```


### Which database adapter are you using with dbt?

postgres

### Additional Context

_No response_


Discussion:

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
@@ -45,7 +45,7 @@ def __init__(self):
     def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
         if node.name == "model":
             self.num_model_def += 1
-            if not node.args.args[0].arg == "dbt":
+            if node.args.args and not node.args.args[0].arg == "dbt":
                 self.dbt_errors.append("'dbt' not provided for model as the first argument")
             if len(node.args.args) != 2:
                 self.dbt_errors.append(


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
@@ -623,7 +623,17 @@ def model(dbt):
         self.parser.manifest.files[block.file.file_id] = block.file
         with self.assertRaises(ParsingException):
             self.parser.parse_file(block)
-    
+
+    def test_wrong_python_model_def_miss_session(self):
+        py_code = """
+def model():
+    return df
+        """
+        block = self.file_block_for(py_code, 'nested/py_model.py')
+        self.parser.manifest.files[block.file.file_id] = block.file
+        with self.assertRaises(ParsingException):
+            self.parser.parse_file(block)
+
     def test_wrong_python_model_def_wrong_arg(self):
         """ First argument for python model should be dbt
         """