From 89cae6e3bbdb15e10a19eb67eea78a4ef01110bd Mon Sep 17 00:00:00 2001 From: Abdelkader Kouhli Date: Tue, 9 Nov 2021 09:26:02 +0100 Subject: [PATCH] Dbt init with provided project name --- core/dbt/main.py | 16 ++ core/dbt/task/init.py | 46 ++-- test/integration/040_init_test/test_init.py | 254 ++++++++++++++++++++ 3 files changed, 297 insertions(+), 19 deletions(-) diff --git a/core/dbt/main.py b/core/dbt/main.py index ba7058f88bf..83d900a4bab 100644 --- a/core/dbt/main.py +++ b/core/dbt/main.py @@ -343,6 +343,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 new 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 index ba54800fb9f..cb801a21974 100644 --- a/core/dbt/task/init.py +++ b/core/dbt/task/init.py @@ -295,28 +295,36 @@ 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(): - logger.info( - f"A project called {project_name} already exists here." - ) - 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(): + logger.info( + f"A project called {project_name} already exists here." + ) + 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() diff --git a/test/integration/040_init_test/test_init.py b/test/integration/040_init_test/test_init.py index 8b3481804de..db660751748 100644 --- 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