Skip to content

Commit

Permalink
[CT-1591] Don't parse empty Python files
Browse files Browse the repository at this point in the history
  • Loading branch information
aranke committed Dec 13, 2022
1 parent 0570d39 commit 9f88b89
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 30 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Fixes-20221213-113915.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Fixes
body: '[CT-1591] Don''t parse empty Python files'
time: 2022-12-13T11:39:15.818464-08:00
custom:
Author: aranke
Issue: "6345"
64 changes: 34 additions & 30 deletions core/dbt/parser/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from dbt.dataclass_schema import ValidationError
from dbt.exceptions import ParsingException, validator_error_message, UndefinedMacroException


dbt_function_key_words = set(["ref", "source", "config", "get"])
dbt_function_full_names = set(["dbt.ref", "dbt.source", "dbt.config", "dbt.config.get"])

Expand Down Expand Up @@ -196,42 +195,47 @@ def get_compiled_path(cls, block: FileBlock):
return block.path.relative_path

def parse_python_model(self, node, config, context):
config_keys_used = []
config_keys_defaults = []

try:
tree = ast.parse(node.raw_code, filename=node.original_file_path)
except SyntaxError as exc:
msg = validator_error_message(exc)
raise ParsingException(f"{msg}\n{exc.text}", node=node) from exc

# We are doing a validator and a parser because visit_FunctionDef in parser
# would actually make the parser not doing the visit_Calls any more
dbtValidator = PythonValidationVisitor()
dbtValidator.visit(tree)
dbtValidator.check_error(node)
# Only parse if AST tree has instructions in body
if tree.body:
# We are doing a validator and a parser because visit_FunctionDef in parser
# would actually make the parser not doing the visit_Calls any more
dbt_validator = PythonValidationVisitor()
dbt_validator.visit(tree)
dbt_validator.check_error(node)

dbt_parser = PythonParseVisitor(node)
dbt_parser.visit(tree)

for (func, args, kwargs) in dbt_parser.dbt_function_calls:
if func == "get":
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)

dbtParser = PythonParseVisitor(node)
dbtParser.visit(tree)
config_keys_used = []
config_keys_defaults = []
for (func, args, kwargs) in dbtParser.dbt_function_calls:
if func == "get":
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"](
Expand Down
8 changes: 8 additions & 0 deletions test/unit/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,8 @@ def model1(dbt, session):
return dbt.ref("some_model")
"""

python_model_empty_file = """ """

python_model_multiple_returns = """
def model(dbt, session):
dbt.config(materialized='table')
Expand Down Expand Up @@ -749,6 +751,11 @@ def test_python_model_incorrect_function_name(self):
with self.assertRaises(ParsingException):
self.parser.parse_file(block)

def test_python_model_empty_file(self):
block = self.file_block_for(python_model_empty_file, "nested/py_model.py")
self.parser.manifest.files[block.file.file_id] = block.file
self.assertIsNone(self.parser.parse_file(block))

def test_python_model_multiple_returns(self):
block = self.file_block_for(python_model_multiple_returns, 'nested/py_model.py')
self.parser.manifest.files[block.file.file_id] = block.file
Expand Down Expand Up @@ -786,6 +793,7 @@ def test_python_model_custom_materialization(self):
node = list(self.parser.manifest.nodes.values())[0]
self.assertEqual(node.get_materialization(), "view")


class StaticModelParserTest(BaseParserTest):
def setUp(self):
super().setUp()
Expand Down

0 comments on commit 9f88b89

Please sign in to comment.