Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Support piperider compare between git refs #888

Merged
merged 9 commits into from
Sep 18, 2023
25 changes: 23 additions & 2 deletions piperider_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ def cloud_compare_reports(**kwargs):


@cli.command(name='compare', short_help='Compare the change for the current branch.', cls=TrackCommand)
@click.argument('ref', required=False, type=click.STRING)
@click.option('--recipe', default=None, type=click.STRING, help='Select a different recipe.')
@click.option('--upload', default=False, is_flag=True, help='Upload the report to PipeRider Cloud.')
@click.option('--share', default=False, is_flag=True, help='Enable public share of the report to PipeRider Cloud.')
Expand All @@ -450,13 +451,33 @@ def cloud_compare_reports(**kwargs):
])
@add_options(dbt_related_options)
@add_options(debug_option)
def compare_with_recipe(**kwargs):
def compare_with_recipe(ref, **kwargs):
"""
Generate the comparison report for your branch.

\b
# compare with main/master branch
piperider compare

\b
# compare with specific branch
piperider compare --base-branch <branch>

\b
# compare with any reference
piperider compare <git-ref>

\b
# compare with two references
piperider compare <git-ref>...<git-ref>

\b
Note: <git-ref> can be reference that git understands. e.g., branch, tag, commit, etc.

"""

from piperider_cli.cli_utils.compare_with_recipe import compare_with_recipe as cmd
return cmd(**kwargs)
return cmd(ref, **kwargs)


@cloud.command(short_help='Signup to PipeRider Cloud.', cls=TrackCommand)
Expand Down
46 changes: 42 additions & 4 deletions piperider_cli/cli_utils/compare_with_recipe.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
def compare_with_recipe(**kwargs):
from rich.console import Console


def parse_compare_ref(ref: str):
console = Console()

if ref is None:
return None, None

if '...' in ref:
base_ref = ref.split('...')[0]
target_ref = ref.split('...')[1]
if base_ref == '' or target_ref == '':
console.print('[bold red]Error:[/bold red] '
'Please either provide a single git reference or a 3-dot diff comparison form.')
return None, None
elif '..' in ref:
console.print('[bold red]Error:[/bold red] Two-dot diff comparisons are not supported')
return None, None
else:
base_ref = ref
target_ref = None

return base_ref, target_ref


def compare_with_recipe(ref, **kwargs):
"""
Generate the comparison report for your branch.
"""
Expand All @@ -10,6 +36,8 @@ def compare_with_recipe(**kwargs):
from piperider_cli.initializer import Initializer
from piperider_cli.recipes import RecipeConfiguration, configure_recipe_execution_flags, is_recipe_dry_run

console = Console()

recipe = kwargs.get('recipe')
summary_file = kwargs.get('summary_file')
force_upload = kwargs.get('upload')
Expand All @@ -19,10 +47,19 @@ def compare_with_recipe(**kwargs):
debug = kwargs.get('debug', False)
select = kwargs.get('select')
modified = kwargs.get('modified')

base_branch = kwargs.get('base_branch')
skip_datasource_connection = kwargs.get('skip_datasource')

base_ref, target_ref = parse_compare_ref(ref)
if ref is not None and base_ref is None:
return -1

if base_ref is not None and kwargs.get('base_branch') is not None:
console.print("[bold red]Error:[/bold red] "
"'--base-branch' option and '[REF]' argument cannot be used together")
return -1
elif base_ref is None:
base_ref = kwargs.get('base_branch')

# reconfigure recipe global flags
configure_recipe_execution_flags(dry_run=kwargs.get('dry_run'), interactive=kwargs.get('interactive'))

Expand Down Expand Up @@ -55,7 +92,8 @@ def compare_with_recipe(**kwargs):
recipe_name=recipe,
select=select,
modified=modified,
base_branch=base_branch,
base_ref=base_ref,
target_ref=target_ref,
skip_datasource_connection=skip_datasource_connection,
debug=debug)
last = False
Expand Down
8 changes: 4 additions & 4 deletions piperider_cli/dbt/list_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ def create_temp_dir():
return tempfile.mkdtemp()


def load_full_manifest(target_path: str):
def load_full_manifest(target_path: str, project_dir: str = None):
from dbt.adapters.factory import register_adapter
from dbt.parser.manifest import ManifestLoader

runtime_config = PrepareRuntimeConfig(target_path)
runtime_config = PrepareRuntimeConfig(target_path, project_dir=project_dir)
register_adapter(runtime_config)

v = dbt_version
Expand Down Expand Up @@ -207,9 +207,9 @@ def convert_time_type(cls, agate_table: agate.Table, col_idx: int) -> str:
pass


