Repository: dbt-labs/dbt-core

Issue:
[CT-745] dbt init may early return if no adapters available
### Is this your first time opening an issue?

- [X] I have read the [expectations for open source contributors](https://docs.getdbt.com/docs/contributing/oss-expectations)

### Describe the Feature

Currently `dbt init` prompts for the project name but get stuck on next step if no adapters is available.
Would be nice to early return before the first prompt suggesting to install any adapter in advance.

### Describe alternatives you've considered

_No response_

### Who will this benefit?

New users

### Are you interested in contributing this feature?

Yes

### Anything else?

_No response_


Discussion:

Resolving patch:
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
@@ -299,6 +299,10 @@ def run(self):
 
         # When dbt init is run outside of an existing project,
         # create a new project and set up the user's profile.
+        available_adapters = list(_get_adapter_plugin_names())
+        if not len(available_adapters):
+            print("No adapters available. Go to https://docs.getdbt.com/docs/available-adapters")
+            exit(1)
         project_name = self.get_valid_project_name()
         project_path = Path(project_name)
         if project_path.exists():


Test patch:
diff --git a/test/integration/040_init_tests/test_init.py b/test/integration/040_init_tests/test_init.py
--- a/test/integration/040_init_tests/test_init.py
+++ b/test/integration/040_init_tests/test_init.py
@@ -34,9 +34,10 @@ def models(self):
       reason="Broken because of https://github.com/dbt-labs/dbt-core/pull/5171"
     )
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_task_in_project_with_existing_profiles_yml(self, mock_prompt, mock_confirm):
+    def test_postgres_init_task_in_project_with_existing_profiles_yml(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -51,6 +52,7 @@ def test_postgres_init_task_in_project_with_existing_profiles_yml(self, mock_pro
             'test_schema',
             4,
         ]
+        mock_get_adapter.return_value = [1]
 
         self.run_dbt(['init'])
 
@@ -88,10 +90,11 @@ def test_postgres_init_task_in_project_with_existing_profiles_yml(self, mock_pro
       reason="Broken because of https://github.com/dbt-labs/dbt-core/pull/5171"
     )
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
     @mock.patch.object(Path, 'exists', autospec=True)
-    def test_postgres_init_task_in_project_without_existing_profiles_yml(self, exists, mock_prompt, mock_confirm):
+    def test_postgres_init_task_in_project_without_existing_profiles_yml(self, exists, mock_prompt, mock_confirm, mock_get_adapter):
 
         def exists_side_effect(path):
             # Override responses on specific files, default to 'real world' if not overriden
@@ -112,6 +115,7 @@ def exists_side_effect(path):
             'test_schema',
             4,
         ]
+        mock_get_adapter.return_value = [1]
 
         self.run_dbt(['init'])
 
@@ -146,10 +150,11 @@ def exists_side_effect(path):
       reason="Broken because of https://github.com/dbt-labs/dbt-core/pull/5171"
     )
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
     @mock.patch.object(Path, 'exists', autospec=True)
-    def test_postgres_init_task_in_project_without_existing_profiles_yml_or_profile_template(self, exists, mock_prompt, mock_confirm):
+    def test_postgres_init_task_in_project_without_existing_profiles_yml_or_profile_template(self, exists, mock_prompt, mock_confirm, mock_get_adapter):
 
         def exists_side_effect(path):
             # Override responses on specific files, default to 'real world' if not overriden
@@ -165,6 +170,7 @@ def exists_side_effect(path):
         manager.prompt.side_effect = [
             1,
         ]
+        mock_get_adapter.return_value = [1]
         self.run_dbt(['init'])
         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),
@@ -198,10 +204,11 @@ def exists_side_effect(path):
 """
 
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
     @mock.patch.object(Path, 'exists', autospec=True)
-    def test_postgres_init_task_in_project_with_profile_template_without_existing_profiles_yml(self, exists, mock_prompt, mock_confirm):
+    def test_postgres_init_task_in_project_with_profile_template_without_existing_profiles_yml(self, exists, mock_prompt, mock_confirm, mock_get_adapter):
 
         def exists_side_effect(path):
             # Override responses on specific files, default to 'real world' if not overriden
@@ -241,6 +248,7 @@ def exists_side_effect(path):
             'test_username',
             'test_password'
         ]
+        mock_get_adapter.return_value = [1]
         self.run_dbt(['init'])
         manager.assert_has_calls([
             call.prompt('target (The target name)', default=None, hide_input=False, type=click.STRING),
@@ -268,9 +276,10 @@ def exists_side_effect(path):
       reason="Broken because of https://github.com/dbt-labs/dbt-core/pull/5171"
     )
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_task_in_project_with_invalid_profile_template(self, mock_prompt, mock_confirm):
+    def test_postgres_init_task_in_project_with_invalid_profile_template(self, mock_prompt, mock_confirm, mock_get_adapter):
         """Test that when an invalid profile_template.yml is provided in the project,
         init command falls back to the target's profile_template.yml"""
 
