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 feature

  • fix: Bug fix

  • docs: Documentation

  • refactor: Code refactoring

  • test: Adding tests

  • chore: Maintenance

Example:

feat: Add async variants to TrustManager

- Added create_reciprocal_trust_async()
- Added delete_peer_trust_async()
- Updated tests

Closes #123

See Also