Object Model

Site, Page, Section, and Asset data models.

6 min read 1259 words

Bengal’s object model defines howSite, Page, Section, Asset, and Menu relate, and where cacheable metadata lives (PageCore).

Read this first

  • If you want the object graph: start with “Core Objects”, then “Object Model Relationships”.
  • If you want the cache contract: start with “PageCore” (and refer tobengal/core/page/page_core.py).

Core Objects

Central data container (bengal/core/site/)

Holds all site content and delegates build coordination to orchestrators.

Key Attributes:

  • pages: List of all Page objects
  • sections: List of all Section objects
  • assets: List of all Asset objects
  • config: Configuration dictionary
  • menu: Built navigation menus

Key Methods:

  • build(): Delegates to BuildOrchestrator
  • discover_content(): Delegates to ContentOrchestrator

Content Unit (bengal/core/page/)

Represents a single content page with source, metadata, rendered HTML, and navigation.

Architecture:

  • Composition Pattern:Page contains a PageCoreinstance for cacheable metadata

  • Split into focused mixins (key modules):

    • page_core.py: Cacheable metadata (title, date, tags, etc.)
    • metadata.py: Frontmatter parsing
    • navigation.py: Next/prev/parent links
    • relationships.py: Section membership
    • computed.py: URL generation, TOC
    • operations.py: Rendering logic
    • proxy.py: Lazy-loading wrapper for incremental builds

    PageCore Integration:

  • Cacheable fields (title, date, tags, slug) stored inpage.core

  • Property delegates provide direct access:page.titlepage.core.title

  • Enables type-safe caching and lazy loading viaPageProxy

Structural Unit (bengal/core/section/)

Represents folder-based grouping of pages with hierarchical organization.

Architecture:

  • Composition Pattern: Split into focused mixins:

    • hierarchy.py: Tree traversal, parent/child, identity (__hash__, __eq__)
    • navigation.py: URL generation, version-aware filtering
    • queries.py: Page retrieval, sorting, index detection
    • ergonomics.py: Theme developer helpers (recent_pages, featured_posts, etc.)

    Features:

  • Hierarchy: Parent/child relationships (subsections)

  • Navigation: Access toregular_pages and sections

  • Cascade: Inheritance of frontmatter metadata to descendants

  • Path-based Registry: O(1) lookup viaSite.registry(ContentRegistry) using normalized paths

  • Stable References: Sections referenced by path strings (not object identity) for reliable incremental builds

Static Resource (bengal/core/asset/)

Handles static files (images, CSS, JS) with optimization.

Capabilities:

  • Minification (CSS/JS)
  • Image optimization
  • Cache busting (fingerprinting)
  • Output copying

Navigation Structure (bengal/core/menu.py)

Provides hierarchical navigation menus built from config + frontmatter.

Components:

  • MenuItem: Nested item with active state
  • MenuBuilder: Constructs hierarchy and marks active items

PageCore

PageCoreis the single cacheable metadata structure shared by:

  • Page: via page.coreand property delegates
  • PageMetadata: a type alias of PageCoreused by caches
  • PageProxy: wraps PageCoreand lazy-loads only non-core fields

Refer to:

  • bengal/core/page/page_core.py
  • bengal/cache/page_discovery_cache.py
  • bengal/core/page/proxy.py

Stable Section References

Bengal uses path-based section references instead of object identity for reliable incremental builds.

Path-Based Registry

Sections are stored in a dictionary keyed by normalized paths:

class Site:
    @property
    def registry(self) -> ContentRegistry:
        """Central registry for O(1) page/section lookups."""
        ...

    def get_section_by_path(self, path: Path | str) -> Section | None:
        """Delegate to ContentRegistry for O(1) lookup."""
        return self.registry.get_section(path)

Benefits

  • Stable Across Rebuilds: Path strings persist in cache, not object references
  • O(1) Lookup: Dictionary lookup is constant time
  • Reliable Incremental Builds: Sections can be renamed/moved without breaking references

Implementation

  • Sections stored as path strings inPageCore.section(not Section objects)
  • Registry built duringSite.register_sections()

Refer tobengal/core/site/section_registry.pyfor the registry implementation.

Object Model Relationships

