Code Style Guide¶
Axon follows strict code quality standards to ensure maintainability, readability, and consistency.
Overview¶
- Formatter: Black (100-character line length)
- Linter: Ruff (fast Python linter)
- Type Checker: mypy (strict mode)
- Docstrings: Google style
- Import Order: isort-compatible
Code Formatting¶
Black¶
Axon uses Black with 100-character line length.
Configuration:
Usage:
# Format all files
black src/ tests/
# Check without modifying
black --check src/ tests/
# Show diff
black --diff src/ tests/
Example:
# ✅ Good - Black formatted
def long_function_name(
parameter_one: str,
parameter_two: int,
parameter_three: list[str],
) -> dict[str, Any]:
"""Properly formatted function."""
return {
"key1": parameter_one,
"key2": parameter_two,
"key3": parameter_three,
}
Linting¶
Ruff¶
Fast Python linter replacing Flake8, isort, and more.
Configuration:
# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py39"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"C4", # flake8-comprehensions
]
Usage:
# Check all files
ruff check src/ tests/
# Fix auto-fixable issues
ruff check --fix src/ tests/
# Show all violations
ruff check --output-format=full src/
Type Hints¶
mypy¶
Strict type checking with mypy.
Configuration:
# pyproject.toml
[tool.mypy]
python_version = "3.9"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Required Type Hints:
# ✅ Good - Full type hints
from typing import List, Dict, Optional
def process_entries(
entries: List[MemoryEntry],
filter: Optional[MemoryFilter] = None
) -> Dict[str, Any]:
"""Process memory entries."""
result: Dict[str, Any] = {}
return result
# ❌ Bad - Missing type hints
def process_entries(entries, filter=None):
result = {}
return result
Usage:
Naming Conventions¶
Files¶
Use snake_case for file names:
✅ memory_system.py
✅ redis_adapter.py
✅ policy_engine.py
❌ MemorySystem.py
❌ RedisAdapter.py
❌ PolicyEngine.py
Classes¶
Use PascalCase for class names:
# ✅ Good
class MemorySystem:
pass
class RedisAdapter:
pass
class PolicyEngine:
pass
# ❌ Bad
class memory_system:
pass
class redisAdapter:
pass
Functions & Methods¶
Use snake_case for functions and methods:
# ✅ Good
def store_memory():
pass
def get_entry_by_id():
pass
async def recall_memories():
pass
# ❌ Bad
def storeMemory():
pass
def GetEntryById():
pass
Variables¶
Use snake_case for variables:
# ✅ Good
user_name = "John"
entry_count = 10
memory_tier = MemoryTier.PERSISTENT
# ❌ Bad
userName = "John"
entryCount = 10
MemoryTier = MemoryTier.PERSISTENT
Constants¶
Use UPPER_SNAKE_CASE for constants:
# ✅ Good
MAX_BATCH_SIZE = 1000
DEFAULT_TIMEOUT = 30
API_VERSION = "v1"
# ❌ Bad
max_batch_size = 1000
defaultTimeout = 30
apiVersion = "v1"
Private Members¶
Use _leading_underscore for private:
class MemorySystem:
def __init__(self):
# ✅ Good - private attributes
self._adapter = None
self._config = None
def _internal_method(self):
"""Private method."""
pass
# ✅ Good - public interface
def store(self, content: str):
"""Public method."""
pass
Docstrings¶
Google Style¶
Axon uses Google-style docstrings.
Module Docstring:
"""Memory system core implementation.
This module provides the main MemorySystem class which orchestrates
multi-tier memory storage, retrieval, and lifecycle management.
"""
Class Docstring:
class MemorySystem:
"""Unified memory system for LLM applications.
Provides multi-tier memory storage with automatic routing,
policy-based lifecycle management, and semantic search.
Attributes:
config: Memory configuration
embedder: Text embedding provider
Example:
```python
memory = MemorySystem()
entry_id = await memory.store("Important note")
results = await memory.search("note", k=5)
```
"""
Function Docstring:
async def store(
self,
content: str,
tier: MemoryTier | None = None,
metadata: dict[str, Any] | None = None
) -> str:
"""Store a memory entry.
Args:
content: Text content to store
tier: Target tier (auto-selected if None)
metadata: Optional metadata dict
Returns:
Entry ID (UUID string)
Raises:
ValueError: If content is empty
RuntimeError: If storage fails
Example:
```python
entry_id = await memory.store(
"User prefers dark mode",
tier=MemoryTier.PERSISTENT,
metadata={"user_id": "user123"}
)
```
"""
Import Organization¶
Import Order¶
- Standard library
- Third-party packages
- Local imports
Example:
# Standard library
import asyncio
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional
# Third-party
import pytest
from pydantic import BaseModel, Field
# Local
from axon.adapters import StorageAdapter
from axon.core.config import MemoryConfig
from axon.models.entry import MemoryEntry
Absolute vs Relative¶
Use absolute imports:
# ✅ Good - absolute
from axon.core.memory_system import MemorySystem
from axon.adapters.redis import RedisAdapter
# ❌ Bad - relative
from ..core.memory_system import MemorySystem
from .redis import RedisAdapter
Code Organization¶
File Structure¶
"""Module docstring."""
# Imports
import asyncio
from typing import Any
# Constants
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
# Classes
class MyClass:
"""Class docstring."""
pass
# Functions
def my_function():
"""Function docstring."""
pass
# Main execution
if __name__ == "__main__":
main()
Method Order in Classes¶
__init__and special methods- Class methods (
@classmethod) - Static methods (
@staticmethod) - Properties (
@property) - Public methods
- Private methods (
_method)
Example:
class MemorySystem:
def __init__(self, config: MemoryConfig):
"""Initialize."""
self.config = config
def __repr__(self) -> str:
"""String representation."""
return f"MemorySystem(config={self.config})"
@classmethod
def from_config(cls, path: str):
"""Create from config file."""
pass
@staticmethod
def validate_config(config: MemoryConfig):
"""Validate configuration."""
pass
@property
def tier_count(self) -> int:
"""Number of configured tiers."""
return len(self.config.tiers)
async def store(self, content: str):
"""Public API method."""
pass
async def _store_to_tier(self, entry, tier):
"""Private helper method."""
pass
Error Handling¶
Exception Style¶
# ✅ Good - specific exceptions
if not content:
raise ValueError("Content cannot be empty")
if not isinstance(tier, MemoryTier):
raise TypeError(f"Expected MemoryTier, got {type(tier)}")
# ❌ Bad - generic exceptions
if not content:
raise Exception("Invalid content")
Try-Except¶
# ✅ Good - specific exception handling
try:
result = await adapter.store(entry)
except ConnectionError as e:
logger.error(f"Connection failed: {e}")
raise
except TimeoutError as e:
logger.warning(f"Operation timed out: {e}")
return None
# ❌ Bad - bare except
try:
result = await adapter.store(entry)
except:
pass
Comments¶
When to Comment¶
# ✅ Good - explains WHY
# Use exponential backoff to avoid overwhelming the API
await asyncio.sleep(2 ** retry_count)
# ✅ Good - explains complex logic
# Convert tier hierarchy to priority queue (ephemeral=1, session=2, persistent=3)
priority = {"ephemeral": 1, "session": 2, "persistent": 3}[tier]
# ❌ Bad - obvious comment
# Increment counter by 1
counter += 1
# ❌ Bad - outdated comment
# TODO: Implement caching (already implemented)
Docstrings vs Comments¶
- Docstrings: Public API documentation
- Comments: Implementation details
def complex_algorithm(data: list[int]) -> int:
"""Calculate optimized score.
Args:
data: List of integers
Returns:
Optimized score value
"""
# Use binary search for efficiency (O(log n) instead of O(n))
result = binary_search(data)
return result
Best Practices¶
1. Single Responsibility¶
# ✅ Good - single responsibility
def validate_content(content: str) -> bool:
"""Validate content is non-empty."""
return bool(content and content.strip())
def store_to_adapter(entry: MemoryEntry, adapter: StorageAdapter) -> str:
"""Store entry to adapter."""
return adapter.store(entry)
# ❌ Bad - multiple responsibilities
def validate_and_store(content: str, adapter: StorageAdapter):
"""Validate and store content."""
if not content:
raise ValueError("Invalid")
return adapter.store(content)
2. Explicit is Better Than Implicit¶
# ✅ Good - explicit
from axon.models import MemoryTier
tier = MemoryTier.PERSISTENT
# ❌ Bad - implicit
import axon
tier = axon.models.MemoryTier.PERSISTENT
3. Avoid Magic Numbers¶
# ✅ Good - named constants
MAX_BATCH_SIZE = 1000
DEFAULT_K = 10
if len(batch) > MAX_BATCH_SIZE:
raise ValueError(f"Batch too large: {len(batch)} > {MAX_BATCH_SIZE}")
# ❌ Bad - magic numbers
if len(batch) > 1000:
raise ValueError("Batch too large")
4. Use Type Aliases¶
# ✅ Good - readable type aliases
from typing import TypeAlias
EntryID: TypeAlias = str
Metadata: TypeAlias = dict[str, Any]
Embeddings: TypeAlias = list[float]
def store(content: str, metadata: Metadata) -> EntryID:
pass
# ❌ Bad - complex inline types
def store(content: str, metadata: dict[str, Any]) -> str:
pass
Pre-commit Checklist¶
Before committing code:
- Run
black src/ tests/ - Run
ruff check --fix src/ tests/ - Run
mypy src/ - Run tests:
pytest - Update docstrings if needed
- Check line length (<100 chars)
- Add type hints to new code
- Remove debug statements
Tools Setup¶
Editor Integration¶
VS Code (settings.json):
{
"python.formatting.provider": "black",
"python.linting.ruffEnabled": true,
"python.linting.mypyEnabled": true,
"editor.formatOnSave": true,
"editor.rulers": [100]
}
PyCharm: - Install Black plugin - Configure Ruff as external tool - Enable mypy inspection
See Also¶
- Development Guide - Setup and workflow
- Testing Guide - Writing tests
- PEP 8 - Python style guide
- Black Documentation - Code formatter
- Ruff Documentation - Linter
- mypy Documentation - Type checker