@@ -291,6 +300,7 @@ def test_postgres_init_task_in_project_with_invalid_profile_template(self, mock_
             'test_schema',
             4,
         ]
+        mock_get_adapter.return_value = [1]
 
         self.run_dbt(['init'])
 
@@ -327,9 +337,10 @@ def test_postgres_init_task_in_project_with_invalid_profile_template(self, mock_
       reason="Broken because of https://github.com/dbt-labs/dbt-core/pull/5171"
     )
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_task_outside_of_project(self, mock_prompt, mock_confirm):
+    def test_postgres_init_task_outside_of_project(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -349,6 +360,7 @@ def test_postgres_init_task_outside_of_project(self, mock_prompt, mock_confirm):
             'test_schema',
             4,
         ]
+        mock_get_adapter.return_value = [1]
         self.run_dbt(['init'])
         manager.assert_has_calls([
             call.prompt("Enter a name for your project (letters, digits, underscore)"),
@@ -445,9 +457,10 @@ def test_postgres_init_task_outside_of_project(self, mock_prompt, mock_confirm):
       reason="Broken because of https://github.com/dbt-labs/dbt-core/pull/5171"
     )
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_with_provided_project_name(self, mock_prompt, mock_confirm):
+    def test_postgres_init_with_provided_project_name(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -465,6 +478,7 @@ def test_postgres_init_with_provided_project_name(self, mock_prompt, mock_confir
             'test_schema',
             4,
         ]
+        mock_get_adapter.return_value = [1]
 
         # Provide project name through the init command.
         project_name = self.get_project_name()
@@ -560,9 +574,10 @@ def test_postgres_init_with_provided_project_name(self, mock_prompt, mock_confir
 """
 
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_invalid_project_name_cli(self, mock_prompt, mock_confirm):
+    def test_postgres_init_invalid_project_name_cli(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -573,6 +588,7 @@ def test_postgres_init_invalid_project_name_cli(self, mock_prompt, mock_confirm)
         manager.prompt.side_effect = [
             valid_name
         ]
+        mock_get_adapter.return_value = [1]
 
         self.run_dbt(['init', invalid_name, '-s'])
         manager.assert_has_calls([
@@ -580,9 +596,10 @@ def test_postgres_init_invalid_project_name_cli(self, mock_prompt, mock_confirm)
         ])
 
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_invalid_project_name_prompt(self, mock_prompt, mock_confirm):
+    def test_postgres_init_invalid_project_name_prompt(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -594,6 +611,7 @@ def test_postgres_init_invalid_project_name_prompt(self, mock_prompt, mock_confi
         manager.prompt.side_effect = [
             invalid_name, valid_name
         ]
+        mock_get_adapter.return_value = [1]
 
         self.run_dbt(['init', '-s'])
         manager.assert_has_calls([
@@ -602,9 +620,10 @@ def test_postgres_init_invalid_project_name_prompt(self, mock_prompt, mock_confi
         ])
 
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_skip_profile_setup(self, mock_prompt, mock_confirm):
+    def test_postgres_init_skip_profile_setup(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -616,6 +635,7 @@ def test_postgres_init_skip_profile_setup(self, mock_prompt, mock_confirm):
         manager.prompt.side_effect = [
             project_name,
         ]
+        mock_get_adapter.return_value = [1]
 
         # provide project name through the ini command
         self.run_dbt(['init', '-s'])
@@ -665,9 +685,10 @@ def test_postgres_init_skip_profile_setup(self, mock_prompt, mock_confirm):
 """
 
     @use_profile('postgres')
+    @mock.patch('dbt.task.init._get_adapter_plugin_names')
     @mock.patch('click.confirm')
     @mock.patch('click.prompt')
-    def test_postgres_init_provided_project_name_and_skip_profile_setup(self, mock_prompt, mock_confirm):
+    def test_postgres_init_provided_project_name_and_skip_profile_setup(self, mock_prompt, mock_confirm, mock_get_adapter):
         manager = Mock()
         manager.attach_mock(mock_prompt, 'prompt')
         manager.attach_mock(mock_confirm, 'confirm')
@@ -685,6 +706,7 @@ def test_postgres_init_provided_project_name_and_skip_profile_setup(self, mock_p
             'test_schema',
             4,
         ]
+        mock_get_adapter.return_value = [1]
 
         # provide project name through the ini command
         project_name = self.get_project_name()