From fa5438ccf41f17d622d46e0476a621c97d360596 Mon Sep 17 00:00:00 2001 From: Spycner Date: Fri, 28 Nov 2025 13:41:21 +0100 Subject: [PATCH 1/3] feat: add initial documentation and configuration for Brix CLI - Introduced mkdocs configuration for project documentation. - Added comprehensive user guides covering installation, quick start, command usage, and profile management. - Implemented a GitHub Actions workflow for automatic documentation deployment. - Created API and developer guides to assist contributors and users in understanding the project structure and contribution process. - Enhanced README with project description, features, and installation instructions. --- .github/workflows/docs.yml | 27 +++ .gitignore | 3 +- README.md | 69 +++++- docs/api/index.md | 81 +++++++ docs/api/models.md | 19 ++ docs/api/modules.md | 52 +++++ docs/developer-guide/adding-adapters.md | 258 ++++++++++++++++++++ docs/developer-guide/architecture.md | 198 ++++++++++++++++ docs/developer-guide/contributing.md | 232 ++++++++++++++++++ docs/developer-guide/testing.md | 297 ++++++++++++++++++++++++ docs/getting-started/installation.md | 96 ++++++++ docs/getting-started/quickstart.md | 106 +++++++++ docs/index.md | 58 +++++ docs/user-guide/commands.md | 114 +++++++++ docs/user-guide/configuration.md | 153 ++++++++++++ docs/user-guide/passthrough.md | 187 +++++++++++++++ docs/user-guide/profiles.md | 222 ++++++++++++++++++ docs/user-guide/projects.md | 254 ++++++++++++++++++++ mkdocs.yml | 73 ++++++ pyproject.toml | 13 ++ uv.lock | 297 +++++++++++++++++++++++- 21 files changed, 2806 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/api/index.md create mode 100644 docs/api/models.md create mode 100644 docs/api/modules.md create mode 100644 docs/developer-guide/adding-adapters.md create mode 100644 docs/developer-guide/architecture.md create mode 100644 docs/developer-guide/contributing.md create mode 100644 docs/developer-guide/testing.md create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/quickstart.md create mode 100644 docs/index.md create mode 100644 docs/user-guide/commands.md create mode 100644 docs/user-guide/configuration.md create mode 100644 docs/user-guide/passthrough.md create mode 100644 docs/user-guide/profiles.md create mode 100644 docs/user-guide/projects.md create mode 100644 mkdocs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..ab78d50 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,27 @@ +name: Deploy Documentation + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Set up Python + run: uv python install + + - name: Install dependencies + run: uv sync --extra docs + + - name: Build and deploy docs + run: uv run mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index ab46d4d..9ff206f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ htmlcov .vscode *.swp -logs \ No newline at end of file +logs +site \ No newline at end of file diff --git a/README.md b/README.md index 0636745..4637f48 100644 --- a/README.md +++ b/README.md @@ -1 +1,68 @@ -# databricks-dbt-cli \ No newline at end of file +# Brix + +[![PyPI version](https://badge.fury.io/py/brix.svg)](https://badge.fury.io/py/brix) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Documentation](https://img.shields.io/badge/docs-mkdocs-blue.svg)](https://spycner.github.io/brix/) + +**CLI for dbt project and profile management with Databricks focus** + +Brix simplifies dbt workflow by providing convenient commands for profile and project management while allowing full passthrough to the native dbt CLI. + +## Features + +- **Profile Management** - Initialize, view, and edit `profiles.yml` with interactive or CLI modes +- **Project Scaffolding** - Create dbt projects with sensible defaults and package management +- **dbt Passthrough** - Run any dbt command through brix (`brix dbt run`, `brix dbt test`, etc.) +- **Multiple Adapters** - Built-in support for DuckDB (local development) and Databricks +- **Interactive & CLI Modes** - Use guided wizards or script with CLI flags + +## Installation + +```bash +pip install brix +``` + +Or with [uv](https://docs.astral.sh/uv/): + +```bash +uv tool install brix +``` + +## Quick Start + +```bash +# Initialize a dbt profile +brix dbt profile init + +# Create a new dbt project +brix dbt project init + +# Run dbt commands +brix dbt run +brix dbt test +``` + +## Documentation + +Full documentation is available at **[spycner.github.io/brix](https://spycner.github.io/brix/)** + +- [Installation](https://spycner.github.io/brix/getting-started/installation/) +- [Quick Start](https://spycner.github.io/brix/getting-started/quickstart/) +- [Command Reference](https://spycner.github.io/brix/user-guide/commands/) +- [Developer Guide](https://spycner.github.io/brix/developer-guide/architecture/) +- [API Reference](https://spycner.github.io/brix/api/) + +## Development + +```bash +git clone https://github.com/Spycner/brix.git +cd brix +uv sync +uv run brix --help +``` + +See the [Contributing Guide](https://spycner.github.io/brix/developer-guide/contributing/) for development setup and guidelines. + +## License + +MIT License - see [LICENSE](LICENSE) for details. diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..9b428ad --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,81 @@ +# API Reference + +Brix can be used as a Python library in addition to the CLI. + +## Overview + +The brix package is organized into modules that can be imported directly: + +```python +from brix.modules.dbt.profile.models import DbtProfiles, DuckDbOutput +from brix.modules.dbt.profile.service import init_profile +from brix.modules.dbt.project.models import DbtProject +``` + +## Package Structure + +``` +brix.modules.dbt.profile +├── models # Pydantic models for profiles.yml +├── service # Profile initialization and operations +├── editor # Profile CRUD operations +└── prompts # Interactive prompts + +brix.modules.dbt.project +├── models # Pydantic models for dbt_project.yml +├── service # Project initialization +├── editor # Project CRUD operations +├── finder # Project discovery +└── prompts # Interactive prompts + +brix.utils +└── logging # Terraform-style logger +``` + +## Quick Examples + +### Working with Profiles + +```python +from brix.modules.dbt.profile.models import DbtProfiles, DuckDbOutput + +# Create a profile programmatically +profiles = DbtProfiles( + profiles={ + "my_project": { + "target": "dev", + "outputs": { + "dev": DuckDbOutput(path="./dev.duckdb"), + }, + }, + }, +) + +# Serialize to YAML +yaml_content = profiles.to_yaml() + +# Parse from YAML +loaded = DbtProfiles.from_yaml(yaml_content) +``` + +### Working with Projects + +```python +from brix.modules.dbt.project.models import DbtProject + +# Load a project +with open("dbt_project.yml") as f: + project = DbtProject.from_yaml(f.read()) + +# Modify +project.name = "new_name" + +# Save +with open("dbt_project.yml", "w") as f: + f.write(project.to_yaml()) +``` + +## Reference Sections + +- [Modules](modules.md) - Service and editor functions +- [Models](models.md) - Pydantic data models diff --git a/docs/api/models.md b/docs/api/models.md new file mode 100644 index 0000000..9cbfede --- /dev/null +++ b/docs/api/models.md @@ -0,0 +1,19 @@ +# Models Reference + +Auto-generated API documentation for brix Pydantic models. + +## Profile Models + +::: brix.modules.dbt.profile.models + options: + show_root_heading: true + members_order: source + show_bases: true + +## Project Models + +::: brix.modules.dbt.project.models + options: + show_root_heading: true + members_order: source + show_bases: true diff --git a/docs/api/modules.md b/docs/api/modules.md new file mode 100644 index 0000000..df0da74 --- /dev/null +++ b/docs/api/modules.md @@ -0,0 +1,52 @@ +# Modules Reference + +Auto-generated API documentation for brix modules. + +## Profile Service + +::: brix.modules.dbt.profile.service + options: + show_root_heading: true + members_order: source + +## Profile Editor + +::: brix.modules.dbt.profile.editor + options: + show_root_heading: true + members_order: source + +## Project Service + +::: brix.modules.dbt.project.service + options: + show_root_heading: true + members_order: source + +## Project Editor + +::: brix.modules.dbt.project.editor + options: + show_root_heading: true + members_order: source + +## Project Finder + +::: brix.modules.dbt.project.finder + options: + show_root_heading: true + members_order: source + +## dbt Passthrough + +::: brix.modules.dbt.passthrough + options: + show_root_heading: true + members_order: source + +## Logging + +::: brix.utils.logging + options: + show_root_heading: true + members_order: source diff --git a/docs/developer-guide/adding-adapters.md b/docs/developer-guide/adding-adapters.md new file mode 100644 index 0000000..7c64ba8 --- /dev/null +++ b/docs/developer-guide/adding-adapters.md @@ -0,0 +1,258 @@ +# Adding Adapters + +Guide for adding new database adapter support to brix. + +## Overview + +Brix supports multiple dbt adapters through Pydantic models with discriminated unions. Adding a new adapter requires: + +1. Creating a Pydantic model for the adapter +2. Adding to the discriminated union +3. Implementing interactive prompts +4. Adding tests + +## Step 1: Create the Model + +Add a new model in `src/brix/modules/dbt/profile/models.py`: + +```python +from pydantic import BaseModel, Field + +class SnowflakeOutput(BaseModel): + """Snowflake adapter output configuration.""" + + type: Literal["snowflake"] = "snowflake" + account: str = Field(..., description="Snowflake account identifier") + user: str = Field(..., description="Username") + password: str | None = Field(None, description="Password (use env_var)") + role: str = Field(..., description="Role to use") + database: str = Field(..., description="Database name") + warehouse: str = Field(..., description="Warehouse name") + schema_: str = Field(..., alias="schema", description="Schema name") + threads: int = Field(4, description="Number of threads") + + model_config = ConfigDict(populate_by_name=True) +``` + +Key considerations: +- Use `Literal["adapter_name"]` for the `type` field +- Use `Field(...)` for required fields +- Use `Field(alias="schema")` for reserved Python keywords +- Document fields with `description` + +## Step 2: Add to Discriminated Union + +Update the `OutputConfig` type in `models.py`: + +```python +OutputConfig = Annotated[ + DuckDbOutput | DatabricksOutput | SnowflakeOutput, + Field(discriminator="type"), +] +``` + +The discriminator ensures the correct model is used based on the `type` field. + +## Step 3: Add Interactive Prompts + +Add prompts in `src/brix/modules/dbt/profile/prompts.py`: + +```python +import questionary + +def prompt_snowflake_output(name: str = "") -> SnowflakeOutput: + """Prompt for Snowflake output configuration.""" + name = name or questionary.text( + "Output name:", + default="prod", + ).ask() + + account = questionary.text( + "Snowflake account:", + instruction="e.g., xy12345.us-east-1", + ).ask() + + user = questionary.text("Username:").ask() + + password = questionary.password( + "Password (leave empty to use env var):" + ).ask() + + role = questionary.text( + "Role:", + default="ACCOUNTADMIN", + ).ask() + + database = questionary.text("Database:").ask() + warehouse = questionary.text("Warehouse:").ask() + schema = questionary.text("Schema:", default="public").ask() + + threads = int(questionary.text( + "Threads:", + default="4", + ).ask()) + + # Use env_var for password if not provided + password_value = ( + password if password + else "{{ env_var('DBT_SNOWFLAKE_PASSWORD') }}" + ) + + return SnowflakeOutput( + type="snowflake", + account=account, + user=user, + password=password_value, + role=role, + database=database, + warehouse=warehouse, + schema=schema, + threads=threads, + ) +``` + +## Step 4: Register in Adapter Selection + +Update the adapter selection in `prompts.py`: + +```python +def prompt_output_type() -> str: + """Prompt for adapter type selection.""" + return questionary.select( + "Select adapter type:", + choices=[ + "duckdb", + "databricks", + "snowflake", # Add new adapter + ], + ).ask() + + +def prompt_new_output(adapter_type: str, name: str = "") -> OutputConfig: + """Create output based on selected adapter type.""" + if adapter_type == "duckdb": + return prompt_duckdb_output(name) + elif adapter_type == "databricks": + return prompt_databricks_output(name) + elif adapter_type == "snowflake": + return prompt_snowflake_output(name) + else: + raise ValueError(f"Unknown adapter type: {adapter_type}") +``` + +## Step 5: Add Tests + +### Unit Tests + +Create `tests/unit/test_snowflake_models.py`: + +```python +import pytest +from brix.modules.dbt.profile.models import SnowflakeOutput, OutputConfig + +def test_snowflake_output_creation(): + output = SnowflakeOutput( + account="xy12345.us-east-1", + user="dbt_user", + password="{{ env_var('DBT_SNOWFLAKE_PASSWORD') }}", + role="TRANSFORM_ROLE", + database="ANALYTICS", + warehouse="COMPUTE_WH", + schema="dbt_prod", + ) + assert output.type == "snowflake" + assert output.account == "xy12345.us-east-1" + +def test_snowflake_output_discriminator(): + """Test that discriminated union correctly identifies Snowflake.""" + data = { + "type": "snowflake", + "account": "xy12345", + "user": "user", + "role": "role", + "database": "db", + "warehouse": "wh", + "schema": "public", + } + # This would be used when parsing YAML + from pydantic import TypeAdapter + adapter = TypeAdapter(OutputConfig) + output = adapter.validate_python(data) + assert isinstance(output, SnowflakeOutput) +``` + +### Integration Tests + +Create `tests/integration/test_snowflake_profile.py`: + +```python +import pytest +from pathlib import Path +from brix.modules.dbt.profile.models import DbtProfiles, SnowflakeOutput + +@pytest.mark.integration +def test_snowflake_profile_yaml_roundtrip(tmp_path: Path): + """Test Snowflake profile YAML serialization.""" + profiles = DbtProfiles( + profiles={ + "snowflake_project": { + "target": "prod", + "outputs": { + "prod": SnowflakeOutput( + account="xy12345", + user="dbt", + role="transform", + database="analytics", + warehouse="compute", + schema="dbt", + ), + }, + }, + }, + ) + + yaml_path = tmp_path / "profiles.yml" + yaml_path.write_text(profiles.to_yaml()) + + loaded = DbtProfiles.from_yaml(yaml_path.read_text()) + assert "snowflake_project" in loaded.profiles +``` + +## Step 6: Update Documentation + +Add adapter documentation in `docs/user-guide/profiles.md`: + +```markdown +### Snowflake + +For Snowflake data warehouse. + +#### Configuration + +```yaml +outputs: + prod: + type: snowflake + account: xy12345.us-east-1 + user: dbt_user + password: "{{ env_var('DBT_SNOWFLAKE_PASSWORD') }}" + role: TRANSFORM_ROLE + database: ANALYTICS + warehouse: COMPUTE_WH + schema: dbt_prod + threads: 4 +``` +``` + +## Checklist + +- [ ] Create Pydantic model with all required fields +- [ ] Add to `OutputConfig` discriminated union +- [ ] Implement `prompt_*_output()` function +- [ ] Update `prompt_output_type()` choices +- [ ] Update `prompt_new_output()` dispatcher +- [ ] Add unit tests for model validation +- [ ] Add integration tests for YAML roundtrip +- [ ] Update documentation with configuration examples +- [ ] Run full test suite: `uv run poe test` +- [ ] Run pre-commit: `uv run poe pre-commit` diff --git a/docs/developer-guide/architecture.md b/docs/developer-guide/architecture.md new file mode 100644 index 0000000..a76b993 --- /dev/null +++ b/docs/developer-guide/architecture.md @@ -0,0 +1,198 @@ +# Architecture + +Brix follows a layered architecture that separates CLI concerns from business logic. + +## Project Structure + +``` +src/brix/ +├── commands/ # CLI layer (Typer) +│ └── dbt/ +│ ├── __init__.py # DbtGroup passthrough +│ ├── profile.py # Profile CLI commands +│ └── project.py # Project CLI commands +├── modules/ # Business logic layer +│ └── dbt/ +│ ├── passthrough.py # dbt CLI execution +│ ├── profile/ +│ │ ├── models.py # Pydantic models +│ │ ├── service.py # Core operations +│ │ ├── editor.py # CRUD operations +│ │ └── prompts.py # Interactive prompts +│ └── project/ +│ ├── models.py +│ ├── service.py +│ ├── editor.py +│ ├── prompts.py +│ └── finder.py # Project discovery +├── templates/ # Bundled templates +├── utils/ +│ └── logging.py # Terraform-style logger +├── version_check.py # Background version checking +└── main.py # Entry point +``` + +## Layer Separation + +### CLI Layer (`commands/`) + +Responsibilities: +- Argument parsing with Typer +- Output formatting (typer.echo) +- Error handling and exit codes +- No business logic + +### Business Logic Layer (`modules/`) + +Responsibilities: +- Core operations (init, edit, validate) +- Data models (Pydantic) +- File I/O +- No CLI dependencies + +This separation allows: +- Unit testing without CLI +- Reuse as a library +- Clear responsibility boundaries + +## Key Patterns + +### DbtGroup Passthrough + +The `DbtGroup` class in `commands/dbt/__init__.py` intercepts unknown commands and passes them to the native dbt CLI. + +```python +class DbtGroup(TyperGroup): + def resolve_command(self, ctx, args): + try: + return super().resolve_command(ctx, args) + except click.UsageError: + return None, None, args # Pass through to dbt + + def invoke(self, ctx): + if no_command_matched: + run_dbt(args) # Execute native dbt +``` + +This enables `brix dbt run`, `brix dbt test`, etc. to work transparently. + +### Pydantic Models with Discriminated Unions + +Profile models use discriminated unions for adapter types: + +```python +OutputConfig = Annotated[ + DuckDbOutput | DatabricksOutput, + Field(discriminator="type") +] +``` + +This provides: +- Type-safe YAML parsing +- Automatic validation +- Clear error messages + +### Configuration with pydantic-settings + +All configuration uses `BaseSettings` with `BRIX_` prefix: + +```python +class ProfileConfig(BaseSettings): + profile_path: Path = Path("~/.dbt/profiles.yml") + + model_config = SettingsConfigDict(env_prefix="BRIX_DBT_") +``` + +Override chain: CLI args > env vars > defaults + +### Result Objects + +Operations return structured result objects instead of exceptions: + +```python +@dataclass +class ProfileInitResult: + success: bool + path: Path + action: str # "created", "exists", "overwritten" + message: str +``` + +### Thread-safe Logging + +The logger is a singleton with thread-safe initialization: + +```python +logger = get_logger() # Always returns same instance +logger.debug("message %s", arg) # Lazy evaluation +``` + +Features: +- Custom TRACE level +- Terraform-style output +- JSON format support +- Non-blocking version check + +### Template System + +Templates are bundled with the package and loaded via `importlib.resources`: + +```python +from brix.templates import get_template + +content = get_template("profiles.yml") +``` + +## Data Flow + +``` +User Input (CLI) + ↓ +commands/ (Typer) + │ + ├── Parse arguments + ├── Validate input + └── Call business logic + ↓ +modules/ + │ + ├── models.py → Validate data structures + ├── service.py → Execute operations + ├── editor.py → Modify files + └── prompts.py → Interactive input + ↓ +File System / dbt CLI + ↓ +Result object + ↓ +commands/ + │ + └── Format output (typer.echo) + ↓ +User Output +``` + +## Module Structure Convention + +Each domain follows this structure: + +| File | Purpose | +|------|---------| +| `models.py` | Pydantic data models | +| `service.py` | Initialization, resolution, fetching | +| `editor.py` | CRUD operations | +| `prompts.py` | questionary interactive prompts | + +## Dependencies + +**Runtime:** +- `typer` - CLI framework +- `pydantic` / `pydantic-settings` - Data validation +- `questionary` - Interactive prompts +- `httpx` - HTTP requests + +**Development:** +- `ruff` - Linting and formatting +- `ty` - Type checking +- `pytest` - Testing +- `dbt-core`, `dbt-databricks`, `dbt-duckdb` - Integration tests diff --git a/docs/developer-guide/contributing.md b/docs/developer-guide/contributing.md new file mode 100644 index 0000000..1cf0584 --- /dev/null +++ b/docs/developer-guide/contributing.md @@ -0,0 +1,232 @@ +# Contributing + +Guide for contributing to brix development. + +## Development Setup + +### Prerequisites + +- Python 3.10+ +- [uv](https://docs.astral.sh/uv/) package manager +- Git + +### Clone and Install + +```bash +git clone https://github.com/Spycner/brix.git +cd brix +uv sync +``` + +### Verify Setup + +```bash +uv run brix --version +uv run poe test +``` + +## Development Workflow + +### Task Runner + +Use poethepoet for common tasks: + +```bash +uv run poe lint # Run ruff linting +uv run poe format # Run ruff formatting +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 +uv run poe test-e2e # Run e2e tests +uv run poe check # Run lint + typecheck +uv run poe pre-commit # Run pre-commit hooks +``` + +### Running the CLI + +```bash +uv run brix --help +uv run brix dbt profile init +``` + +### Running Tests + +```bash +# All tests +uv run poe test + +# Specific test file +uv run pytest tests/unit/test_profile_models.py -v + +# Specific test +uv run pytest tests/unit/test_profile_models.py::test_duckdb_output -v + +# With coverage +uv run pytest --cov=brix +``` + +## Code Style + +### Linting and Formatting + +Brix uses ruff for linting and formatting: + +```bash +# Check for issues +uv run poe lint + +# Auto-format +uv run poe format +``` + +### Type Hints + +All public functions require type hints (ANN rules enforced): + +```python +def init_profile( + profile_path: Path, + force: bool = False, + template_name: str = "profiles.yml", +) -> ProfileInitResult: + ... +``` + +### Docstrings + +Use Google-style docstrings: + +```python +def init_profile(profile_path: Path, force: bool = False) -> ProfileInitResult: + """Initialize a dbt profile from template. + + Creates a profiles.yml file at the specified path with a DuckDB + configuration for local development. + + Args: + profile_path: Path where profiles.yml will be created. + force: If True, overwrite existing file. + + Returns: + ProfileInitResult with success status and message. + + Raises: + ProfileExistsError: If file exists and force is False. + """ +``` + +### Line Length + +Maximum 120 characters per line. + +## Pre-commit Hooks + +Always run pre-commit before committing: + +```bash +uv run poe pre-commit +``` + +Or install hooks to run automatically: + +```bash +uv run pre-commit install +``` + +## Pull Request Process + +1. **Fork** the repository +2. **Create a branch** from `main` +3. **Make changes** following code style guidelines +4. **Add tests** for new functionality +5. **Run checks**: `uv run poe check && uv run poe test` +6. **Run pre-commit**: `uv run poe pre-commit` +7. **Create PR** with clear description + +### Commit Messages + +Follow conventional commits: + +``` +feat: add snowflake adapter support +fix: handle empty profiles.yml gracefully +docs: update installation instructions +test: add integration tests for project init +refactor: extract validation logic to separate module +``` + +### PR Description + +Include: +- Summary of changes +- Related issues +- Test coverage +- Breaking changes (if any) + +## Adding Features + +### Adding a New Command + +1. Create command file in `commands/dbt/`: + +```python +# commands/dbt/new_command.py +import typer + +app = typer.Typer() + +@app.command() +def action(name: str) -> None: + """Command description.""" + from brix.modules.dbt.new_feature import do_action + result = do_action(name) + typer.echo(result.message) +``` + +2. Register in `commands/dbt/__init__.py`: + +```python +from brix.commands.dbt.new_command import app as new_command_app + +app.add_typer(new_command_app, name="new-command") +``` + +3. Add business logic in `modules/dbt/new_feature/` + +### Adding Tests + +Place tests in the appropriate directory: + +- `tests/unit/` - No external dependencies, mocked I/O +- `tests/integration/` - May require dbt, real file operations +- `tests/e2e/` - Full dbt execution, real commands + +Use markers for non-unit tests: + +```python +import pytest + +@pytest.mark.integration +def test_profile_creation(): + ... + +@pytest.mark.e2e +def test_full_workflow(): + ... +``` + +## Release Process + +Releases are automated via semantic-release: + +1. Merge PR to `main` +2. GitHub Action analyzes commits +3. Bumps version based on commit types +4. Creates GitHub release +5. Publishes to PyPI + +Version bumps: +- `fix:` → patch (1.0.x) +- `feat:` → minor (1.x.0) +- `BREAKING CHANGE:` → major (x.0.0) diff --git a/docs/developer-guide/testing.md b/docs/developer-guide/testing.md new file mode 100644 index 0000000..dfe248f --- /dev/null +++ b/docs/developer-guide/testing.md @@ -0,0 +1,297 @@ +# Testing + +Guide for testing brix functionality. + +## Test Structure + +``` +tests/ +├── unit/ # No external dependencies +├── integration/ # May require dbt, real files +└── e2e/ # Full dbt execution +``` + +## Test Categories + +### Unit Tests (`tests/unit/`) + +Fast tests with no external dependencies: +- Model validation +- Pure functions +- Mocked I/O + +```bash +uv run poe test-unit +``` + +Example: + +```python +from brix.modules.dbt.profile.models import DuckDbOutput + +def test_duckdb_output_defaults(): + output = DuckDbOutput(path="./test.duckdb") + assert output.type == "duckdb" + assert output.threads == 4 # default +``` + +### Integration Tests (`tests/integration/`) + +Tests requiring real file operations or dbt: +- File creation/modification +- YAML parsing +- Template rendering + +```bash +uv run poe test-integration +``` + +Mark with `@pytest.mark.integration`: + +```python +import pytest +from pathlib import Path + +@pytest.mark.integration +def test_profile_yaml_creation(tmp_path: Path): + from brix.modules.dbt.profile.service import init_profile + + result = init_profile(tmp_path / "profiles.yml") + assert result.success + assert (tmp_path / "profiles.yml").exists() +``` + +### E2E Tests (`tests/e2e/`) + +Full workflow tests with real dbt execution: +- Project initialization +- dbt command passthrough +- Complete user workflows + +```bash +uv run poe test-e2e +``` + +Mark with `@pytest.mark.e2e`: + +```python +import pytest +import subprocess + +@pytest.mark.e2e +def test_dbt_run_passthrough(tmp_path: Path, initialized_project): + result = subprocess.run( + ["brix", "dbt", "-p", str(initialized_project), "debug"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 +``` + +## Running Tests + +### All Tests + +```bash +uv run poe test +``` + +### Specific Category + +```bash +uv run poe test-unit +uv run poe test-integration +uv run poe test-e2e +``` + +### Specific File + +```bash +uv run pytest tests/unit/test_profile_models.py -v +``` + +### Specific Test + +```bash +uv run pytest tests/unit/test_profile_models.py::test_duckdb_output -v +``` + +### With Coverage + +```bash +uv run pytest --cov=brix --cov-report=html +# Open htmlcov/index.html +``` + +### Verbose Output + +```bash +uv run pytest -v --tb=long +``` + +## Fixtures + +### Common Fixtures + +```python +# conftest.py +import pytest +from pathlib import Path + +@pytest.fixture +def tmp_profiles(tmp_path: Path) -> Path: + """Create temporary profiles.yml.""" + profiles_path = tmp_path / "profiles.yml" + profiles_path.write_text(""" +default: + target: dev + outputs: + dev: + type: duckdb + path: ./dev.duckdb +""") + return profiles_path + +@pytest.fixture +def tmp_project(tmp_path: Path) -> Path: + """Create temporary dbt project.""" + project_path = tmp_path / "test_project" + project_path.mkdir() + (project_path / "dbt_project.yml").write_text(""" +name: test_project +version: '1.0.0' +profile: default +""") + return project_path +``` + +### Mocking + +```python +from unittest.mock import patch, MagicMock + +def test_version_check_disabled(): + with patch("brix.version_check.httpx.get") as mock_get: + mock_get.side_effect = Exception("Network error") + # Version check should fail silently + from brix.version_check import check_version + result = check_version() + assert result is None # No crash +``` + +## Test Patterns + +### Testing CLI Commands + +```python +from typer.testing import CliRunner +from brix.main import app + +runner = CliRunner() + +def test_version_flag(): + result = runner.invoke(app, ["--version"]) + assert result.exit_code == 0 + assert "brix" in result.output + +def test_profile_init(tmp_path: Path): + result = runner.invoke(app, [ + "dbt", "profile", "init", + "--profile-path", str(tmp_path / "profiles.yml"), + ]) + assert result.exit_code == 0 +``` + +### Testing Interactive Prompts + +```python +from unittest.mock import patch + +def test_interactive_profile_edit(): + with patch("questionary.select") as mock_select: + mock_select.return_value.ask.return_value = "add-profile" + # Test prompt behavior +``` + +### Testing Pydantic Models + +```python +import pytest +from pydantic import ValidationError + +def test_required_field_validation(): + with pytest.raises(ValidationError): + DatabricksOutput() # Missing required fields + +def test_discriminated_union(): + from pydantic import TypeAdapter + adapter = TypeAdapter(OutputConfig) + + duckdb_data = {"type": "duckdb", "path": "./test.db"} + result = adapter.validate_python(duckdb_data) + assert isinstance(result, DuckDbOutput) +``` + +### Testing File Operations + +```python +@pytest.mark.integration +def test_yaml_roundtrip(tmp_path: Path): + original = DbtProfiles(profiles={"test": {...}}) + yaml_path = tmp_path / "profiles.yml" + + # Write + yaml_path.write_text(original.to_yaml()) + + # Read back + loaded = DbtProfiles.from_yaml(yaml_path.read_text()) + + assert loaded == original +``` + +## Debugging Tests + +### Print Output + +```bash +uv run pytest -v -s # -s shows print statements +``` + +### Stop on First Failure + +```bash +uv run pytest -x +``` + +### Drop into Debugger + +```bash +uv run pytest --pdb +``` + +Or in code: + +```python +def test_something(): + import pdb; pdb.set_trace() + # ... +``` + +### Show Local Variables + +```bash +uv run pytest -l --tb=long +``` + +## CI/CD Integration + +Tests run automatically on: +- Pull requests +- Pushes to main + +GitHub Actions workflow runs: +1. `uv run poe lint` +2. `uv run poe typecheck` +3. `uv run poe test` + +See `.github/workflows/ci.yml` for configuration. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..ebfcb01 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,96 @@ +# Installation + +## Prerequisites + +- Python 3.10 or higher +- pip, uv, or pipx for installation + +## Installation Methods + +### Using pip + +```bash +pip install brix +``` + +### Using uv (Recommended) + +[uv](https://docs.astral.sh/uv/) is a fast Python package manager: + +```bash +# Install as a tool (isolated environment) +uv tool install brix + +# Or add to a project +uv add brix +``` + +### Using pipx + +For isolated installation: + +```bash +pipx install brix +``` + +### From Source + +```bash +git clone https://github.com/Spycner/brix.git +cd brix +uv sync +uv run brix --help +``` + +## Verify Installation + +```bash +brix --version +``` + +You should see output like: + +``` +brix 1.2.0 +``` + +## Shell Completion + +Brix supports shell completion for bash, zsh, and fish. + +### Install Completion + +```bash +brix --install-completion +``` + +### Show Completion Script + +```bash +brix --show-completion +``` + +## Upgrading + +### pip + +```bash +pip install --upgrade brix +``` + +### uv + +```bash +uv tool upgrade brix +``` + +### pipx + +```bash +pipx upgrade brix +``` + +## Next Steps + +- Follow the [Quick Start](quickstart.md) guide to create your first project +- Read the [Commands Overview](../user-guide/commands.md) for all available commands diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 0000000..37988a6 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,106 @@ +# Quick Start + +This guide will help you get started with brix in just a few minutes. + +## Step 1: Initialize a Profile + +Before creating a dbt project, you need a profile configuration. Brix can initialize one for you: + +```bash +brix dbt profile init +``` + +This creates a `profiles.yml` at `~/.dbt/profiles.yml` with a DuckDB configuration for local development. + +!!! tip "Custom Location" + Specify a custom path with `--profile-path`: + ```bash + brix dbt profile init --profile-path ./profiles.yml + ``` + +## Step 2: Create a Project + +Create a new dbt project with the interactive wizard: + +```bash +brix dbt project init +``` + +The wizard will guide you through: + +1. **Project name** - Name for your dbt project +2. **Profile selection** - Which profile to use +3. **Packages** - Add common packages (dbt_utils, elementary, etc.) +4. **Databricks settings** - Materialization and documentation options +5. **Example model** - Generate a sample model to get started + +!!! info "Non-Interactive Mode" + For scripting, use CLI flags: + ```bash + brix dbt project init --project-name my_project --profile default + ``` + +## Step 3: Run dbt Commands + +Brix passes through any dbt command: + +```bash +# Install packages +brix dbt deps + +# Run models +brix dbt run + +# Test models +brix dbt test + +# Generate documentation +brix dbt docs generate +brix dbt docs serve +``` + +## Common Workflows + +### Local Development with DuckDB + +```bash +# Initialize profile with DuckDB +brix dbt profile init + +# Create project +brix dbt project init + +# Run locally +brix dbt run +``` + +### Add a Databricks Connection + +```bash +# Edit profile to add Databricks +brix dbt profile edit + +# Select "Add new output" +# Choose "databricks" adapter +# Configure authentication (OAuth or Personal Access Token) +``` + +### Manage Packages + +```bash +# Add a package from dbt Hub +brix dbt project edit --action add-hub-package + +# Or non-interactively +brix dbt project edit --action add-hub-package --package-name dbt_utils --package-version ">=1.0.0" + +# Install packages +brix dbt deps +``` + +## Next Steps + +- [Commands Overview](../user-guide/commands.md) - Full command reference +- [Profile Management](../user-guide/profiles.md) - Deep dive into profiles +- [Project Management](../user-guide/projects.md) - Advanced project configuration +- [Configuration](../user-guide/configuration.md) - Environment variables and settings diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b351f74 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,58 @@ +# Brix + +**CLI for dbt project and profile management with Databricks focus** + +[![PyPI version](https://badge.fury.io/py/brix.svg)](https://badge.fury.io/py/brix) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +--- + +## What is Brix? + +Brix is a command-line tool that simplifies dbt project and profile management. It wraps the dbt CLI, adding convenience commands while allowing full passthrough to native dbt commands. + +### Key Features + +- **Profile Management** - Initialize, view, and edit `profiles.yml` with interactive or CLI modes +- **Project Scaffolding** - Create new dbt projects with sensible defaults and package management +- **dbt Passthrough** - Run any dbt command through brix (`brix dbt run`, `brix dbt test`, etc.) +- **Multiple Adapters** - Built-in support for DuckDB (local development) and Databricks +- **Interactive & CLI Modes** - Use guided wizards or script with CLI flags + +## Quick Install + +```bash +pip install brix +``` + +Or with [uv](https://docs.astral.sh/uv/): + +```bash +uv tool install brix +``` + +## Quick Start + +```bash +# Initialize a dbt profile +brix dbt profile init + +# Create a new dbt project +brix dbt project init + +# Run dbt commands through brix +brix dbt run +brix dbt test +``` + +## Documentation + +- [Installation](getting-started/installation.md) - Detailed installation instructions +- [Quick Start](getting-started/quickstart.md) - Get up and running in minutes +- [User Guide](user-guide/commands.md) - Complete command reference +- [Developer Guide](developer-guide/architecture.md) - Contribute to brix +- [API Reference](api/index.md) - Python API documentation + +## License + +MIT License - see [LICENSE](https://github.com/Spycner/brix/blob/main/LICENSE) for details. diff --git a/docs/user-guide/commands.md b/docs/user-guide/commands.md new file mode 100644 index 0000000..cfb3e37 --- /dev/null +++ b/docs/user-guide/commands.md @@ -0,0 +1,114 @@ +# Commands Overview + +Brix provides a hierarchical command structure centered around dbt operations. + +## Command Structure + +``` +brix [global-options] dbt [dbt-options] [subcommand] [args] +``` + +## Global Options + +| Option | Short | Description | +|--------|-------|-------------| +| `--version` | `-v` | Show version and exit | +| `--log-level` | | Log level: TRACE, DEBUG, INFO, WARN, ERROR, OFF | +| `--log-path` | | File path for log output | +| `--log-json` | | Enable JSON log format | +| `--install-completion` | | Install shell completion | +| `--show-completion` | | Show shell completion script | +| `--help` | `-h` | Show help message | + +## Command Groups + +### `brix dbt` + +Main command group for dbt operations. + +| Option | Short | Description | +|--------|-------|-------------| +| `--project` | `-p` | Path to dbt project directory (cached for subsequent commands) | + +#### Subcommands + +| Command | Description | +|---------|-------------| +| `profile` | Manage dbt profile configuration | +| `project` | Manage dbt projects | +| *any dbt command* | Passed through to dbt CLI | + +### `brix dbt profile` + +Manage `profiles.yml` configuration. + +| Command | Description | +|---------|-------------| +| `init` | Initialize a dbt profile from template | +| `show` | Show the current profile path and contents | +| `edit` | Edit profile configuration (interactive or CLI) | + +### `brix dbt project` + +Manage dbt projects. + +| Command | Description | +|---------|-------------| +| `init` | Initialize a new dbt project | +| `edit` | Edit project configuration | + +## Quick Reference + +### Profile Commands + +```bash +# Initialize profile +brix dbt profile init +brix dbt profile init --profile-path ./profiles.yml --force + +# View profile +brix dbt profile show + +# Edit profile (interactive) +brix dbt profile edit + +# Edit profile (CLI) +brix dbt profile edit --action add-profile --profile myproj --target dev +brix dbt profile edit --action delete-profile --profile old --force +``` + +### Project Commands + +```bash +# Create project (interactive) +brix dbt project init + +# Create project (CLI) +brix dbt project init -n my_project -p default --materialization table + +# Edit project (interactive) +brix dbt project edit + +# Edit project (CLI) +brix dbt project edit -p ./dbt_project.yml --action set-name --name new_name +brix dbt project edit --action add-hub-package --package dbt_utils +``` + +### dbt Passthrough + +```bash +# Any dbt command works +brix dbt run +brix dbt test +brix dbt build +brix dbt docs generate +brix dbt docs serve +brix dbt seed +brix dbt snapshot +``` + +## Detailed Command Reference + +- [Profile Management](profiles.md) - Complete profile command reference +- [Project Management](projects.md) - Complete project command reference +- [dbt Passthrough](passthrough.md) - Using native dbt commands diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md new file mode 100644 index 0000000..c0632cf --- /dev/null +++ b/docs/user-guide/configuration.md @@ -0,0 +1,153 @@ +# Configuration + +Brix uses environment variables and CLI options for configuration. + +## Configuration Precedence + +1. **CLI arguments** - Highest priority +2. **Environment variables** - Override defaults +3. **Defaults** - Built-in values + +## Environment Variables + +### Profile Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `BRIX_DBT_PROFILE_PATH` | `~/.dbt/profiles.yml` | Path to dbt profiles.yml | + +### Project Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `BRIX_DBT_PROJECT_BASE_DIR` | `.` | Base directory for new projects | + +### Logging Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `BRIX_LOG` | `OFF` | Log level: TRACE, DEBUG, INFO, WARN, ERROR, OFF | +| `BRIX_LOG_PATH` | | File path for log output | +| `BRIX_LOG_JSON` | `false` | Enable JSON log format | + +## CLI Options + +### Global Options + +```bash +brix --log-level DEBUG dbt run +brix --log-path ./brix.log dbt test +brix --log-json dbt build +``` + +### Profile Options + +```bash +brix dbt profile init --profile-path ./profiles.yml +brix dbt profile edit -p ./profiles.yml +``` + +### Project Options + +```bash +brix dbt -p ./my_project run +brix dbt project init --base-dir ./projects +``` + +## Cache Locations + +Brix stores cache files in `~/.cache/brix/`: + +| File | Purpose | +|------|---------| +| `dbt_project_path.json` | Last used project path | +| `version_check.json` | Version check results (24-hour TTL) | + +## Logging + +Brix uses Terraform-style logging with customizable output. + +### Log Levels + +| Level | Description | +|-------|-------------| +| `TRACE` | Most verbose, debugging internals | +| `DEBUG` | Detailed debugging information | +| `INFO` | General operational information | +| `WARN` | Warning messages | +| `ERROR` | Error messages only | +| `OFF` | Disable logging (default) | + +### Examples + +**Console logging:** + +```bash +BRIX_LOG=DEBUG brix dbt run +``` + +**File logging:** + +```bash +BRIX_LOG=INFO BRIX_LOG_PATH=./brix.log brix dbt run +``` + +**JSON logging:** + +```bash +BRIX_LOG=DEBUG BRIX_LOG_JSON=true brix dbt run +``` + +**Via CLI:** + +```bash +brix --log-level DEBUG --log-path ./debug.log dbt run +``` + +## Version Checking + +Brix checks for updates in the background: + +- Runs in a non-blocking background thread +- Results cached for 24 hours +- Shows notification if a newer version is available +- Never blocks command execution + +To disable version checks, the feature fails silently if network is unavailable. + +## Example: Production Setup + +```bash +# .env file +export BRIX_DBT_PROFILE_PATH=/etc/dbt/profiles.yml +export BRIX_DBT_PROJECT_BASE_DIR=/var/dbt/projects +export BRIX_LOG=INFO +export BRIX_LOG_PATH=/var/log/brix/brix.log +export BRIX_LOG_JSON=true +``` + +## Example: Development Setup + +```bash +# Development .env +export BRIX_LOG=DEBUG +export BRIX_DBT_PROFILE_PATH=./profiles.yml +``` + +## Shell Completion + +Enable tab completion for faster command entry: + +```bash +# Install completion for your shell +brix --install-completion + +# View completion script +brix --show-completion +``` + +Supported shells: +- bash +- zsh +- fish +- PowerShell diff --git a/docs/user-guide/passthrough.md b/docs/user-guide/passthrough.md new file mode 100644 index 0000000..1253ce4 --- /dev/null +++ b/docs/user-guide/passthrough.md @@ -0,0 +1,187 @@ +# dbt Passthrough + +Brix transparently passes through any dbt command that isn't a built-in brix command. + +## How It Works + +When you run a command like `brix dbt run`, brix checks if `run` is a built-in subcommand. Since it isn't, brix passes the command directly to the dbt CLI. + +```bash +brix dbt run # Executes: dbt run +brix dbt test # Executes: dbt test +brix dbt build # Executes: dbt build +``` + +## Project Path Caching + +Brix caches the project path for convenience. When you specify a project once, subsequent commands use the same path: + +```bash +# First command sets the project path +brix dbt -p ./my_project run + +# Subsequent commands use the cached path +brix dbt test +brix dbt docs generate +``` + +The cache is stored in `~/.cache/brix/dbt_project_path.json`. + +To use a different project: + +```bash +brix dbt -p ./other_project run +``` + +## Common dbt Commands + +### Model Execution + +```bash +# Run all models +brix dbt run + +# Run specific model +brix dbt run --select my_model + +# Run models with tags +brix dbt run --select tag:daily + +# Full refresh incremental models +brix dbt run --full-refresh +``` + +### Testing + +```bash +# Run all tests +brix dbt test + +# Test specific model +brix dbt test --select my_model + +# Run only data tests +brix dbt test --select test_type:data + +# Run only schema tests +brix dbt test --select test_type:schema +``` + +### Build + +```bash +# Run + test in dependency order +brix dbt build + +# Build specific models +brix dbt build --select staging.* +``` + +### Seeds + +```bash +# Load all seed files +brix dbt seed + +# Load specific seed +brix dbt seed --select countries +``` + +### Snapshots + +```bash +# Run all snapshots +brix dbt snapshot + +# Run specific snapshot +brix dbt snapshot --select orders_snapshot +``` + +### Documentation + +```bash +# Generate docs +brix dbt docs generate + +# Serve docs locally +brix dbt docs serve + +# Serve on specific port +brix dbt docs serve --port 8080 +``` + +### Dependencies + +```bash +# Install packages +brix dbt deps +``` + +### Debugging + +```bash +# Debug configuration +brix dbt debug + +# Show compiled SQL +brix dbt compile --select my_model + +# List resources +brix dbt ls +brix dbt ls --select tag:pii +``` + +### Source Management + +```bash +# Check source freshness +brix dbt source freshness +``` + +## Passing Arguments + +All dbt arguments work as expected: + +```bash +# Multiple selectors +brix dbt run --select model1 model2 + +# Exclude models +brix dbt run --exclude staging.* + +# Set variables +brix dbt run --vars '{"start_date": "2024-01-01"}' + +# Target specific environment +brix dbt run --target prod + +# Thread count +brix dbt run --threads 8 + +# Fail fast +brix dbt run --fail-fast +``` + +## Environment Variables + +dbt environment variables work normally: + +```bash +export DBT_PROFILES_DIR=./custom_profiles +export DBT_TARGET=production + +brix dbt run +``` + +## Limitations + +- The project path cache is per-user, not per-terminal session +- brix-specific options (`--log-level`, etc.) must come before `dbt` + +```bash +# Correct +brix --log-level DEBUG dbt run + +# Also correct +brix dbt -p ./project run --select my_model +``` diff --git a/docs/user-guide/profiles.md b/docs/user-guide/profiles.md new file mode 100644 index 0000000..f6e7381 --- /dev/null +++ b/docs/user-guide/profiles.md @@ -0,0 +1,222 @@ +# Profile Management + +Brix provides commands to initialize, view, and edit dbt profile configurations (`profiles.yml`). + +## Overview + +dbt profiles define connection information for your data warehouses. Brix simplifies profile management with: + +- **Templates** - Quick initialization with sensible defaults +- **Interactive editing** - Menu-driven configuration +- **CLI mode** - Scriptable, non-interactive operations +- **Multiple adapters** - DuckDB for local dev, Databricks for production + +## Commands + +### `brix dbt profile init` + +Initialize a dbt profile from template. + +```bash +brix dbt profile init [OPTIONS] +``` + +#### Options + +| Option | Short | Default | Description | +|--------|-------|---------|-------------| +| `--profile-path` | `-p` | `~/.dbt/profiles.yml` | Path to profiles.yml | +| `--force` | `-f` | | Overwrite existing profile | + +#### Environment Variables + +| Variable | Description | +|----------|-------------| +| `BRIX_DBT_PROFILE_PATH` | Default path for profiles.yml | + +#### Examples + +```bash +# Initialize at default location +brix dbt profile init + +# Initialize at custom location +brix dbt profile init --profile-path ./profiles.yml + +# Overwrite existing +brix dbt profile init --force +``` + +The template includes a DuckDB configuration: + +```yaml +default: + target: dev + outputs: + dev: + type: duckdb + path: ./dev.duckdb +``` + +--- + +### `brix dbt profile show` + +Display the current profile path and contents. + +```bash +brix dbt profile show +``` + +Shows: +- Profile file path +- Full YAML contents + +--- + +### `brix dbt profile edit` + +Edit dbt profile configuration. + +```bash +brix dbt profile edit [OPTIONS] +``` + +#### Options + +| Option | Short | Description | +|--------|-------|-------------| +| `--profile-path` | `-p` | Path to profiles.yml | +| `--action` | `-a` | Action to perform (see below) | +| `--profile` | `-P` | Profile name | +| `--output` | `-o` | Output name | +| `--target` | `-t` | Default target name | +| `--path` | | DuckDB path | +| `--threads` | | Thread count | +| `--force` | `-f` | Skip confirmation for destructive actions | + +#### Actions + +| Action | Description | +|--------|-------------| +| `add-profile` | Add a new profile | +| `edit-profile` | Edit existing profile settings | +| `delete-profile` | Remove a profile | +| `add-output` | Add output to a profile | +| `edit-output` | Edit an existing output | +| `delete-output` | Remove an output | + +#### Interactive Mode + +Without `--action`, launches an interactive menu: + +```bash +brix dbt profile edit +``` + +The menu provides: + +1. Select profile to edit +2. Choose action (add/edit/delete profile or output) +3. Configure settings via prompts + +#### CLI Mode Examples + +```bash +# Add a new profile +brix dbt profile edit --action add-profile --profile myproject --target dev + +# Edit profile target +brix dbt profile edit --action edit-profile --profile default --target prod + +# Delete profile (with confirmation skip) +brix dbt profile edit --action delete-profile --profile old --force + +# Add DuckDB output +brix dbt profile edit --action add-output --profile default --output local \ + --path ./local.duckdb --threads 4 + +# Edit existing output +brix dbt profile edit --action edit-output --profile default --output dev \ + --path ./new.duckdb + +# Delete output +brix dbt profile edit --action delete-output --profile default --output old --force +``` + +## Supported Adapters + +### DuckDB + +For local development and testing. + +```yaml +outputs: + dev: + type: duckdb + path: ./dev.duckdb + threads: 4 + extensions: + - httpfs + - parquet + settings: + memory_limit: 4GB +``` + +Configuration options: +- `path` - Database file path (`:memory:` for in-memory) +- `threads` - Number of threads +- `extensions` - DuckDB extensions to load +- `settings` - DuckDB configuration settings + +### Databricks + +For production workloads on Databricks. + +#### OAuth User-to-Machine (U2M) + +```yaml +outputs: + prod: + type: databricks + host: dbc-abc123.cloud.databricks.com + http_path: /sql/1.0/warehouses/xyz789 + catalog: main + schema: analytics + auth_type: oauth-u2m +``` + +#### OAuth Machine-to-Machine (M2M) + +```yaml +outputs: + prod: + type: databricks + host: dbc-abc123.cloud.databricks.com + http_path: /sql/1.0/warehouses/xyz789 + catalog: main + schema: analytics + auth_type: oauth-m2m + client_id: "{{ env_var('DBT_DATABRICKS_CLIENT_ID') }}" + client_secret: "{{ env_var('DBT_DATABRICKS_CLIENT_SECRET') }}" +``` + +#### Personal Access Token (PAT) + +```yaml +outputs: + prod: + type: databricks + host: dbc-abc123.cloud.databricks.com + http_path: /sql/1.0/warehouses/xyz789 + catalog: main + schema: analytics + token: "{{ env_var('DBT_DATABRICKS_TOKEN') }}" +``` + +## Best Practices + +1. **Use environment variables** for sensitive values like tokens +2. **Keep local dev profiles** using DuckDB for fast iteration +3. **Separate targets** for dev, staging, and production +4. **Use OAuth** when possible for better security on Databricks diff --git a/docs/user-guide/projects.md b/docs/user-guide/projects.md new file mode 100644 index 0000000..f339d51 --- /dev/null +++ b/docs/user-guide/projects.md @@ -0,0 +1,254 @@ +# Project Management + +Brix provides commands to create and edit dbt projects with sensible defaults and package management. + +## Overview + +- **Project scaffolding** - Create complete dbt projects with one command +- **Package management** - Add packages from dbt Hub, Git, or local paths +- **Interactive wizard** - Guided setup for new projects +- **CLI mode** - Fully scriptable project creation and modification + +## Commands + +### `brix dbt project init` + +Initialize a new dbt project with sensible defaults. + +```bash +brix dbt project init [OPTIONS] +``` + +#### Options + +| Option | Short | Default | Description | +|--------|-------|---------|-------------| +| `--project-name` | `-n` | | Name of the dbt project (interactive if not provided) | +| `--base-dir` | `-b` | `.` | Base directory for project | +| `--team` | `-t` | | Team subdirectory (optional) | +| `--profile` | `-p` | | Profile name to use | +| `--profile-path` | | `~/.dbt/profiles.yml` | Path to profiles.yml for validation | +| `--packages` | | | Packages to include (can repeat) | +| `--no-packages` | | | Skip package installation | +| `--materialization` | | | Default: view, table, or ephemeral | +| `--persist-docs` | | | Enable persist_docs for Unity Catalog | +| `--run-deps` | | | Run `dbt deps` after creation | +| `--with-example` | | | Create example model | +| `--force` | `-f` | | Overwrite existing project | + +#### Environment Variables + +| Variable | Description | +|----------|-------------| +| `BRIX_DBT_PROJECT_BASE_DIR` | Default base directory | +| `BRIX_DBT_PROFILE_PATH` | Default profiles.yml path | + +#### Interactive Mode + +Without `--project-name`, launches a wizard: + +```bash +brix dbt project init +``` + +The wizard guides you through: + +1. **Project name** - Validates naming conventions +2. **Team directory** - Optional subdirectory organization +3. **Profile selection** - Lists available profiles +4. **Packages** - Select from common packages or enter custom +5. **Databricks settings** - Materialization and persist_docs +6. **Example model** - Generate a sample model + +#### CLI Mode Examples + +```bash +# Minimal project +brix dbt project init -n my_project + +# Full configuration +brix dbt project init \ + --project-name analytics \ + --base-dir ./projects \ + --team data-engineering \ + --profile production \ + --packages dbt_utils \ + --packages elementary \ + --materialization table \ + --persist-docs \ + --run-deps \ + --with-example + +# Overwrite existing +brix dbt project init -n my_project --force +``` + +#### Generated Structure + +``` +my_project/ +├── dbt_project.yml +├── packages.yml +├── models/ +│ └── example/ +│ ├── example_model.sql +│ └── schema.yml +├── seeds/ +├── macros/ +├── snapshots/ +├── tests/ +└── .gitignore +``` + +--- + +### `brix dbt project edit` + +Edit dbt project configuration. + +```bash +brix dbt project edit [OPTIONS] +``` + +#### Options + +| Option | Short | Description | +|--------|-------|-------------| +| `--project` | `-p` | Path to dbt_project.yml | +| `--action` | `-a` | Action to perform | +| `--name` | | New project name | +| `--profile` | | New profile name | +| `--version` | `-v` | New project version | +| `--require-dbt-version` | | dbt version constraint | +| `--path-field` | | Path field to modify | +| `--path` | | Path value | +| `--create-dir` | | Create directory when adding path | +| `--package` | | Package name | +| `--package-version` | | Package version specifier | +| `--revision` | | Git revision | +| `--subdirectory` | | Subdirectory in git repo | +| `--force` | `-f` | Skip confirmations | + +#### Actions + +**Project Settings:** + +| Action | Description | +|--------|-------------| +| `set-name` | Change project name | +| `set-profile` | Change profile reference | +| `set-version` | Change project version | +| `set-require-dbt-version` | Set dbt version constraint | + +**Path Management:** + +| Action | Description | +|--------|-------------| +| `add-path` | Add to path arrays (model-paths, seed-paths, etc.) | +| `remove-path` | Remove from path arrays | + +**Package Management:** + +| Action | Description | +|--------|-------------| +| `add-hub-package` | Add package from dbt Hub | +| `add-git-package` | Add package from Git repository | +| `add-local-package` | Add local package | +| `remove-package` | Remove a package | +| `update-package-version` | Update package version | + +#### Interactive Mode + +Without `--action`, launches interactive editor: + +```bash +brix dbt project edit +``` + +Features: +- Project discovery (finds dbt_project.yml in current directory tree) +- Menu-driven action selection +- Guided prompts for each action + +#### CLI Mode Examples + +**Project Settings:** + +```bash +# Change project name +brix dbt project edit -p ./dbt_project.yml --action set-name --name new_name + +# Change profile +brix dbt project edit --action set-profile --profile production + +# Set version +brix dbt project edit --action set-version --version 2.0.0 + +# Set dbt version constraint +brix dbt project edit --action set-require-dbt-version --require-dbt-version ">=1.7.0" +``` + +**Path Management:** + +```bash +# Add model path with directory creation +brix dbt project edit --action add-path \ + --path-field model-paths \ + --path staging \ + --create-dir + +# Remove seed path +brix dbt project edit --action remove-path \ + --path-field seed-paths \ + --path old_seeds +``` + +**Package Management:** + +```bash +# Add dbt Hub package +brix dbt project edit --action add-hub-package \ + --package dbt-labs/dbt_utils \ + --package-version ">=1.0.0" + +# Add Git package +brix dbt project edit --action add-git-package \ + --package https://github.com/org/repo.git \ + --revision main \ + --subdirectory dbt + +# Add local package +brix dbt project edit --action add-local-package \ + --package ../shared_macros + +# Remove package +brix dbt project edit --action remove-package \ + --package dbt-labs/dbt_utils \ + --force + +# Update package version +brix dbt project edit --action update-package-version \ + --package dbt-labs/dbt_utils \ + --package-version ">=2.0.0" +``` + +## Package Shortcuts + +When using interactive mode, brix recognizes common package shortcuts: + +| Shortcut | Full Package | +|----------|-------------| +| `dbt_utils` | dbt-labs/dbt_utils | +| `elementary` | elementary-data/elementary | +| `codegen` | dbt-labs/codegen | +| `audit_helper` | dbt-labs/audit_helper | +| `dbt_expectations` | calogica/dbt_expectations | +| `dbt_date` | calogica/dbt_date | + +## Best Practices + +1. **Use semantic versioning** for package versions (`>=1.0.0,<2.0.0`) +2. **Pin to specific versions** in production for reproducibility +3. **Run `dbt deps`** after modifying packages +4. **Use persist_docs** with Databricks Unity Catalog +5. **Organize models** in subdirectories (staging, marts, etc.) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..74a829b --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,73 @@ +site_name: Brix +site_description: CLI for dbt project and profile management +site_url: https://spycner.github.io/brix/ +repo_url: https://github.com/Spycner/brix +repo_name: Spycner/brix + +theme: + name: material + palette: + - scheme: default + primary: deep purple + accent: amber + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: deep purple + accent: amber + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.instant + - navigation.sections + - navigation.expand + - navigation.top + - search.highlight + - content.code.copy + - content.tabs.link + +plugins: + - search + - mkdocstrings: + handlers: + python: + options: + docstring_style: google + show_source: true + show_root_heading: true + members_order: source + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - toc: + permalink: true + +nav: + - Home: index.md + - Getting Started: + - Installation: getting-started/installation.md + - Quick Start: getting-started/quickstart.md + - User Guide: + - Commands Overview: user-guide/commands.md + - Profile Management: user-guide/profiles.md + - Project Management: user-guide/projects.md + - dbt Passthrough: user-guide/passthrough.md + - Configuration: user-guide/configuration.md + - Developer Guide: + - Architecture: developer-guide/architecture.md + - Contributing: developer-guide/contributing.md + - Adding Adapters: developer-guide/adding-adapters.md + - Testing: developer-guide/testing.md + - API Reference: + - Overview: api/index.md + - Modules: api/modules.md + - Models: api/models.md diff --git a/pyproject.toml b/pyproject.toml index 68ff462..fbd3ff6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,16 @@ dependencies = [ "typer>=0.20.0", ] +[project.optional-dependencies] +docs = [ + "mkdocs>=1.6", + "mkdocs-material>=9.5", + "mkdocstrings[python]>=0.27", + "mkdocs-gen-files>=0.5", + "mkdocs-literate-nav>=0.6", + "mkdocs-section-index>=0.3", +] + [project.scripts] brix = "brix.main:app" @@ -120,6 +130,9 @@ test-integration = "pytest tests/integration -m integration" test-e2e = "pytest tests/e2e -m e2e" check = ["lint", "typecheck"] pre-commit = "pre-commit run --all-files" +docs-serve = "mkdocs serve" +docs-build = "mkdocs build" +docs-deploy = "mkdocs gh-deploy" [tool.semantic_release] version_toml = ["pyproject.toml:project.version"] diff --git a/uv.lock b/uv.lock index de5789f..0a5dd5e 100644 --- a/uv.lock +++ b/uv.lock @@ -77,9 +77,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181 }, ] +[[package]] +name = "backrefs" +version = "6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059 }, + { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854 }, + { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770 }, + { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726 }, + { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584 }, + { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058 }, +] + [[package]] name = "brix" -version = "1.1.0" +version = "1.2.0" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -89,6 +103,16 @@ dependencies = [ { name = "typer" }, ] +[package.optional-dependencies] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-gen-files" }, + { name = "mkdocs-literate-nav" }, + { name = "mkdocs-material" }, + { name = "mkdocs-section-index" }, + { name = "mkdocstrings", extra = ["python"] }, +] + [package.dev-dependencies] dev = [ { name = "dbt-core" }, @@ -107,11 +131,18 @@ dev = [ [package.metadata] requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, + { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.6" }, + { name = "mkdocs-gen-files", marker = "extra == 'docs'", specifier = ">=0.5" }, + { name = "mkdocs-literate-nav", marker = "extra == 'docs'", specifier = ">=0.6" }, + { name = "mkdocs-material", marker = "extra == 'docs'", specifier = ">=9.5" }, + { name = "mkdocs-section-index", marker = "extra == 'docs'", specifier = ">=0.3" }, + { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.27" }, { name = "pydantic", specifier = ">=2.12.5" }, { name = "pydantic-settings", specifier = ">=2.12.0" }, { name = "questionary", specifier = ">=2.1.1" }, { name = "typer", specifier = ">=0.20.0" }, ] +provides-extras = ["docs"] [package.metadata.requires-dev] dev = [ @@ -820,6 +851,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054 }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, +] + [[package]] name = "gitdb" version = "4.0.12" @@ -858,6 +901,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/d1/385110a9ae86d91cc14c5282c61fe9f4dc41c0b9f7d423c6ad77038c4448/google_auth-2.43.0-py2.py3-none-any.whl", hash = "sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16", size = 223114 }, ] +[[package]] +name = "griffe" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705 }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1122,6 +1177,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/28/2635a8141c9a4f4bc23f5135a92bbcf48d928d8ca094088c962df1879d64/lz4-4.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:d994b87abaa7a88ceb7a37c90f547b8284ff9da694e6afcfaa8568d739faf3f7", size = 93812 }, ] +[[package]] +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678 }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -1245,6 +1309,171 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034 }, +] + +[[package]] +name = "mkdocs-gen-files" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/35/f26349f7fa18414eb2e25d75a6fa9c7e3186c36e1d227c0b2d785a7bd5c4/mkdocs_gen_files-0.6.0.tar.gz", hash = "sha256:52022dc14dcc0451e05e54a8f5d5e7760351b6701eff816d1e9739577ec5635e", size = 8642 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/ec/72417415563c60ae01b36f0d497f1f4c803972f447ef4fb7f7746d6e07db/mkdocs_gen_files-0.6.0-py3-none-any.whl", hash = "sha256:815af15f3e2dbfda379629c1b95c02c8e6f232edf2a901186ea3b204ab1135b2", size = 8182 }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, +] + +[[package]] +name = "mkdocs-literate-nav" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/5f/99aa379b305cd1c2084d42db3d26f6de0ea9bf2cc1d10ed17f61aff35b9a/mkdocs_literate_nav-0.6.2.tar.gz", hash = "sha256:760e1708aa4be86af81a2b56e82c739d5a8388a0eab1517ecfd8e5aa40810a75", size = 17419 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/84/b5b14d2745e4dd1a90115186284e9ee1b4d0863104011ab46abb7355a1c3/mkdocs_literate_nav-0.6.2-py3-none-any.whl", hash = "sha256:0a6489a26ec7598477b56fa112056a5e3a6c15729f0214bea8a4dbc55bd5f630", size = 13261 }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770 }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, +] + +[[package]] +name = "mkdocs-section-index" +version = "0.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/40/4aa9d3cfa2ac6528b91048847a35f005b97ec293204c02b179762a85b7f2/mkdocs_section_index-0.3.10.tar.gz", hash = "sha256:a82afbda633c82c5568f0e3b008176b9b365bf4bd8b6f919d6eff09ee146b9f8", size = 14446 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/53/76c109e6f822a6d19befb0450c87330b9a6ce52353de6a9dda7892060a1f/mkdocs_section_index-0.3.10-py3-none-any.whl", hash = "sha256:bc27c0d0dc497c0ebaee1fc72839362aed77be7318b5ec0c30628f65918e4776", size = 8796 }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135 }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/0d/dab7b08ca7e5a38b033cd83565bb0f95f05e8f3df7bc273e793c2ad3576e/mkdocstrings_python-2.0.0.tar.gz", hash = "sha256:4d872290f595221740a304bebca5b3afa4beafe84cc6fd27314d52dc3fbb4676", size = 199113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/de/063481352688c3a1468c51c10b6cfb858d5e35dfef8323d9c83c4f2faa03/mkdocstrings_python-2.0.0-py3-none-any.whl", hash = "sha256:1d552dda109d47e4fddecbb1f06f9a86699c1b073e8b166fba89eeef0a0ffec6", size = 104803 }, +] + [[package]] name = "more-itertools" version = "10.8.0" @@ -1540,6 +1769,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, +] + [[package]] name = "pandas" version = "2.2.3" @@ -1935,6 +2173,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, ] +[[package]] +name = "pymdown-extensions" +version = "10.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/6d/af5378dbdb379fddd9a277f8b9888c027db480cde70028669ebd009d642a/pymdown_extensions-10.17.2.tar.gz", hash = "sha256:26bb3d7688e651606260c90fb46409fbda70bf9fdc3623c7868643a1aeee4713", size = 847344 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/78/b93cb80bd673bdc9f6ede63d8eb5b4646366953df15667eb3603be57a2b1/pymdown_extensions-10.17.2-py3-none-any.whl", hash = "sha256:bffae79a2e8b9e44aef0d813583a8fea63457b7a23643a43988055b7b79b4992", size = 266556 }, +] + [[package]] name = "pytest" version = "9.0.1" @@ -2128,6 +2379,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722 }, +] + [[package]] name = "questionary" version = "2.1.1" @@ -2616,6 +2879,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095 }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +] + [[package]] name = "wcwidth" version = "0.2.14" From 0a0f61b810898e95b53cb4352de92e0c8409b663 Mon Sep 17 00:00:00 2001 From: Spycner Date: Fri, 28 Nov 2025 14:20:24 +0100 Subject: [PATCH 2/3] chore: update GitHub Actions workflows for documentation - Removed redundant Python setup step in the docs workflow. - Consolidated dependency installation commands for clarity and efficiency. - Added a new step to install documentation-specific dependencies in the pre-commit workflow. - Enhanced the pre-commit workflow to include a build step for documentation. --- .github/workflows/docs.yml | 7 +++---- .github/workflows/pre-commit.yml | 11 ++++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ab78d50..01ab549 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,11 +17,10 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 - - name: Set up Python - run: uv python install - - name: Install dependencies - run: uv sync --extra docs + run: | + uv sync --extra docs + uv pip install -e . - name: Build and deploy docs run: uv run mkdocs gh-deploy --force diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 02883f0..0b97a6e 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -13,10 +13,15 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 - - name: Set up Python - run: uv python install 3.10 - - name: Install dependencies run: uv sync --dev - uses: pre-commit/action@v3.0.1 + + - name: Install docs dependencies + run: | + uv sync --extra docs + uv pip install -e . + + - name: Build docs + run: uv run mkdocs build --strict From b986ab8b41a0bf517a8ed175b4dcaea3fc6f1223 Mon Sep 17 00:00:00 2001 From: Spycner Date: Fri, 28 Nov 2025 14:26:20 +0100 Subject: [PATCH 3/3] docs: update log format description in BrixFormatter - Changed the log format description in the BrixFormatter class to use backticks for better clarity and emphasis on the format structure. --- src/brix/utils/logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brix/utils/logging.py b/src/brix/utils/logging.py index a98452f..bab25c1 100644 --- a/src/brix/utils/logging.py +++ b/src/brix/utils/logging.py @@ -78,7 +78,7 @@ def normalize_log_level(cls, v: str) -> str: class BrixFormatter(logging.Formatter): """Human-readable log formatter for console output. - Format: [2024-01-15T10:30:45Z] [DEBUG] message + Format: ``[2024-01-15T10:30:45Z] [DEBUG] message`` """ def format(self, record: logging.LogRecord) -> str: