Repository: dbt-labs/dbt-core

Issue:
iterating over the results of a missing macro gives a bad error
## Issue
Iterating over the results of a missing macro results in an unhelpful error

### Issue description

Iterating over the result of a macro that's missing causes dbt to fail during parsing (eek) with a `TypeError` (double eek).

This is pretty common when users are using macros in an external package that return iterables (like `dbt_utils.group_by()`) and forget to run `dbt deps`.

The core problem is that our `ParserMacroCapture` override for `Undefined` during parsing has this:

```
def __call__(self, *args,**kwargs):
    return True
```

So any missing macros return `True` during parsing, and you can't iterate over that!


As a workaround, users can intuit that they need to run `dbt deps`, and then everything will work. But we should really make this error message less awful, and not require a deep understanding of jinja/dbt's internals to figure out what's happening.

### Results
What I got:
```
$ dbt compile
Running with dbt=0.13.0
Encountered an error:
'bool' object is not iterable
```

What I expected:
```
Running with dbt=0.13.0
Found 1 models, 0 tests, 1 archives, 0 analyses, 101 macros, 0 operations, 0 seed files, 1 sources

07:50:51 | Concurrency: 2 threads (target='default')
07:50:51 |
Encountered an error:
Runtime Error
  Compilation Error in model x (models/x.sql)
    'missing_macro' is undefined
```



### System information
dbt: Happens on 0.13.0, I assume most earlier versions as well.

OS/Python version agnostic.

### Steps to reproduce

```
$ cat models/x.sql
{% for x in missing_macro() %}
{% endfor %}
$ dbt compile
Running with dbt=0.13.0
Encountered an error:
'bool' object is not iterable
```




Discussion:

Resolving patch:
diff --git a/core/dbt/clients/jinja.py b/core/dbt/clients/jinja.py
--- a/core/dbt/clients/jinja.py
+++ b/core/dbt/clients/jinja.py
@@ -219,7 +219,7 @@ def __getattr__(self, name):
             return self
 
         def __call__(self, *args, **kwargs):
-            return True
+            return self
 
     return ParserMacroCapture
 


Test patch:
diff --git a/test/integration/006_simple_dependency_test/sad_iteration_models/iterate.sql b/test/integration/006_simple_dependency_test/sad_iteration_models/iterate.sql
new file mode 100644
--- /dev/null
+++ b/test/integration/006_simple_dependency_test/sad_iteration_models/iterate.sql
@@ -0,0 +1,2 @@
+{% for x in no_such_dependency.no_such_method() %}
+{% endfor %}
diff --git a/test/integration/006_simple_dependency_test/test_local_dependency.py b/test/integration/006_simple_dependency_test/test_local_dependency.py
--- a/test/integration/006_simple_dependency_test/test_local_dependency.py
+++ b/test/integration/006_simple_dependency_test/test_local_dependency.py
@@ -5,6 +5,7 @@
 import dbt.config
 import dbt.exceptions
 
+
 class TestSimpleDependency(DBTIntegrationTest):
 
     @property
@@ -45,6 +46,25 @@ def test_postgres_local_dependency(self):
         )
 
 
+class TestMissingDependency(DBTIntegrationTest):
+    @property
+    def schema(self):
+        return "local_dependency_006"
+
+    @property
+    def models(self):
+        return "test/integration/006_simple_dependency_test/sad_iteration_models"
+
+    @use_profile('postgres')
+    def test_postgres_missing_dependency(self):
+        # dbt should raise a dbt exception, not raise a parse-time TypeError.
+        with self.assertRaises(dbt.exceptions.Exception) as exc:
+            self.run_dbt(['compile'])
+        message = str(exc.exception)
+        self.assertIn('no_such_dependency', message)
+        self.assertIn('is undefined', message)
+
+
 class TestSimpleDependencyWithSchema(TestSimpleDependency):
     @property
     def project_config(self):