Repository: dbt-labs/dbt-core

Issue:
Re-add support for `dbt init <project_name>`
Folks are used to being able to pass project name as a first positional argument to the `dbt init` subcommand. The new approach (new for v1!) is much more fun + interactive, but for muscle memory's sake, let's see if we can't find a way to keep supporting `dbt init my_new_project`, too.

If passed, I think this argument should be used as the implicit answer for "What is the desired project name?" We should set `project_name` accordingly [here](https://github.com/dbt-labs/dbt-core/blob/178f74b753aab0c40d03799b3287a13f35740248/core/dbt/task/init.py#L301), and skip the prompt.

Related ask that we should foresee:
- Ability to skip profile setup entirely, and just create scaffolding for a new project (`--skip-profile-setup`?). This could be used by tools which manage connection setup themselves, such as dbt Cloud
- Ability to create project scaffolding in the current working directory, rather than always creating a new subfolder (#4141)


Discussion:
Hello @jtcohen6 :wave:,

Indeed, it could be great to keep supporting `dbt init my_new_project`  for the v1 :slightly_smiling_face:.

> Ability to skip profile setup entirely, and just create scaffolding for a new project (--skip-profile-setup?). This could be used by tools which manage connection setup themselves, such as dbt Cloud

I agree too, this feature would be use full :slightly_smiling_face:.


Happy to can work on it if it's ok for you? :slightly_smiling_face: 
Thanks :pray: 
By all means!
Hi @kadero, have you worked on this one already? If not, I am happy to raise a PR for it.
> Hi @kadero, have you worked on this one already? If not, I am happy to raise a PR for it.

Hello @b-per, 
Thanks for your message :slightly_smiling_face: .

Yes I began to work on it. I didn't find the time to finish the job (fix integration tests) and open a PR. I'm going to open a PR ASAP.

Thanks :pray: 


Resolving patch:
diff --git a/core/dbt/main.py b/core/dbt/main.py
--- a/core/dbt/main.py
+++ b/core/dbt/main.py
@@ -340,6 +340,22 @@ def _build_init_subparser(subparsers, base_subparser):
         Initialize a new DBT project.
         '''
     )
+    sub.add_argument(
+        'project_name',
+        nargs='?',
+        help='''
+        Name of the new DBT project.
+        '''
+    )
+    sub.add_argument(
+        '-s',
+        '--skip-profile-setup',
+        dest='skip_profile_setup',
+        action='store_true',
+        help='''
+        Skip interative profile setup.
+        '''
+    )
     sub.set_defaults(cls=init_task.InitTask, which='init', rpc_method=None)
     return sub
 
diff --git a/core/dbt/task/init.py b/core/dbt/task/init.py
--- a/core/dbt/task/init.py
+++ b/core/dbt/task/init.py
@@ -302,26 +302,33 @@ def run(self):
             self.create_profile_from_target(
                 adapter, profile_name=profile_name
             )
-        else:
-            # When dbt init is run outside of an existing project,
-            # create a new project and set up the user's profile.
+            return
+
+        # When dbt init is run outside of an existing project,
+        # create a new project and set up the user's profile.
+        project_name = self.args.project_name
+        if project_name is None:
+            # If project name is not provided,
+            # ask the user which project name they'd like to use.
             project_name = click.prompt("What is the desired project name?")
-            project_path = Path(project_name)
-            if project_path.exists():
-                fire_event(ProjectNameAlreadyExists(name=project_name))
-                return
-
-            self.copy_starter_repo(project_name)
-            os.chdir(project_name)
-            with open("dbt_project.yml", "r+") as f:
-                content = f"{f.read()}".format(
-                    project_name=project_name,
-                    profile_name=project_name
-                )
-                f.seek(0)
-                f.write(content)
-                f.truncate()
+        project_path = Path(project_name)
+        if project_path.exists():
+            fire_event(ProjectNameAlreadyExists(name=project_name))
+            return
+
+        self.copy_starter_repo(project_name)
+        os.chdir(project_name)
+        with open("dbt_project.yml", "r+") as f:
+            content = f"{f.read()}".format(
+                project_name=project_name,
+                profile_name=project_name
+            )
+            f.seek(0)
+            f.write(content)
+            f.truncate()
 
+        # Ask for adapter only if skip_profile_setup flag is not provided.
+        if not self.args.skip_profile_setup:
             if not self.check_if_can_write_profile(profile_name=project_name):
                 return
             adapter = self.ask_for_adapter_choice()


Test patch:
diff --git a/test/integration/040_init_test/test_init.py b/test/integration/040_init_test/test_init.py
--- a/test/integration/040_init_test/test_init.py
+++ b/test/integration/040_init_test/test_init.py
@@ -404,6 +404,260 @@ def test_postgres_init_task_outside_of_project(self, mock_prompt, mock_confirm):
   - "dbt_packages"
 
 
+# Configuring models
+# Full documentation: https://docs.getdbt.com/docs/configuring-models
+
+# In this example config, we tell dbt to build all models in the example/ directory
+# as tables. These settings can be overridden in the individual model files
+# using the `{{{{ config(...) }}}}` macro.
+models:
+  {project_name}:
+    # Config indicated by + and applies to all files under models/example/
+    example:
+      +materialized: view
+"""
+
+    @use_profile('postgres')
+    @mock.patch('click.confirm')
+    @mock.patch('click.prompt')
+    def test_postgres_init_with_provided_project_name(self, mock_prompt, mock_confirm):
+        manager = Mock()
+        manager.attach_mock(mock_prompt, 'prompt')
+        manager.attach_mock(mock_confirm, 'confirm')
+
+        # Start by removing the dbt_project.yml so that we're not in an existing project
+        os.remove('dbt_project.yml')
+
+        manager.prompt.side_effect = [
+            1,
+            'localhost',
+            5432,
+            'test_username',
+            'test_password',
+            'test_db',
+            'test_schema',
+            4,
+        ]
+
+        # Provide project name through the init command.
+        project_name = self.get_project_name()
+        self.run_dbt(['init', project_name])
+        manager.assert_has_calls([
+            call.prompt("Which database would you like to use?\n[1] postgres\n\n(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)\n\nEnter a number", type=click.INT),
+            call.prompt('host (hostname for the instance)', default=None, hide_input=False, type=None),
+            call.prompt('port', default=5432, hide_input=False, type=click.INT),
+            call.prompt('user (dev username)', default=None, hide_input=False, type=None),
+            call.prompt('pass (dev password)', default=None, hide_input=True, type=None),
+            call.prompt('dbname (default database that dbt will build objects in)', default=None, hide_input=False, type=None),
+            call.prompt('schema (default schema that dbt will build objects in)', default=None, hide_input=False, type=None),
+            call.prompt('threads (1 or more)', default=1, hide_input=False, type=click.INT),
+        ])
+
+        with open(os.path.join(self.test_root_dir, 'profiles.yml'), 'r') as f:
+            assert f.read() == f"""config:
+  send_anonymous_usage_stats: false
+{project_name}:
+  outputs:
+    dev:
+      dbname: test_db
+      host: localhost
+      pass: test_password
+      port: 5432
+      schema: test_schema
+      threads: 4
+      type: postgres
+      user: test_username
+  target: dev
+test:
+  outputs:
+    default2:
+      dbname: dbt
+      host: localhost
+      pass: password
+      port: 5432
+      schema: {self.unique_schema()}
+      threads: 4
+      type: postgres
+      user: root
+    noaccess:
+      dbname: dbt
+      host: localhost
+      pass: password
+      port: 5432
+      schema: {self.unique_schema()}
+      threads: 4
+      type: postgres
+      user: noaccess
+  target: default2
+"""
+
+        with open(os.path.join(self.test_root_dir, project_name, 'dbt_project.yml'), 'r') as f:
+            assert f.read() == f"""
+# Name your project! Project names should contain only lowercase characters
+# and underscores. A good package name should reflect your organization's
+# name or the intended use of these models
+name: '{project_name}'
+version: '1.0.0'
+config-version: 2
+
+# This setting configures which "profile" dbt uses for this project.
+profile: '{project_name}'
+
+# These configurations specify where dbt should look for different types of files.
+# The `model-paths` config, for example, states that models in this project can be
+# found in the "models/" directory. You probably won't need to change these!
+model-paths: ["models"]
+analysis-paths: ["analyses"]
+test-paths: ["tests"]
+seed-paths: ["seeds"]
+macro-paths: ["macros"]
+snapshot-paths: ["snapshots"]
+
+target-path: "target"  # directory which will store compiled SQL files
+clean-targets:         # directories to be removed by `dbt clean`
+  - "target"
+  - "dbt_packages"
+
+
+# Configuring models
+# Full documentation: https://docs.getdbt.com/docs/configuring-models
+
+# In this example config, we tell dbt to build all models in the example/ directory
+# as tables. These settings can be overridden in the individual model files
+# using the `{{{{ config(...) }}}}` macro.
+models:
+  {project_name}:
+    # Config indicated by + and applies to all files under models/example/
+    example:
+      +materialized: view
+"""
+
+    @use_profile('postgres')
+    @mock.patch('click.confirm')
+    @mock.patch('click.prompt')
+    def test_postgres_init_skip_profile_setup(self, mock_prompt, mock_confirm):
+        manager = Mock()
+        manager.attach_mock(mock_prompt, 'prompt')
+        manager.attach_mock(mock_confirm, 'confirm')
+
+        # Start by removing the dbt_project.yml so that we're not in an existing project
+        os.remove('dbt_project.yml')
+
+        project_name = self.get_project_name()
+        manager.prompt.side_effect = [
+            project_name,
+            1,
+            'localhost',
+            5432,
+            'test_username',
+            'test_password',
+            'test_db',
+            'test_schema',
+            4,
+        ]
+
+        # provide project name through the ini command
+        self.run_dbt(['init', '-s'])
+        manager.assert_has_calls([
+          call.prompt('What is the desired project name?')
+        ])
+
+        with open(os.path.join(self.test_root_dir, project_name, 'dbt_project.yml'), 'r') as f:
+            assert f.read() == f"""
+# Name your project! Project names should contain only lowercase characters
+# and underscores. A good package name should reflect your organization's
+# name or the intended use of these models
+name: '{project_name}'
+version: '1.0.0'
+config-version: 2
+
+# This setting configures which "profile" dbt uses for this project.
+profile: '{project_name}'
+
+# These configurations specify where dbt should look for different types of files.
+# The `model-paths` config, for example, states that models in this project can be
+# found in the "models/" directory. You probably won't need to change these!
+model-paths: ["models"]
+analysis-paths: ["analyses"]
+test-paths: ["tests"]
+seed-paths: ["seeds"]
+macro-paths: ["macros"]
+snapshot-paths: ["snapshots"]
+
+target-path: "target"  # directory which will store compiled SQL files
+clean-targets:         # directories to be removed by `dbt clean`
+  - "target"
+  - "dbt_packages"
+
+
+# Configuring models
+# Full documentation: https://docs.getdbt.com/docs/configuring-models
+
+# In this example config, we tell dbt to build all models in the example/ directory
+# as tables. These settings can be overridden in the individual model files
+# using the `{{{{ config(...) }}}}` macro.
+models:
+  {project_name}:
+    # Config indicated by + and applies to all files under models/example/
+    example:
+      +materialized: view
+"""
+
+    @use_profile('postgres')
+    @mock.patch('click.confirm')
+    @mock.patch('click.prompt')
+    def test_postgres_init_provided_project_name_and_skip_profile_setup(self, mock_prompt, mock_confirm):
+        manager = Mock()
+        manager.attach_mock(mock_prompt, 'prompt')
+        manager.attach_mock(mock_confirm, 'confirm')
+
+        # Start by removing the dbt_project.yml so that we're not in an existing project
+        os.remove('dbt_project.yml')
+
+        manager.prompt.side_effect = [
+            1,
+            'localhost',
+            5432,
+            'test_username',
+            'test_password',
+            'test_db',
+            'test_schema',
+            4,
+        ]
+
+        # provide project name through the ini command
+        project_name = self.get_project_name()
+        self.run_dbt(['init', project_name, '-s'])
+        manager.assert_not_called()
+
+        with open(os.path.join(self.test_root_dir, project_name, 'dbt_project.yml'), 'r') as f:
+            assert f.read() == f"""
+# Name your project! Project names should contain only lowercase characters
+# and underscores. A good package name should reflect your organization's
+# name or the intended use of these models
+name: '{project_name}'
+version: '1.0.0'
+config-version: 2
+
+# This setting configures which "profile" dbt uses for this project.
+profile: '{project_name}'
+
+# These configurations specify where dbt should look for different types of files.
+# The `model-paths` config, for example, states that models in this project can be
+# found in the "models/" directory. You probably won't need to change these!
+model-paths: ["models"]
+analysis-paths: ["analyses"]
+test-paths: ["tests"]
+seed-paths: ["seeds"]
+macro-paths: ["macros"]
+snapshot-paths: ["snapshots"]
+
+target-path: "target"  # directory which will store compiled SQL files
+clean-targets:         # directories to be removed by `dbt clean`
+  - "target"
+  - "dbt_packages"
+
+
 # Configuring models
 # Full documentation: https://docs.getdbt.com/docs/configuring-models