"""Provide Betty's extension API."""
from __future__ import annotations
from abc import abstractmethod
from typing import TypeVar, TYPE_CHECKING, Generic, Self, Sequence
from typing_extensions import override
from betty.config import Configurable, Configuration
from betty.core import CoreComponent
from betty.locale.localizable import Localizable, _, call
from betty.plugin import (
PluginRepository,
PluginIdToTypeMap,
OrderedPlugin,
CyclicDependencyError,
DependentPlugin,
sort_dependent_plugin_graph,
sort_ordered_plugin_graph,
)
from betty.plugin.entry_point import EntryPointPluginRepository
from betty.project.factory import ProjectDependentFactory
from betty.requirement import AllRequirements
from betty.typing import private
if TYPE_CHECKING:
from graphlib import TopologicalSorter
from collections.abc import Iterable
from betty.event_dispatcher import EventHandlerRegistry
from betty.requirement import Requirement
from betty.project import Project
from pathlib import Path
_ConfigurationT = TypeVar("_ConfigurationT", bound=Configuration)
[docs]
class Extension(
OrderedPlugin["Extension"],
DependentPlugin["Extension"],
CoreComponent,
ProjectDependentFactory,
):
"""
Integrate optional functionality with Betty :py:class:`betty.project.Project`s.
Read more about :doc:`/development/plugin/extension`.
To test your own subclasses, use :py:class:`betty.test_utils.project.extension.ExtensionTestBase`.
"""
[docs]
def __init__(self, project: Project):
assert type(self) is not Extension
super().__init__()
self._project = project
[docs]
def register_event_handlers(self, registry: EventHandlerRegistry) -> None:
"""
Register event handlers with the project.
"""
pass
@property
def project(self) -> Project:
"""
The project this extension runs within.
"""
return self._project
[docs]
@classmethod
async def requirement(cls) -> Requirement:
"""
Define the requirement for this extension to be enabled.
This defaults to the extension's dependencies.
"""
return await Dependencies.new(cls)
[docs]
@classmethod
def assets_directory_path(cls) -> Path | None:
"""
Return the path on disk where the extension's assets are located.
This may be anywhere in your Python package.
"""
return None
_ExtensionT = TypeVar("_ExtensionT", bound=Extension)
EXTENSION_REPOSITORY: PluginRepository[Extension] = EntryPointPluginRepository(
"betty.extension"
)
"""
The project extension plugin repository.
Read more about :doc:`/development/plugin/extension`.
"""
[docs]
class Theme(Extension):
"""
An extension that is a front-end theme.
"""
pass # pragma: no cover
[docs]
class ConfigurableExtension(
Extension, Generic[_ConfigurationT], Configurable[_ConfigurationT]
):
"""
A configurable extension.
"""