# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a Django REST API for Suno, an AI music generation platform.

## Typechecking Fixes

Whenever fixing a type error, you should always consider whether the types should be changed to conform to the existing usage vs. updating the usage to conform to the type.
Today we should typically lean towards conforming to the usage when there is ambiguity, but both are valid options.

We should use `uv run pyright` to perform typechecking. You can use `uv run pyright --outputjson` to get json output for scripts.
We should maintain an up to date json output in `typechecking_utils/pyright_report.json` for use in other tooling.
We should validate fixes and see that we haven't introduced new type errors after fixes before commiting any code.

It is worth trying to fix type-errors broadly once a class or specific pattern is noticed. eg. if we see a common type error across files, we should first come up with a repeatable plan to fix that, and then run that plan until all the examples of that specific error.
Note: we should not over-generalize errors, so that forward progress can actually be made. If there is ambiguity about approach or class of errors, please ask for help and guidance, especially around scope.

### UUID/String Type Handling

IMPORTANT: Always be mindful of UUID/string type mismatches in this codebase. Many models use UUID primary keys (inheriting from BaseModel), but functions often expect string parameters.

**Common patterns to watch for:**
- Model instances have `.id` fields that are UUID objects
- Database queries and function parameters often expect string IDs
- When passing model IDs to functions expecting strings, use `str(model.id)`
- When working with clip IDs, playlist IDs, user IDs, or other model references, verify the expected type


## Common Development Commands

Note: `python` should never be run directly, always use `uv run ...` instead to ensure we get an appropriate environment.

### Development Server

```bash
uv run python manage.py runserver  # Start development server on port 8000
```

### Database Operations

```bash
uv run python manage.py migrate                    # Run migrations
uv run python manage.py makemigrations            # Create new migrations
uv run rebase_migration bots                      # Resolve migration conflicts
```

### Dependencies

```bash
uv add <package>==version           # Add new dependency
uv lock                            # Regenerate lockfile
```

### Testing

```bash
uv run pytest                      # Run all tests
uv run pytest -m "not slow"       # Skip slow tests
uv run pytest --cov --cov-report=html  # Generate coverage report
```

### Code Quality

```bash
uv run ruff format . --config ./pyproject.toml    # Format code
uv run ruff check --config ./pyproject.toml --fix .  # Fix linting issues
uv run pyright                     # Type checking
```

Ruff linting should be run before commiting any changed Python code to ensure we do not hit linting issues in CI using the above commands.

### Schema Management

If any changes

```bash
uv run manage.py schema_codegen
```

## Code Editing Best Practices

### Adding Python Imports

When adding new Python imports, always use a single `Edit` call that includes both the import statement AND its usage in the code. Never make separate edits for the import and usage, as the linter may remove "unused" imports between edits.

**Example - Correct approach:**
```python
# Single Edit call that includes both import and usage
from typing import cast

def some_function():
    user = User.objects.create(username="test")
    return cast(UserWithDiscordInfo, user)
```

**Example - Incorrect approach:**
```python
# ❌ First Edit: Add import only
from typing import cast

# ❌ Second Edit: Add usage separately
def some_function():
    return cast(UserWithDiscordInfo, user)
# This fails because linter may remove the "unused" import between edits
```

### API Response Types

**IMPORTANT**: Always wrap Ninja API endpoint response schemas with `APIResponse` to ensure error responses (403, 404, 422, 429) are properly documented in the OpenAPI schema. This enables proper TypeScript typing in the frontend.

**Example - Correct approach:**
```python
from studio_api.bots.specs_and_schemas import APIResponse

@router.put("/contest/{contest_id}/winners_playlist", auth=AuthBearer(), response=APIResponse(ContestSchema))
def set_winners_playlist(request: AuthedHttpRequest, contest_id: str, spec: SetWinnersPlaylistSpec) -> ContestSchema:
    # ... implementation
```

**Example - Incorrect approach:**
```python
# ❌ Missing APIResponse wrapper - error responses won't be in OpenAPI schema
@router.put("/contest/{contest_id}/winners_playlist", auth=AuthBearer(), response=ContestSchema)
def set_winners_playlist(request: AuthedHttpRequest, contest_id: str, spec: SetWinnersPlaylistSpec) -> ContestSchema:
    # ... implementation
    # This causes TypeScript error types to be 'never' in the frontend
```

**Why this matters:**
- `APIResponse` automatically includes standard error responses (403, 404, 422, 429) in the OpenAPI schema
- Without it, the frontend TypeScript types will show `error` as `never`, making error handling impossible
- This ensures the API contract is complete and frontend developers can properly handle errors

## Development Workflow

### Migration Management

- Use `django-linear-migrations` for conflict prevention
- Migration conflicts resolved via `uv run rebase_migration bots`
- Check `max_migration.txt` for latest migration numbers

### Testing Strategy

- Mark slow tests with `@pytest.mark.slow` decorator
- Use `pytest-django` for Django-specific testing
- Coverage reports available via `pytest --cov`
- Environment variables loaded via `pytest-env`

#### Test Data Patterns

Use shared helpers from `setup_billing_data.py` for consistency:

```python
from studio_api.bots.billing.tests.setup_billing_data import setup_usage_plans_from_json

@pytest.fixture(autouse=True)
def setup(self, db):
    usage_plans = setup_usage_plans_from_json()
    self.premier_plan = usage_plans["premier"]
```

#### Type Casting in Tests

Cast manually created User objects to `UserWithDiscordInfo` for type safety:

```python
from typing import cast
from studio_api.types import UserWithDiscordInfo

def _create_user_with_discord_info(username: str, email: str = "") -> UserWithDiscordInfo:
    user = User.objects.create(username=username, email=email)
    DiscordInfo.objects.create(user=user, handle=username)
    return cast(UserWithDiscordInfo, user)
```

## Commit Guidelines

- Never include Claude signatures, "Generated with Claude Code", or "Co-Authored-By: Claude" in commit messages
- Use standard commit messages without any AI attribution