classDiagram Site "1" --> "*" Page : manages Site "1" --> "*" Section : contains Site "1" --> "*" Asset : tracks Site "1" --> "*" MenuBuilder : uses MenuBuilder "1" --> "*" MenuItem : builds Section "1" --> "*" Page : groups Section "1" o-- "0..1" Page : index_page Section "1" --> "*" Section : subsections Section --> Section : parent Page --> Page : next/prev Page --> Page : next_in_section/prev_in_section Page --> Section : parent MenuItem --> MenuItem : children (nested) class Site { +root_path: Path +config: Dict +pages: List~Page~ +sections: List~Section~ +build() } class Page { +core: PageCore +content: str +rendered_html: str +render() } class PageCore { +source_path: str +title: str +date: datetime +tags: list +section: str } Page "1" *-- "1" PageCore : contains PageProxy "1" *-- "1" PageCore : wraps class Section { +name: str +path: Path +pages: List~Page~ +subsections: List~Section~ }

URL Ownership

Bengal uses a URL ownership system with claim-time enforcement to prevent URL collisions and ensure explicit ownership policy across all content producers.

URLRegistry

TheURLRegistry on Siteis the central authority for URL claims. It enforces ownership at claim time (before file writes), preventing invalid states from being created.

Key Features:

  • Claim-time enforcement: URLs are claimed before any file is written
  • Priority-based resolution: Higher priority claims win conflicts
  • Ownership context: All claims include owner, source, and priority metadata
  • Incremental safety: Claims are cached and loaded for incremental builds

Usage:

# Claim a URL (done automatically by orchestrators)
site.url_registry.claim(
    url="/about/",
    owner="content",
    source="content/about.md",
    priority=100,  # User content (highest priority)
)

# Claim via output path (for direct file writers)
url = site.url_registry.claim_output_path(
    output_path=Path("public/about/index.html"),
    site=site,
    owner="content",
    source="content/about.md",
    priority=100,
)

Priority Levels

URL claims use priority levels to resolve conflicts:

Priority Owner Rationale
100 User content User intent always wins
90 Autodoc sections Explicitly configured by user
80 Autodoc pages Derived from sections
50 Section indexes Structural authority
40 Taxonomy Auto-generated
10 Special pages Fallback utility pages
5 Redirects Should never shadow actual content

Conflict Resolution:

  • Higher priority wins (user content can override generated content)
  • Same priority + same source = idempotent (allowed)
  • Same priority + different source = collision error

Reserved Namespaces

Certain URL namespaces are reserved for specific generators:

  • /tags/- Reserved for taxonomy (priority 40)
  • /search/, /404/, /graph/- Reserved for special pages (priority 10)
  • Autodoc prefixes (e.g.,/cli/, /api/python/) - Reserved for autodoc output (priority 90/80)

TheOwnershipPolicyValidatorwarns when user content lands in reserved namespaces.

Integration Points

URLRegistry is integrated across all content producers:

  • ContentDiscovery: Claims URLs for user content (priority 100)
  • SectionOrchestrator: Claims section index URLs (priority 50)
  • TaxonomyOrchestrator: Claims taxonomy URLs (priority 40)
  • AutodocOrchestrator: Claims autodoc URLs (priority 90/80)
  • RedirectGenerator: Claims redirect URLs (priority 5)
  • SpecialPagesGenerator: Claims special page URLs (priority 10)

Incremental Build Safety

URL claims are persisted inBuildCacheand loaded during incremental builds. This prevents new content from shadowing existing URLs that weren't rebuilt in the current build.

Cache Integration:

  • Claims are saved toBuildCache.url_claimsafter build completes
  • Cached claims are loaded during discovery phase for incremental builds
  • Registry is pre-populated with claims from pages not being rebuilt

Error Handling

When a collision is detected,URLCollisionErroris raised with diagnostic information:

URL collision detected: /about/
  Existing claim: content (priority 100)
    Source: content/about.md
  New claim: taxonomy (priority 40)
    Source: tags/about
  Priority: Existing claim has higher priority (100 > 40) - new claim rejected
  Tip: Check for duplicate slugs, conflicting autodoc output, or namespace violations

See Also

  • bengal/core/url_ownership.py- URLRegistry implementation
  • bengal/config/url_policy.py- Reserved namespace definitions