def PrepareRuntimeConfig(target_path: str):
def PrepareRuntimeConfig(target_path: str, project_dir: str = None):
from piperider_cli.configuration import FileSystem
project_root = FileSystem.WORKING_DIRECTORY
project_root = project_dir if project_dir is not None else FileSystem.WORKING_DIRECTORY
profiles_dir = FileSystem.DBT_PROFILES_DIR

def _get_v13_runtime_config(flags):
Expand Down
10 changes: 6 additions & 4 deletions piperider_cli/dbtutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ def _get_state_run_results(dbt_state_dir: str):
return run_results


def _get_state_manifest(dbt_state_dir: str):
def _get_state_manifest(dbt_state_dir: str, project_dir: str = None):
path = os.path.join(dbt_state_dir, 'manifest.json')
if os.path.isabs(path) is False:
if project_dir is not None:
path = os.path.join(project_dir, path)
elif os.path.isabs(path) is False:
from piperider_cli.configuration import FileSystem
path = os.path.join(FileSystem.WORKING_DIRECTORY, path)
with open(path) as f:
Expand Down Expand Up @@ -656,8 +658,8 @@ def check_dbt_manifest(dbt_state_dir: str) -> bool:
return os.path.exists(path)


def get_dbt_manifest(dbt_state_dir: str):
return _get_state_manifest(dbt_state_dir)
def get_dbt_manifest(dbt_state_dir: str, project_dir: str = None):
return _get_state_manifest(dbt_state_dir, project_dir=project_dir)


def load_dbt_resources(target_path: str, select: tuple = None, state=None):
Expand Down
49 changes: 25 additions & 24 deletions piperider_cli/recipe_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

class RecipeExecutor:
@staticmethod
def exec(recipe_name: str, auto_generate_default_recipe: bool = True, select: tuple = None, modified: bool = False,
base_branch: str = None, skip_datasource_connection: bool = False, debug=False) -> RecipeConfiguration:
def exec(recipe_name: str, select: tuple = None, modified: bool = False, base_ref: str = None,
target_ref: str = None, skip_datasource_connection: bool = False, debug=False) -> RecipeConfiguration:
config = Configuration.instance()
recipe_path = select_recipe_file(recipe_name)

if recipe_name and (select or modified or base_branch or skip_datasource_connection):
if recipe_name and (select or modified or base_ref or skip_datasource_connection):
console.print(
"[[bold yellow]Warning[/bold yellow]] "
"The recipe will be ignored when '--select', '--modified', '--base-branch', "
Expand All @@ -26,27 +26,28 @@ def exec(recipe_name: str, auto_generate_default_recipe: bool = True, select: tu
if not skip_datasource_connection and select:
console.print(
f"[[bold green]Select[/bold green]] Manually select the dbt nodes to run by '{','.join(select)}'")
if recipe_path is None or select or modified or base_branch or skip_datasource_connection:
if auto_generate_default_recipe:
dbt_project_path = None
if config.dataSources and config.dataSources[0].args.get('dbt'):
dbt_project_path = os.path.relpath(config.dataSources[0].args.get('dbt', {}).get('projectDir'))
# generate a default recipe
console.rule("Recipe executor: generate recipe")
options = dict(base_branch=base_branch, skip_datasource_connection=skip_datasource_connection)
if select:
options['select'] = select
recipe = generate_default_recipe(overwrite_existing=False,
dbt_project_path=dbt_project_path,
options=options)
if recipe is None:
raise RecipeConfigException(
message='Default recipe generation failed.',
hint='Please provide a recipe file or feedback an issue to us'
)
show_recipe_content(recipe)
else:
raise FileNotFoundError(f"Cannot find the recipe '{recipe_name}'")
if recipe_path is None or select or modified or base_ref or skip_datasource_connection:
dbt_project_path = None
if config.dataSources and config.dataSources[0].args.get('dbt'):
dbt_project_path = os.path.relpath(config.dataSources[0].args.get('dbt', {}).get('projectDir'))
# generate a default recipe
console.rule("Recipe executor: generate recipe")
options = dict(
base_ref=base_ref,
target_ref=target_ref,
skip_datasource_connection=skip_datasource_connection
)
if select:
options['select'] = select
recipe = generate_default_recipe(overwrite_existing=False,
dbt_project_path=dbt_project_path,
options=options)
if recipe is None:
raise RecipeConfigException(
message='Default recipe generation failed.',
hint='Please provide a recipe file or feedback an issue to us'
)
show_recipe_content(recipe)
else:
recipe = RecipeConfiguration.load(recipe_path)

Expand Down
Loading
Loading