Imported from andrewmarconi/PyNuxtBase (
AGENTS.md). Install upstream withnpx skills add andrewmarconi/PyNuxtBase. Copyright stays with the author.
PyNuxtBase Development Guidelines
This file provides guidance for AI agents working on the PyNuxtBase project.
Tech Stack
- Backend: Python 3.12, Django 6.x ORM, FastAPI 0.115+, PostgreSQL 17
- Frontend: Nuxt 4.x, Vue 3, TypeScript 5.7+, Tailwind CSS 4.x
- Package Managers: uv (Python), npm (frontend)
Build, Lint, and Test Commands
Backend (cd src/backend)
# Development server - USE uv run
uv run uvicorn main:app --reload
# Run with custom port
uv run uvicorn main:app --reload --port 8000
# Django commands - USE uv run
uv run python manage.py makemigrations
uv run python manage.py migrate
uv run python manage.py createsuperuser
uv run python manage.py showmigrations
# Formatting and linting - USE uvx for tools
uvx black . --line-length 88
uvx flake8 . --max-line-length 88
uvx isort . --profile black
uvx mypy . --strict
# Testing with pytest - USE uv run
uv run pytest # Run all tests
uv run pytest path/to/test.py # Run specific test file
uv run pytest path/to/test.py::test_func # Run single test function
uv run pytest -v # Verbose output
uv run pytest --cov # With coverage
uv run pytest --cov=src/backend --cov-report=html # Coverage report
# All checks (pre-commit style)
uvx black . --check && uvx isort . --check && uvx flake8 . && uvx mypy .
Frontend (cd src/frontend)
# Development server
npm run dev
# Build and quality
npm run build # Production build
npm run typecheck # Vue/Nuxt type checking
npm run lint # ESLint check
npm run lint:fix # Auto-fix ESLint issues
# Run single test file
npm run test -- test/file.spec.ts
Code Style Guidelines
Python (Backend)
Type Hints: Required on ALL functions, variables, and class attributes
def process_user(user_id: str, include_metadata: bool = False) -> User | None:
"""Process a user by ID."""
result: dict[str, Any] = {}
return None
Import Order: stdlib → third-party → local (use isort with black profile)
# stdlib
from datetime import datetime, timedelta, timezone
from typing import TypedDict
# third-party
import django
from asgiref.sync import sync_to_async
from django.conf import settings
from fastapi import APIRouter, Depends, HTTPException
# local
from app.api.schemas.auth import UserResponse
from app.api.services.token_service import create_access_token
from app.django_app.apps.users.models import User
Naming Conventions:
- Variables/functions:
snake_case - Classes:
PascalCase - Constants:
UPPER_CASE - Private methods:
_leading_underscore - Private attributes: `dunder_attrib
String Quotes: Double quotes for strings, f-strings for interpolation
message: str = "User registered successfully"
full_name: str = f"{user.first_name} {user.last_name}"
Error Handling: Catch specific exceptions, use HTTPException for APIs
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
raise HTTPException(status_code=404, detail="User not found")
# Avoid bare except - always catch specific exceptions
try:
result = process_data(data)
except (ValueError, TypeError) as e:
raise HTTPException(status_code=400, detail=str(e))
Docstrings: Google-style for all public functions and classes
def create_user(email: str, username: str, password: str) -> User:
"""Create a new user account.
Args:
email: User's email address (will be lowercased)
username: Unique username identifier
password: Plain text password (will be hashed)
Returns:
Newly created User instance
Raises:
ValueError: If email or username already exists
"""
TypeScript/Vue (Frontend)
Strict Mode: All TypeScript strict flags enabled - no any without reason
Component Format:
<template>
<div class="component">...</div>
</template>
<script setup lang="ts">
// imports, props, composables - NEVER manually import ref, onMounted, etc.
</script>
Naming:
- Variables/functions:
camelCase - Components:
PascalCase - Constants:
UPPER_CASE - Props:
camelCasewith type annotations
Auto-imports: Never manually import Nuxt composables
- Available automatically:
ref,computed,onMounted,useRoute,useRouter, etc. - Must import explicitly: composables from
~/composables/, API utilities
Style: Tailwind CSS utility classes only, no custom CSS unless necessary
<template>
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow">
<h1 class="text-xl font-semibold text-gray-900">{{ title }}</h1>
</div>
</template>
API Calls: Wrap in composables under composables/api/
// composables/api/useUsers.ts
export function useUsers() {
const { data, error, execute } = useFetch('/api/v1/users')
async function fetchUsers(): Promise<User[]> {
await execute()
return data.value || []
}
return { users: readonly(data), error, fetchUsers }
}
Database Design
Primary Keys: UUIDs only (no auto-increment integers)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Timestamps: Use created_at and updated_at
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Indexes: Add on frequently queried fields
class Meta:
indexes = [
models.Index(fields=['email'], name='idx_user_email'),
models.Index(fields=['created_at'], name='idx_user_created'),
]
API Design
Versioning: All endpoints under /api/v1/
POST /api/v1/auth/login
GET /api/v1/users/me
PUT /api/v1/users/me/profile
Response Format: Consistent Pydantic schemas
class UserResponse(BaseModel):
id: UUID
email: EmailStr
username: str
name: str
created_at: datetime
Error Responses:
raise HTTPException(
status_code=400,
detail="Invalid email or password"
)
Async: All FastAPI endpoints use async/await
@router.get("/users")
async def list_users() -> list[UserResponse]:
users = await get_users_async()
return users
Security Guidelines
Token Handling:
- Password reset tokens: bcrypt hash stored, plaintext in URL
- JWT tokens: HS256 algorithm, type claims ('access' vs 'refresh')
- Never log or expose plaintext tokens
Passwords:
- Never store plaintext passwords
- Use pbkdf2_sha256 for user passwords
- Use bcrypt for reset/verification tokens
Rate Limiting:
- Track by email and IP address
- Login: 5 attempts per email, 10 per IP per 15 minutes
- Password reset: 3 requests per hour per email
Git Workflow
Commits: Conventional Commits format
feat: add password reset email template
fix: resolve token verification timing issue
docs: update API documentation
refactor: simplify auth middleware logic
test: add unit tests for email service
chore: update dependencies
Branching: feature/xxx, fix/xxx, docs/xxx
File Locations
| Type | Location |
|---|---|
| Backend API routers | src/backend/app/api/routers/ |
| Backend schemas | src/backend/app/api/schemas/ |
| Backend services | src/backend/app/api/services/ |
| Django models | src/backend/app/django_app/apps/*/models.py |
| Django admin | src/backend/app/django_app/apps/*/admin.py |
| Email templates | src/backend/app/templates/emails/ |
| Frontend composables | src/frontend/app/composables/ |
| Frontend API wrappers | src/frontend/app/composables/api/ |
| Frontend components | src/frontend/app/components/ |
| Frontend pages | src/frontend/app/pages/ |
| Frontend middleware | src/frontend/app/middleware/ |
| Frontend stores | src/frontend/app/store/ |
| Environment config | .env at repository root |
Key Principles
- Use latest stable versions from official sources
- UUID primary keys for all database models
- JWT authentication (15-min access, 7-day refresh tokens)
- Type safety first (strict typing in Python and TypeScript)
- Tailwind CSS-only configuration (no tailwind.config.ts)
- TDD not required, but maintain 70%+ code coverage
- Security: never expose secrets, use constant-time comparisons