# DLT Connectors Style Guide

Coding style and conventions for dlt connectors.

---

## Core Principles

Follow the Zen of Python:

- **Simple is better than complex** - Prefer straightforward solutions
- **Flat is better than nested** - Minimize nesting levels
- **Explicit is better than implicit** - Be clear about intentions
- **Readability counts** - Code is read more often than written

---

## Functions Over Classes

Use functions, not classes, unless there is a compelling reason for statefulness.

**Good - Simple functions:**

```python
def init_facebook_api(app_id: str, access_token: str) -> None:
    """Initialize the Facebook Ads API."""
    FacebookAdsApi.init(app_id=app_id, access_token=access_token)


def get_insights(account_id: str, fields: list[str]) -> Iterator[dict]:
    """Fetch insights for an account."""
    ...
```

Only use classes when you need complex state that persists across multiple method calls, implementation of a protocol/interface required by a library, or true object-oriented patterns.

---

## Type Hints

Use modern type hint syntax (Python 3.10+, PEP 604, PEP 585):

```python
# Good - Modern syntax
def process(items: list[str], config: dict[str, int] | None = None) -> list[dict]:
    ...
```

- Use `list[T]` instead of `List[T]`
- Use `dict[K, V]` instead of `Dict[K, V]`
- Use `T | None` instead of `Optional[T]`
- Always type function signatures (parameters and return types)
- Use `-> None` for functions that don't return a value

---

## Naming Conventions

- **Functions and variables**: `snake_case`
- **Constants**: `UPPER_SNAKE_CASE`
- **Private functions**: prefix with `_`
- **Files and modules**: `snake_case`
- **Main entry point**: `<connector_name>_pipeline.py`
- **Source module**: `<connector_name>/__init__.py`
- **Helpers**: `<connector_name>/helpers/<module>.py`

---

## Logging

Use the `logging` module with `%s` style formatting (lazy evaluation, more efficient than f-strings for logging):

```python
import logging

logger = logging.getLogger(__name__)

# Good - %s style (lazy evaluation)
logger.info("Fetching data for account %s from %s to %s", account_id, start, end)

# Bad - f-string (always evaluated)
logger.info(f"Fetching data for account {account_id} from {start} to {end}")
```

Use f-strings for non-logging string formatting.

---

## Collections and Iteration

- Prefer comprehensions over loops for simple transformations
- Use sets for membership testing (O(1) lookup)
- Use generators for large data (memory efficient)

```python
# Good - Generator for large data
def get_insights(...) -> Iterator[dict]:
    for record in results:
        yield record
```

---

## Function Design

- Each function should do one thing well
- Use early returns over nested conditionals
- If a function has more than 5-6 parameters, consider grouping into a dataclass or TypedDict

---

## Docstrings

Use Google-style docstrings for all public functions:

```python
def get_insights(
    account_id: str,
    fields: list[str],
    data_from: date,
    data_until: date,
) -> Iterator[dict]:
    """
    Fetch ad insights for an account.

    Handles async job pattern and chunks requests into 90-day windows.

    Args:
        account_id: Ad Account ID (with or without 'act_' prefix)
        fields: List of insight fields to fetch
        data_from: Start date (inclusive)
        data_until: End date (inclusive)

    Yields:
        Insight records as dictionaries

    Raises:
        RuntimeError: If the async job fails
    """
```

---

## Imports

Order (PEP 8):

1. Standard library imports
2. Third-party imports
3. Local imports

Separate each group with a blank line. Avoid star imports.

---

## Error Handling

Use specific exceptions with context:

```python
# Good
raise ValueError("account_ids required: provide as parameter or set DLT_FACEBOOK_ACCOUNT_IDS")

# Bad
raise Exception("Missing account IDs")
```

---

## Formatting

Format all code with `black`. Maximum line length 100 characters.

---

## File Responsibilities

| File | Contains | Does NOT Contain |
|------|----------|------------------|
| `settings.py` | Environment variable parsing, constants | Business logic |
| `helpers/api.py` | API interaction, data fetching | dlt decorators |
| `__init__.py` | `@dlt.source`, `@dlt.resource` | Direct API calls |
| `*_pipeline.py` | Pipeline initialization, `run()` | Business logic |

---

## Git Workflow

- **Conventional commits**: `feat(name):`, `fix(name):`, `docs(name):`, `chore:`
- **Branch**: `feat/<connector_name>` or `fix/<connector_name>-<issue>`
- **Never commit**: `.dlt/secrets.toml`, `.env`, `*.duckdb`, `__pycache__/`
- **Always commit**: Pipeline code, source modules, Dockerfile, pyproject.toml, README.md, `.dlt/secrets_example.toml`, `.env.example`
