Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/more helpful invalid refs (#1080) #1085

Merged
merged 3 commits into from
Oct 23, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions dbt/clients/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ def parse(self, parser):
return node


def _is_dunder_name(name):
return name.startswith('__') and name.endswith('__')


def create_macro_capture_env(node):

class ParserMacroCapture(jinja2.Undefined):
Expand All @@ -194,17 +198,37 @@ class ParserMacroCapture(jinja2.Undefined):
def __init__(self, hint=None, obj=None, name=None,
exc=None):
super(jinja2.Undefined, self).__init__()

self.node = node
self.name = name
self.package_name = node.get('package_name')

def __getattr__(self, name):

# jinja uses these for safety, so we have to override them.
# see https://github.com/pallets/jinja/blob/master/jinja2/sandbox.py#L332-L339 # noqa
if name in ['unsafe_callable', 'alters_data']:
return False
self.unsafe_callable = False
self.alters_data = False

def __deepcopy__(self, memo):
path = os.path.join(self.node.get('root_path'),
self.node.get('original_file_path'))

logger.debug(
'A ParserMacroCapture has been deecopy()d, invalid reference '
'to "{}" in node {}.{} (source path: {})'
.format(self.name, self.node.get('package_name'),
self.node.get('name'),
path))

dbt.exceptions.raise_compiler_error(
'dbt has detected at least one invalid reference in {}.{}. '
'Check logs for more information'
.format(self.node.get('package_name'), self.node.get('name'))
)

def __getattr__(self, name):
if name == 'name' or _is_dunder_name(name):
raise AttributeError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we raise this error for __*__?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because copy.deepcopy does a series of successive hasattr(x, '__*__') checks to decide how to deepcopy things, and hasattr(obj, name) is basically implemented as:

try:
  getattr(obj, name)
except AttributeError:
  return False
else:
  return True

Since __getattr__ is only accessed when an attribute is not already defined, we want to signal that we don't implement those methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roger!

"'{}' object has no attribute '{}'"
.format(type(self).__name__, name)
)

self.package_name = self.name
self.name = name
Expand Down