Style Guide
Audience: Contributors to the ActingWeb codebase.
This guide covers code style, type annotations, and conventions used in the ActingWeb project.
Code Quality Standards
The ActingWeb project maintains zero errors and zero warnings for both type checking and linting.
Current Status
Pyright: 0 errors, 0 warnings
Ruff: All checks passing
Tests: 100% passing
Running Checks
Before committing, always run:
# Type checking
poetry run pyright actingweb tests
# Linting
poetry run ruff check actingweb tests
# Formatting
poetry run ruff format actingweb tests
# Tests
poetry run pytest tests/
Type Annotations
Always use proper type annotations:
# Good - explicit types
def handle_method(
actor: ActorInterface,
method_name: str,
data: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
return {"result": "success"}
# Bad - missing types
def handle_method(actor, method_name, data):
return {"result": "success"}
Common Type Patterns
Optional values:
from typing import Optional
def get_value(key: str) -> Optional[str]:
return self._data.get(key)
Union types:
from typing import Union
body: Union[str, bytes, None] = request.body
Callable types:
from typing import Callable, Any
def register_hook(self, func: Callable[..., Any]) -> None:
pass
Dict and List:
from typing import Dict, List, Any
def process(data: Dict[str, Any]) -> List[str]:
return list(data.keys())
Null Safety
Always check for None before using optional values:
# Good
result = some_method()
if result is not None:
process(result)
# Bad
result = some_method()
process(result) # May be None!
Early returns for None checks:
def process(actor_id: str) -> Dict[str, Any]:
actor = get_actor(actor_id)
if actor is None:
return {"error": "Not found"}
# Continue with actor guaranteed non-None
return {"id": actor.id}
Method Overrides
Match base class signatures exactly:
# Good - matches base class
def method(self, param1: str, param2: Dict[str, Any]) -> bool:
...
# Bad - different parameter names
def method(self, param1: str, _param2: Dict[str, Any]) -> bool:
...
Import Management
Remove unused imports:
# Good
from typing import Dict, Optional
# Bad - List and Union not used
from typing import Dict, Optional, List, Union
Use TYPE_CHECKING for forward references:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .some_module import SomeClass
Docstrings
Use Google-style docstrings:
def create_trust(
self,
peer_id: str,
relationship: str,
baseuri: str
) -> Optional[Dict[str, Any]]:
"""Create a trust relationship with a peer.
Args:
peer_id: Unique identifier for the peer
relationship: Type of relationship (e.g., "friend")
baseuri: Base URI of the peer's actor
Returns:
Trust record dict if successful, None if failed
Raises:
ValueError: If peer_id is empty
"""
pass
Error Handling
Use specific exceptions:
# Good
raise ValueError("peer_id cannot be empty")
raise PermissionError("Not authorized to access this resource")
# Bad
raise Exception("Error")
Handle exceptions appropriately:
try:
result = risky_operation()
except SpecificError as e:
logger.error(f"Operation failed: {e}")
return None
except Exception as e:
logger.exception("Unexpected error")
raise
Logging
Use module-level loggers:
import logging
logger = logging.getLogger(__name__)
def process():
logger.debug("Starting process")
logger.info("Process completed")
logger.warning("Unexpected state")
logger.error("Operation failed")
Naming Conventions
Classes: PascalCase
class ActorInterface:
class PropertyStore:
Functions/Methods: snake_case
def get_properties():
def create_trust_relationship():
Constants: UPPER_SNAKE_CASE
DEFAULT_TIMEOUT = 30
MAX_RETRIES = 3
Private members: Leading underscore
self._internal_state = {}
def _helper_method(self):
Async methods: Suffix with _async
async def create_trust_async():
async def get_peer_info_async():
Code Organization
Imports order:
# Standard library
import logging
import json
from typing import Dict, Optional
# Third-party
import httpx
from pynamodb.models import Model
# Local
from actingweb.config import Config
from actingweb.interface import ActorInterface
Class organization:
class MyClass:
# Class variables
DEFAULT_VALUE = 10
# __init__
def __init__(self, ...):
pass
# Properties
@property
def value(self):
pass
# Public methods
def public_method(self):
pass
# Private methods
def _private_method(self):
pass
# Class methods
@classmethod
def from_config(cls, config):
pass
# Static methods
@staticmethod
def utility_function():
pass
Testing Conventions
Test file naming:
tests/test_<module>.py
tests/integration/test_<feature>.py
Test function naming:
def test_<function>_<scenario>():
def test_create_trust_with_valid_peer():
def test_create_trust_with_invalid_peer_raises_error():
Test structure (Arrange-Act-Assert):
def test_property_set():
# Arrange
actor = create_test_actor()
store = PropertyStore(actor)
# Act
store["key"] = "value"
# Assert
assert store["key"] == "value"
Git Commit Messages
Format:
<type>: <description>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentationrefactor: Code refactoringtest: Adding testschore: Maintenance
Example:
feat: Add async variants to TrustManager
- Added create_reciprocal_trust_async()
- Added delete_peer_trust_async()
- Updated tests
Closes #123
See Also
Codebase Architecture - Codebase architecture
Testing - Testing guide
PEP 8 - Python style guide