Repository: dbt-labs/dbt-core

Issue:
ability to set target-path to a dynamic value
## Feature

### Feature description
It would be nice to set `target-path` to a dynamic value. For eg: `target_<current_time>`

Currently target-path can be assigned to the value of a variable that is passed as a command line argument. However, since command line args are parsed as simple strings, the value of such a variable cannot be dynamic.

### Who will this benefit?
The target directory contains run stats in its `run_results.json` file. If one were to automate the process of parsing this file after every run for alarming/monitoring, then it is important to not have one run's stats overwrite another before it is parsed. This can be prevented by "versioning" each run by the target directory name which would be a function of current timestamp. 


Discussion:
thanks for the report @Vamanan! Let's solve this by adding the `modules` dict to the minimal compilation context. That would let you do something like:
```
target-path: "target_{{ modules.datetime.datetime.now().strftime(...) }}"
```

We could conceivably also add static context variables like `run_started_at` and `invocation_id`, which are reasonable and i think doable here.

Prioritized for the Wilt Chamberlain release, pending any unforeseen issues with this approach.

Resolving patch:
diff --git a/core/dbt/config/renderer.py b/core/dbt/config/renderer.py
--- a/core/dbt/config/renderer.py
+++ b/core/dbt/config/renderer.py
@@ -1,7 +1,6 @@
 from dbt import compat
 from dbt.clients.jinja import get_rendered
-from dbt.context.common import env_var
-from dbt.context.common import Var
+from dbt.context.common import generate_config_context
 from dbt.exceptions import DbtProfileError
 from dbt.exceptions import DbtProjectError
 from dbt.exceptions import RecursionException
@@ -13,8 +12,7 @@ class ConfigRenderer(object):
     variables and a render type.
     """
     def __init__(self, cli_vars):
-        self.context = {'env_var': env_var}
-        self.context['var'] = Var(None, self.context, cli_vars)
+        self.context = generate_config_context(cli_vars)
 
     @staticmethod
     def _is_hook_or_model_vars_path(keypath):
diff --git a/core/dbt/context/common.py b/core/dbt/context/common.py
--- a/core/dbt/context/common.py
+++ b/core/dbt/context/common.py
@@ -329,6 +329,22 @@ def get_datetime_module_context():
     }
 
 
+def get_context_modules():
+    return {
+        'pytz': get_pytz_module_context(),
+        'datetime': get_datetime_module_context(),
+    }
+
+
+def generate_config_context(cli_vars):
+    context = {
+        'env_var': env_var,
+        'modules': get_context_modules(),
+    }
+    context['var'] = Var(None, context, cli_vars)
+    return _add_tracking(context)
+
+
 def generate_base(model, model_dict, config, manifest, source_config,
                   provider, adapter=None):
     """Generate the common aspects of the config dict."""
@@ -370,10 +386,7 @@ def generate_base(model, model_dict, config, manifest, source_config,
         "graph": manifest.to_flat_graph(),
         "log": log,
         "model": model_dict,
-        "modules": {
-            "pytz": get_pytz_module_context(),
-            "datetime": get_datetime_module_context(),
-        },
+        "modules": get_context_modules(),
         "post_hooks": post_hooks,
         "pre_hooks": pre_hooks,
         "ref": provider.ref(db_wrapper, model, config, manifest),


Test patch:
diff --git a/test/integration/039_config_test/test_configs.py b/test/integration/039_config_test/test_configs.py
--- a/test/integration/039_config_test/test_configs.py
+++ b/test/integration/039_config_test/test_configs.py
@@ -1,3 +1,6 @@
+from datetime import datetime
+import os
+import shutil
 
 from test.integration.base import DBTIntegrationTest, use_profile
 
@@ -39,3 +42,44 @@ def test_postgres_config_layering(self):
         # make sure we overwrote the materialization properly
         models = self.get_models_in_schema()
         self.assertEqual(models['model'], 'table')
+
+
+class TestTargetConfigs(DBTIntegrationTest):
+    @property
+    def schema(self):
+        return "config_039"
+
+    def unique_schema(self):
+        return super(TestTargetConfigs, self).unique_schema().upper()
+
+    @property
+    def models(self):
+        return "test/integration/039_config_test/models"
+
+    def setUp(self):
+        super(TestTargetConfigs, self).setUp()
+        self.init_targets = [d for d in os.listdir('.') if os.path.isdir(d) and d.startswith('target_')]
+
+    def tearDown(self):
+        super(TestTargetConfigs, self).tearDown()
+        for d in self.new_dirs():
+            shutil.rmtree(d)
+
+    def new_dirs(self):
+        for d in os.listdir('.'):
+            if os.path.isdir(d) and d not in self.init_targets and d.startswith('target_'):
+                yield d
+
+    @property
+    def project_config(self):
+        return {
+            'data-paths': ['test/integration/039_config_test/data'],
+            'target-path': "target_{{ modules.datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%S') }}"
+        }
+
+    @use_profile('postgres')
+    def test_alternative_target_paths(self):
+        self.run_dbt(['seed'])
+        dirs = list(self.new_dirs())
+        self.assertEqual(len(dirs), 1)
+        self.assertTrue(os.path.exists(os.path.join(dirs[0], 'manifest.json')))