Toggle Light / Dark / Auto color theme
Toggle table of contents sidebar
Source code for betty.project.extension.wikipedia
"""Integrate Betty with `Wikipedia <https://wikipedia.org>`_."""
from __future__ import annotations
import logging
from asyncio import gather
from pathlib import Path
from typing import Iterable, TYPE_CHECKING, final, Self
from jinja2 import pass_context
from typing_extensions import override
from betty.fetch import FetchError
from betty.jinja2 import Jinja2Provider, context_localizer, Filters, Globals
from betty.locale import negotiate_locale
from betty.locale.localizable import _
from betty.plugin import ShorthandPluginBase
from betty.project.extension import ConfigurableExtension
from betty.project.extension.wikipedia.config import WikipediaConfiguration
from betty.project.load import PostLoadAncestryEvent
from betty.wikipedia import Summary, _parse_url, NotAPageError, _Retriever, _Populator
from betty.wikipedia.copyright_notice import WikipediaContributors
if TYPE_CHECKING:
from betty.copyright_notice import CopyrightNotice
from collections.abc import Awaitable
from betty.project import Project
from betty.event_dispatcher import EventHandlerRegistry
from jinja2.runtime import Context
from betty.ancestry.link import Link
async def _populate_ancestry(event: PostLoadAncestryEvent) -> None:
project = event.project
extensions = await project.extensions
wikipedia = extensions[Wikipedia]
populator = _Populator(
project.ancestry,
list(project.configuration.locales.keys()),
await project.localizers,
await wikipedia.retriever,
await project.copyright_notice_repository.new_target(WikipediaContributors),
)
await populator.populate()
[docs]
@final
class Wikipedia(
ShorthandPluginBase, ConfigurableExtension[WikipediaConfiguration], Jinja2Provider
):
"""
Integrates Betty with `Wikipedia <https://wikipedia.org>`_.
"""
[docs]
def __init__(
self,
project: Project,
wikipedia_contributors_copyright_notice: CopyrightNotice,
*,
configuration: WikipediaConfiguration,
):
super().__init__(project, configuration=configuration)
self._wikipedia_contributors_copyright_notice = (
wikipedia_contributors_copyright_notice
)
self._retriever: _Retriever | None = None
[docs]
@override
@classmethod
async def new_for_project(cls, project: Project) -> Self:
return cls(
project,
await project.copyright_notice_repository.new_target(
"wikipedia-contributors"
),
configuration=cls.new_default_configuration(),
)
_plugin_id = "wikipedia"
_plugin_label = _("Wikipedia")
_plugin_description = _("Enrich your ancestry with information from Wikipedia")
[docs]
@override
def register_event_handlers(self, registry: EventHandlerRegistry) -> None:
registry.add_handler(PostLoadAncestryEvent, _populate_ancestry)
@property
def retriever(self) -> Awaitable[_Retriever]:
"""
The Wikipedia content retriever.
"""
return self._get_retriever()
async def _get_retriever(self) -> _Retriever:
if self._retriever is None:
self.assert_bootstrapped()
return _Retriever(await self.project.app.fetcher)
return self._retriever
@override
@property
def globals(self) -> Globals:
return {
"wikipedia_contributors_copyright_notice": self._wikipedia_contributors_copyright_notice
}
@override
@property
def filters(self) -> Filters:
return {
"wikipedia": self.filter_wikipedia_links,
}
[docs]
@pass_context
async def filter_wikipedia_links(
self, context: Context, links: Iterable[Link]
) -> Iterable[Summary]:
"""
Given a sequence of links, return any Wikipedia summaries for them.
"""
return filter(
None,
await gather(
*(
self._filter_wikipedia_link(
context_localizer(context).locale,
link,
)
for link in links
)
),
)
async def _filter_wikipedia_link(self, locale: str, link: Link) -> Summary | None:
try:
page_language, page_name = _parse_url(link.url)
except NotAPageError:
return None
if negotiate_locale(locale, [page_language]) is None:
return None
try:
retriever = await self.retriever
return await retriever.get_summary(page_language, page_name)
except FetchError as error:
logger = logging.getLogger(__name__)
logger.warning(str(error))
return None
[docs]
@override
@classmethod
def assets_directory_path(cls) -> Path | None:
return Path(__file__).parent / "assets"
[docs]
@override
@classmethod
def new_default_configuration(cls) -> WikipediaConfiguration:
return WikipediaConfiguration()