Documentation Index
Fetch the complete documentation index at: https://docs.fkapi.sunr4y.dev/llms.txt
Use this file to discover all available pages before exploring further.
Overview
FKApi uses Ruff for linting and formatting Python code. Ruff is a fast Python linter and formatter that combines the functionality of multiple tools (Flake8, isort, Black, etc.) into one.
Quick Reference
# Check code style
ruff check .
# Auto-fix issues
ruff check --fix .
# Format code
ruff format .
# Run all checks and formatting
ruff check --fix . && ruff format .
Ruff Configuration
Basic Settings
Configuration is defined in pyproject.toml:
[tool.ruff]
# Exclude common directories
exclude = [
".bzr", ".direnv", ".eggs", ".git", ".hg",
".mypy_cache", ".pytest_cache", ".ruff_cache",
".venv", "venv", "__pycache__",
"build", "dist", "node_modules",
"*/migrations/*.py", # Exclude Django migrations
]
# Line length (generous for readability)
line-length = 120
# Target Python version
target-version = "py310"
Linting Rules
[tool.ruff.lint]
# Enabled rule sets
select = [
"F", # Pyflakes (basic errors)
"E", # pycodestyle errors
"W", # pycodestyle warnings
"I", # isort (import sorting)
"N", # pep8-naming
"DJ", # Django-specific rules
"B", # flake8-bugbear (common bugs)
"C4", # flake8-comprehensions
"UP", # pyupgrade (modern Python syntax)
]
# Ignored rules
ignore = [
"E501", # line too long (handled by formatter)
"DJ001", # Model __str__ not required always
"DJ003", # __unicode__ not needed (Python 3)
"DJ006", # Meta class not always needed
"DJ008", # __repr__ not always needed
"B905", # zip() strict= not in Python 3.10
"N801", # Allow Type_KAdmin naming convention
]
# Allow auto-fixes for all enabled rules
fixable = ["ALL"]
unfixable = []
# Allow unused variables when prefixed with underscore
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
Import Sorting
[tool.ruff.lint.isort]
known-first-party = ["fkapi", "core"]
This ensures imports are sorted correctly:
# Standard library imports
import os
import sys
from typing import Optional
# Third-party imports
import django
from django.db import models
from rest_framework import serializers
# Local imports
from core.models import Kit
from fkapi.settings import DEBUG
[tool.ruff.format]
# Use double quotes (like Black)
quote-style = "double"
# Use spaces for indentation
indent-style = "space"
# Respect magic trailing commas
skip-magic-trailing-comma = false
# Auto-detect line endings
line-ending = "auto"
Code Style Guidelines
Line Length
- Maximum: 120 characters
- Recommended: 80-100 characters for readability
-Formatter will handle line breaking automatically
Naming Conventions
# Classes: PascalCase
class KitService:
pass
# Functions and methods: snake_case
def get_kit_by_slug(slug: str):
pass
# Constants: UPPER_SNAKE_CASE
MAX_KITS_PER_PAGE = 30
DEFAULT_CACHE_TIMEOUT = 3600
# Private methods: leading underscore
def _internal_helper():
pass
# Variables: snake_case
kit_count = 0
user_id = 123
Type Hints
Use type hints for function parameters and return values:
from typing import Optional, List, Dict
from core.models import Kit
def get_kits_by_club(club_slug: str, limit: int = 10) -> List[Kit]:
"""Retrieve kits for a specific club."""
return Kit.objects.filter(team__slug=club_slug)[:limit]
def parse_kit_data(html: str) -> Optional[Dict[str, str]]:
"""Parse kit data from HTML."""
if not html:
return None
return {"name": "Kit Name", "slug": "kit-slug"}
Docstrings
Use clear, concise docstrings:
def scrape_kit(slug: str, force: bool = False) -> Optional[Kit]:
"""
Scrape kit data from the source website.
Args:
slug: The kit slug to scrape
force: Force re-scraping even if kit exists
Returns:
Kit object if successful, None otherwise
Raises:
ScrapingError: If scraping fails after retries
"""
pass
Import Organization
# 1. Standard library
import os
import sys
from datetime import datetime
from typing import Optional
# 2. Third-party packages
import requests
from bs4 import BeautifulSoup
from django.db import models
from ninja import Router
# 3. Local imports
from core.models import Kit, Club
from core.services import KitsService
from fkapi.settings import DEBUG
Prefer f-strings for string formatting:
# Good
name = "Arsenal"
message = f"Welcome to {name} kits"
# Avoid (unless necessary)
message = "Welcome to {} kits".format(name)
message = "Welcome to %s kits" % name
Error Handling
# Specific exceptions
try:
kit = Kit.objects.get(slug=slug)
except Kit.DoesNotExist:
logger.error(f"Kit not found: {slug}")
return None
except Exception as e:
logger.exception(f"Unexpected error: {e}")
raise
# Context managers for resources
with open("file.txt", "r") as f:
content = f.read()
Django-Specific Guidelines
# Model definition
class Kit(models.Model):
"""Football kit model."""
slug = models.SlugField(unique=True, max_length=255)
name = models.CharField(max_length=255)
team = models.ForeignKey(Club, on_delete=models.CASCADE)
class Meta:
ordering = ["-created_at"]
verbose_name = "Kit"
verbose_name_plural = "Kits"
def __str__(self) -> str:
return self.name
# QuerySet optimization
# Good: Use select_related for foreign keys
kits = Kit.objects.select_related("team", "season").all()
# Good: Use prefetch_related for many-to-many
clubs = Club.objects.prefetch_related("competitions").all()
Type Checking (Optional)
Mypy Configuration
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
check_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
strict_equality = true
show_error_codes = true
# Ignore missing imports for third-party packages
[[tool.mypy.overrides]]
module = [
"django.*",
"ninja.*",
"bs4.*",
"requests.*",
]
ignore_missing_imports = true
Running Mypy
# Check types
mypy fkapi/core
# Check specific file
mypy fkapi/core/models.py
Pre-commit Hooks
Configuration
Pre-commit hooks are defined in .pre-commit-config.yaml:
repos:
# Ruff linter and formatter
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
# General checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-json
- id: check-ast
- id: debug-statements
- id: mixed-line-ending
# Django checks
- repo: local
hooks:
- id: django-check
name: Django Check
entry: bash -c 'cd fkapi && python manage.py check'
language: system
pass_filenames: false
always_run: true
Installation
# Install pre-commit
pip install pre-commit
# Install hooks
pre-commit install
# Run manually on all files
pre-commit run --all-files
Code Review Checklist
Before submitting code:
IDE Integration
VS Code
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
}
},
"ruff.lint.args": ["--config=pyproject.toml"]
}
PyCharm
- Install Ruff plugin
- Configure as external tool:
- Program:
ruff
- Arguments:
check --fix $FilePath$
- Configure file watcher for auto-formatting
Common Issues
Line Too Long
# Bad
kit = Kit.objects.filter(team__slug="arsenal", season__name="2024-25", type__category="home").select_related("team", "season").first()
# Good
kit = (
Kit.objects
.filter(team__slug="arsenal", season__name="2024-25", type__category="home")
.select_related("team", "season")
.first()
)
Unused Imports
# Bad
from core.models import Kit, Club, Season # Season unused
# Good
from core.models import Kit, Club
Import Sorting
Ruff will automatically sort imports. Run:
Additional Resources
Consistent code style makes collaboration easier!