Protocol Layer

Consolidated protocol definitions for type-safe interfaces across Bengal

4 min read 789 words

Bengal uses Python protocols (PEP 544) to define contracts between subsystems and external packages. All protocols are consolidated inbengal.protocolsfor discoverability and consistency.

Protocol Categories

graph TB subgraph "bengal.protocols" subgraph "Core Protocols" PageLike["PageLike<br/>Page interface"] SectionLike["SectionLike<br/>Section interface"] SiteLike["SiteLike<br/>Site interface"] NavigableSection["NavigableSection<br/>Navigation methods"] QueryableSection["QueryableSection<br/>Content queries"] end subgraph "Rendering Protocols" TemplateEngine["TemplateEngine<br/>Full engine contract"] TemplateRenderer["TemplateRenderer<br/>Render methods only"] TemplateIntrospector["TemplateIntrospector<br/>Template discovery"] HighlightService["HighlightService<br/>Syntax highlighting"] TemplateEnvironment["TemplateEnvironment<br/>Filter/global registration"] end subgraph "Infrastructure Protocols" ProgressReporter["ProgressReporter<br/>Build progress"] Cacheable["Cacheable<br/>Cache serialization"] OutputTarget["OutputTarget<br/>File output"] ContentSourceProtocol["ContentSourceProtocol<br/>Remote content"] OutputCollector["OutputCollector<br/>CSS collection"] end end subgraph "External Packages" Rosettes["rosettes<br/>Syntax highlighter"] Kida["kida<br/>Template engine"] Patitas["patitas<br/>Markdown parser"] end Rosettes -->|"implements"| HighlightService Kida -->|"implements"| TemplateEngine Patitas -->|"uses"| HighlightService

Usage

Import all protocols from the canonical location:

from bengal.protocols import (
    # Core
    PageLike, SectionLike, SiteLike,
    NavigableSection, QueryableSection,
    
    # Rendering
    TemplateEngine, TemplateRenderer, HighlightService,
    TemplateEnvironment, EngineCapability,
    
    # Infrastructure
    ProgressReporter, Cacheable, OutputTarget,
    ContentSourceProtocol, OutputCollector,
)

Core Protocols

SectionLike

Interface for content sections (directories in content tree).

@runtime_checkable
class SectionLike(Protocol):
    @property
    def name(self) -> str: ...
    @property
    def title(self) -> str: ...
    @property
    def path(self) -> Path: ...
    @property
    def href(self) -> str: ...
    @property
    def pages(self) -> list[PageLike]: ...
    @property
    def subsections(self) -> list[SectionLike]: ...
    @property
    def parent(self) -> SectionLike | None: ...
    @property
    def index_page(self) -> PageLike | None: ...
    @property
    def root(self) -> SectionLike: ...

PageLike

Interface for content pages.

@runtime_checkable
class PageLike(Protocol):
    @property
    def title(self) -> str: ...
    @property
    def href(self) -> str: ...
    @property
    def source_path(self) -> Path: ...
    @property
    def section(self) -> SectionLike: ...

SiteLike

Interface for the site object.

@runtime_checkable
class SiteLike(Protocol):
    @property
    def pages(self) -> list[PageLike]: ...
    @property
    def sections(self) -> list[SectionLike]: ...
    @property
    def root_section(self) -> SectionLike: ...
    @property
    def config(self) -> SiteConfig: ...

Rendering Protocols

TemplateEngine

Full template engine contract. Composed of smaller protocols for flexibility.

class TemplateEngine(TemplateRenderer, TemplateIntrospector, TemplateValidator, Protocol):
    @property
    def capabilities(self) -> EngineCapability: ...
    
    def has_capability(self, cap: EngineCapability) -> bool: ...

Use the smaller protocols when you only need specific functionality:

Protocol Methods Use When
TemplateRenderer render_template,render_string Only rendering
TemplateIntrospector template_exists,list_templates Template discovery
TemplateValidator validate Syntax checking

HighlightService

Syntax highlighting contract. Thread-safe.

@runtime_checkable
class HighlightService(Protocol):
    @property
    def name(self) -> str: ...
    
    def highlight(
        self,
        code: str,
        language: str,
        *,
        hl_lines: list[int] | None = None,
        show_linenos: bool = False,
    ) -> str: ...
    
    def supports_language(self, language: str) -> bool: ...

Thread Safety:highlight()must be safe to call from multiple threads concurrently.

Infrastructure Protocols

Cacheable

Objects that can be serialized to/from the build cache.

@runtime_checkable
class Cacheable(Protocol):
    def to_cache_dict(self) -> dict[str, Any]: ...
    
    @classmethod
    def from_cache_dict(cls: type[T], data: dict[str, Any]) -> T: ...

ProgressReporter

Build progress reporting. Used by orchestration layer.

@runtime_checkable
class ProgressReporter(Protocol):
    def add_phase(self, name: str, total: int) -> None: ...
    def start_phase(self, name: str) -> None: ...
    def update_phase(self, name: str, current: int) -> None: ...
    def complete_phase(self, name: str) -> None: ...
    def log(self, message: str, level: str = "info") -> None: ...

OutputTarget

File output abstraction. Thread-safe.

@runtime_checkable
class OutputTarget(Protocol):
    @property
    def name(self) -> str: ...
    
    def write(self, path: Path, content: str) -> None: ...
    def write_bytes(self, path: Path, content: bytes) -> None: ...
    def copy(self, src: Path, dest: Path) -> None: ...
    def mkdir(self, path: Path) -> None: ...
    def exists(self, path: Path) -> bool: ...

Thread Safety: All methods must be safe for concurrent calls.

Design Principles

Leaf-Node Architecture

bengal.protocolsis a leaf node in the import graph:

  • ✅ Can import:typing,pathlib,collections.abc, standard library
  • ❌ Cannot import:bengal.core,bengal.rendering,bengal.cache, etc.

This prevents circular dependencies and allows external packages to depend on protocols without pulling in Bengal's internals.

Protocol Composition

Large protocols are composed from smaller ones:

# Instead of one large protocol:
class BigEngine(Protocol):
    def render(self): ...
    def validate(self): ...
    def introspect(self): ...

# Use composable protocols:
class TemplateRenderer(Protocol):
    def render_template(self): ...

class TemplateValidator(Protocol):
    def validate(self): ...

class TemplateEngine(TemplateRenderer, TemplateValidator, Protocol):
    """Full engine is composition of capabilities."""

This allows functions to accept only the capabilities they need.

Thread-Safety Requirements

Protocols document thread-safety requirements in docstrings:

  • HighlightService.highlight()— must be thread-safe
  • OutputTarget.*— all methods must be thread-safe
  • ContentSourceProtocol.fetch_all()— must be thread-safe

Backwards Compatibility

Old import paths still work but emit deprecation warnings:

# Old (deprecated, emits warning):
from bengal.core.section.protocols import SectionLike
from bengal.rendering.engines.protocol import TemplateEngineProtocol
from bengal.rendering.highlighting.protocol import HighlightBackend

# New (canonical):
from bengal.protocols import SectionLike, TemplateEngine, HighlightService