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
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-safeOutputTarget.*— all methods must be thread-safeContentSourceProtocol.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
Related Documentation
- Extension Points — Using protocols for customization
- Custom Template Engine — Implementing TemplateEngine
- Object Model — PageLike and SectionLike implementations