Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ uv run poe typecheck # Run ty type checking
uv run poe test # Run all tests
uv run poe test-unit # Run unit tests only
uv run poe test-integration # Run integration tests only
uv run poe test-e2e # Run e2e tests (real dbt execution)
uv run poe check # Run lint + typecheck together
```

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ branch = true
testpaths = ["tests"]
markers = [
"integration: marks tests as integration tests (deselect with '-m \"not integration\"')",
"e2e: marks tests as end-to-end tests requiring dbt execution (deselect with '-m \"not e2e\"')",
]

[tool.poe.tasks]
Expand All @@ -116,6 +117,7 @@ typecheck = "ty check"
test = "pytest"
test-unit = "pytest tests/unit"
test-integration = "pytest tests/integration -m integration"
test-e2e = "pytest tests/e2e -m e2e"
check = ["lint", "typecheck"]
pre-commit = "pre-commit run --all-files"

Expand Down
53 changes: 43 additions & 10 deletions src/brix/commands/dbt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""dbt command - CLI interface for dbt operations."""

from pathlib import Path
from typing import Annotated

import click
import typer
from typer.core import TyperGroup

from brix.commands.dbt.profile import app as profile_app
from brix.modules.dbt import run_dbt
from brix.commands.dbt.project import app as project_app
from brix.modules.dbt import CachedPathNotFoundError, load_project_cache, run_dbt, save_project_cache


class DbtGroup(TyperGroup):
Expand All @@ -29,30 +33,59 @@ def invoke(self, ctx: click.Context) -> None:

if cmd is None and ctx.protected_args:
# No matching command - pass through to dbt
exit_code = run_dbt(ctx.protected_args + ctx.args)
# Extract --project option from context params (set by option parsing)
project_param = ctx.params.get("project")
project_path: Path | None = Path(project_param) if project_param else None

# If project path provided, save to cache
if project_path is not None:
save_project_cache(project_path)
else:
# Try to load from cache
try:
project_path = load_project_cache()
except CachedPathNotFoundError as e:
typer.echo(f"Error: {e}", err=True)
typer.echo("Please specify a valid project path with --project", err=True)
ctx.exit(1)

exit_code = run_dbt(ctx.protected_args + ctx.args, project_path=project_path)
ctx.exit(exit_code)
else:
super().invoke(ctx)


app = typer.Typer(
cls=DbtGroup,
help="Run dbt commands.",
help="Run dbt commands.\n\nCommands not matching built-in commands will be passed through to dbt CLI.",
invoke_without_command=True,
context_settings={"allow_extra_args": True, "ignore_unknown_options": True, "help_option_names": ["-h", "--help"]},
)
app.add_typer(profile_app, name="profile")
app.add_typer(project_app, name="project")


@app.callback()
def dbt_callback(ctx: typer.Context) -> None:
def dbt_callback(
ctx: typer.Context,
project: Annotated[
Path | None,
typer.Option(
"--project",
"-p",
help="Path to dbt project directory. Cached for subsequent commands.",
exists=True,
file_okay=False,
dir_okay=True,
resolve_path=True,
),
] = None,
) -> None:
"""Run dbt commands - custom commands or passthrough to dbt CLI."""
# Store project path in context for use by DbtGroup.invoke()
ctx.ensure_object(dict)
ctx.obj["project_path"] = project

# If no args at all, show help
if ctx.invoked_subcommand is None and not ctx.args and not ctx.protected_args:
typer.echo(ctx.get_help())


@app.command()
def setup() -> None:
"""Setup dbt project configuration (placeholder)."""
typer.echo("dbt setup - not yet implemented")
Loading