Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.extension.demo

    """
    Provide demonstration site functionality.
    """

    from __future__ import annotations

    from contextlib import AsyncExitStack, asynccontextmanager
    from typing import TYPE_CHECKING, final

    from typing_extensions import override

    from betty import load, generate, serve
    from betty.extension.cotton_candy import CottonCandy
    from betty.extension.cotton_candy.config import CottonCandyConfiguration
    from betty.extension.http_api_doc import HttpApiDoc
    from betty.extension.maps import Maps
    from betty.extension.trees import Trees
    from betty.extension.wikipedia import Wikipedia
    from betty.load import LoadAncestryEvent
    from betty.locale.date import Date, DateRange
    from betty.locale.localizable import static, Localizable
    from betty.locale.localizer import DEFAULT_LOCALIZER
    from betty.model.ancestry import (
        Place,
        PlaceName,
        Person,
        Presence,
        PersonName,
        Link,
        Source,
        Citation,
        Event,
        Enclosure,
        Note,
    )
    from betty.model.event_type import Marriage, Birth, Death
    from betty.model.presence_role import Subject
    from betty.project import (
        LocaleConfiguration,
        ExtensionConfiguration,
        EntityReference,
        Project,
    )
    from betty.project.extension import Extension
    from betty.serve import Server, NoPublicUrlBecauseServerNotStartedError

    if TYPE_CHECKING:
        from betty.event_dispatcher import EventHandlerRegistry
        from betty.machine_name import MachineName
        from betty.app import App
        from collections.abc import AsyncIterator
        from betty.model import Entity

    async def _load_ancestry(event: LoadAncestryEvent) -> None:
        def _load(*entities: Entity):
            event.project.ancestry.add(*entities)

        netherlands = Place(
            id="betty-demo-netherlands",
            names=[
                PlaceName(name="Netherlands"),
                PlaceName(
                    name="Nederland",
                    locale="nl",
                ),
                PlaceName(
                    name="Нідерланди",
                    locale="uk",
                ),
                PlaceName(
                    name="Pays-Bas",
                    locale="fr",
                ),
            ],
            links=[Link("https://en.wikipedia.org/wiki/Netherlands")],
        )
        _load(netherlands)

        north_holland = Place(
            id="betty-demo-north-holland",
            names=[
                PlaceName(name="North Holland"),
                PlaceName(
                    name="Noord-Holland",
                    locale="nl",
                ),
                PlaceName(
                    name="Північна Голландія",
                    locale="uk",
                ),
                PlaceName(
                    name="Hollande-Septentrionale",
                    locale="fr",
                ),
            ],
            links=[
                Link("https://en.wikipedia.org/wiki/North_Holland"),
                Link("https://www.noord-holland.nl/Home"),
            ],
        )
        _load(Enclosure(encloses=north_holland, enclosed_by=netherlands))
        _load(north_holland)

        amsterdam_note = Note(
            """
    Did you know that while Amsterdam is the country's official capital, The Hague is the Netherlands' administrative center and seat of government?
        """
        )

        amsterdam = Place(
            id="betty-demo-amsterdam",
            names=[
                PlaceName(name="Amsterdam"),
                PlaceName(
                    name="Амстерда́м",
                    locale="uk",
                ),
            ],
            links=[
                Link("https://nl.wikipedia.org/wiki/Amsterdam"),
                Link("https://www.amsterdam.nl/"),
            ],
            notes=[amsterdam_note],
        )
        _load(Enclosure(encloses=amsterdam, enclosed_by=north_holland))
        _load(amsterdam)

        ilpendam = Place(
            id="betty-demo-ilpendam",
            names=[
                PlaceName(name="Ilpendam"),
                PlaceName(
                    name="Илпендам",
                    locale="uk",
                ),
            ],
            links=[Link("https://nl.wikipedia.org/wiki/Ilpendam")],
        )
        _load(Enclosure(encloses=ilpendam, enclosed_by=north_holland))
        _load(ilpendam)

        personal_accounts = Source(
            id="betty-demo-personal-accounts",
            name="Personal accounts",
        )
        _load(personal_accounts)

        cite_first_person_account = Citation(
            id="betty-demo-first-person-account",
            source=personal_accounts,
        )
        _load(cite_first_person_account)

        noord_hollands_archief = Source(
            id="betty-demo-noord-hollands-archief",
            name="Noord-Hollands Archief",
            links=[Link("https://noord-hollandsarchief.nl/")],
        )
        _load(noord_hollands_archief)

        bevolkingsregister_amsterdam = Source(
            id="betty-demo-bevolkingsregister-amsterdam",
            name="Bevolkingsregister Amsterdam",
            author="Gemeente Amsterdam",
            publisher="Gemeente Amsterdam",
            contained_by=noord_hollands_archief,
        )
        _load(bevolkingsregister_amsterdam)

        david_marinus_lankester = Person(id="betty-demo-david-marinus-lankester")
        _load(
            PersonName(
                person=david_marinus_lankester,
                individual="David Marinus",
                affiliation="Lankester",
            ),
            david_marinus_lankester,
        )

        geertruida_van_ling = Person(id="betty-demo-geertruida-van-ling")
        _load(
            PersonName(
                person=geertruida_van_ling,
                individual="Geertruida",
                affiliation="Van Ling",
            ),
            geertruida_van_ling,
        )

        marriage_of_dirk_jacobus_lankester_and_jannigje_palsen = Event(
            id="betty-demo-marriage-of-dirk-jacobus-lankester-and-jannigje-palsen",
            event_type=Marriage(),
            date=Date(1922, 7, 4),
            place=ilpendam,
        )
        _load(marriage_of_dirk_jacobus_lankester_and_jannigje_palsen)

        birth_of_dirk_jacobus_lankester = Event(
            id="betty-demo-birth-of-dirk-jacobus-lankester",
            event_type=Birth(),
            date=Date(1897, 8, 25),
            place=amsterdam,
        )
        _load(birth_of_dirk_jacobus_lankester)

        death_of_dirk_jacobus_lankester = Event(
            id="betty-demo-death-of-dirk-jacobus-lankester",
            event_type=Death(),
            date=Date(1986, 8, 18),
            place=amsterdam,
        )
        _load(death_of_dirk_jacobus_lankester)

        dirk_jacobus_lankester = Person(
            id="betty-demo-dirk-jacobus-lankester",
            parents=(david_marinus_lankester, geertruida_van_ling),
        )
        _load(
            PersonName(
                person=dirk_jacobus_lankester,
                individual="Dirk Jacobus",
                affiliation="Lankester",
            ),
            Presence(dirk_jacobus_lankester, Subject(), birth_of_dirk_jacobus_lankester),
            Presence(dirk_jacobus_lankester, Subject(), death_of_dirk_jacobus_lankester),
            Presence(
                dirk_jacobus_lankester,
                Subject(),
                marriage_of_dirk_jacobus_lankester_and_jannigje_palsen,
            ),
        )
        _load(dirk_jacobus_lankester)

        birth_of_marinus_david_lankester = Event(
            id="betty-demo-birth-of-marinus-david",
            event_type=Birth(),
            date=DateRange(
                Date(1874, 1, 15),
                Date(1874, 3, 21),
                start_is_boundary=True,
                end_is_boundary=True,
            ),
            place=amsterdam,
        )
        _load(birth_of_marinus_david_lankester)

        death_of_marinus_david_lankester = Event(
            id="betty-demo-death-of-marinus-david",
            event_type=Death(),
            date=Date(1971),
            place=amsterdam,
        )
        _load(death_of_marinus_david_lankester)

        marinus_david_lankester = Person(
            id="betty-demo-marinus-david-lankester",
            parents=(david_marinus_lankester, geertruida_van_ling),
        )
        _load(
            PersonName(
                person=marinus_david_lankester,
                individual="Marinus David",
                affiliation="Lankester",
            ),
            Presence(marinus_david_lankester, Subject(), birth_of_marinus_david_lankester),
            Presence(marinus_david_lankester, Subject(), death_of_marinus_david_lankester),
        )
        _load(marinus_david_lankester)

        birth_of_jacoba_gesina_lankester = Event(
            id="betty-demo-birth-of-jacoba-gesina",
            event_type=Birth(),
            date=Date(1900, 3, 14),
            place=amsterdam,
        )
        _load(birth_of_jacoba_gesina_lankester)

        jacoba_gesina_lankester = Person(
            id="betty-demo-jacoba-gesina-lankester",
            parents=(david_marinus_lankester, geertruida_van_ling),
        )
        _load(
            PersonName(
                person=jacoba_gesina_lankester,
                individual="Jacoba Gesina",
                affiliation="Lankester",
            ),
            Presence(jacoba_gesina_lankester, Subject(), birth_of_jacoba_gesina_lankester),
        )
        _load(jacoba_gesina_lankester)

        jannigje_palsen = Person(id="betty-demo-jannigje-palsen")
        _load(
            PersonName(
                person=jannigje_palsen,
                individual="Jannigje",
                affiliation="Palsen",
            ),
            Presence(
                jannigje_palsen,
                Subject(),
                marriage_of_dirk_jacobus_lankester_and_jannigje_palsen,
            ),
            jannigje_palsen,
        )

        marriage_of_johan_de_boer_and_liberta_lankester = Event(
            id="betty-demo-marriage-of-johan-de-boer-and-liberta-lankester",
            event_type=Marriage(),
            date=Date(1953, 6, 19),
            place=amsterdam,
        )
        _load(marriage_of_johan_de_boer_and_liberta_lankester)

        cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam = Citation(
            id="betty-demo-birth-of-liberta-lankester-from-bevolkingsregister-amsterdam",
            source=bevolkingsregister_amsterdam,
            location=static("Amsterdam"),
        )
        _load(cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam)

        birth_of_liberta_lankester = Event(
            id="betty-demo-birth-of-liberta-lankester",
            event_type=Birth(),
            date=Date(1929, 12, 22),
            place=amsterdam,
            citations=[cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam],
        )
        _load(birth_of_liberta_lankester)

        death_of_liberta_lankester = Event(
            id="betty-demo-death-of-liberta-lankester",
            event_type=Death(),
            date=Date(2015, 1, 17),
            place=amsterdam,
            citations=[cite_first_person_account],
        )
        _load(death_of_liberta_lankester)

        liberta_lankester_note = Note(
            """
    Did you know that Liberta "Betty" Lankester is Betty's namesake?
        """
        )

        liberta_lankester = Person(
            id="betty-demo-liberta-lankester",
            parents=(dirk_jacobus_lankester, jannigje_palsen),
            notes=[liberta_lankester_note],
        )
        _load(
            PersonName(
                person=liberta_lankester,
                individual="Liberta",
                affiliation="Lankester",
            ),
            PersonName(
                person=liberta_lankester,
                individual="Betty",
            ),
            Presence(liberta_lankester, Subject(), birth_of_liberta_lankester),
            Presence(liberta_lankester, Subject(), death_of_liberta_lankester),
            Presence(
                liberta_lankester,
                Subject(),
                marriage_of_johan_de_boer_and_liberta_lankester,
            ),
        )
        _load(liberta_lankester)

        birth_of_johan_de_boer = Event(
            id="betty-demo-birth-of-johan-de-boer",
            event_type=Birth(),
            date=Date(1930, 6, 20),
            place=amsterdam,
        )
        _load(birth_of_johan_de_boer)

        death_of_johan_de_boer = Event(
            id="betty-demo-death-of-johan-de-boer",
            event_type=Death(),
            date=Date(1999, 3, 10),
            place=amsterdam,
            citations=[cite_first_person_account],
        )
        _load(death_of_johan_de_boer)

        johan_de_boer = Person(id="betty-demo-johan-de-boer")
        _load(
            PersonName(
                person=johan_de_boer,
                individual="Johan",
                affiliation="De Boer",
            ),
            PersonName(
                person=johan_de_boer,
                individual="Hans",
            ),
            Presence(johan_de_boer, Subject(), birth_of_johan_de_boer),
            Presence(johan_de_boer, Subject(), death_of_johan_de_boer),
            Presence(
                johan_de_boer,
                Subject(),
                marriage_of_johan_de_boer_and_liberta_lankester,
            ),
            johan_de_boer,
        )

        parent_of_bart_feenstra_child_of_liberta_lankester = Person(
            id="betty-demo-parent-of-bart-feenstra-child-of-liberta-lankester",
            parents=(johan_de_boer, liberta_lankester),
        )
        _load(
            PersonName(
                person=parent_of_bart_feenstra_child_of_liberta_lankester,
                individual="Bart's parent",
            )
        )
        _load(parent_of_bart_feenstra_child_of_liberta_lankester)

        bart_feenstra = Person(
            id="betty-demo-bart-feenstra",
            parents=(parent_of_bart_feenstra_child_of_liberta_lankester,),
        )
        _load(
            PersonName(
                person=bart_feenstra,
                individual="Bart",
                affiliation="Feenstra",
            )
        )
        _load(bart_feenstra)

    @final
    class Demo(Extension):
        """
        Provide demonstration site functionality.
        """

        @override
        @classmethod
        def plugin_id(cls) -> MachineName:
            return "demo"

        @override
        @classmethod
        def plugin_label(cls) -> Localizable:
            return static("Demo")

        @override
        @classmethod
        def depends_on(cls) -> set[MachineName]:
            return {
                CottonCandy.plugin_id(),
                HttpApiDoc.plugin_id(),
                Maps.plugin_id(),
                Trees.plugin_id(),
                Wikipedia.plugin_id(),
            }

        @override
        def register_event_handlers(self, registry: EventHandlerRegistry) -> None:
            registry.add_handler(LoadAncestryEvent, _load_ancestry)

    @final
    class DemoServer(Server):
        """
        Serve the Betty demonstration site.
        """

        def __init__(
            self,
            app: App,
        ):
            super().__init__(localizer=DEFAULT_LOCALIZER)
            self._app = app
            self._server: Server | None = None
            self._exit_stack = AsyncExitStack()

        @override
        @property
        def public_url(self) -> str:
            if self._server is not None:
                return self._server.public_url
            raise NoPublicUrlBecauseServerNotStartedError()

        @override
        async def start(self) -> None:
            try:
                project = await self._exit_stack.enter_async_context(
                    demo_project(self._app)
                )
                self._localizer = self._app.localizer
                await load.load(project)
                self._server = serve.BuiltinProjectServer(project)
                await self._exit_stack.enter_async_context(self._server)
                project.configuration.url = self._server.public_url
                await generate.generate(project)
            except BaseException:
                await self.stop()
                raise

        @override
        async def stop(self) -> None:
            await self._exit_stack.aclose()

    @asynccontextmanager
    async def demo_project(app: App) -> AsyncIterator[Project]:
        """
        Create a new demonstration project.
        """
        async with Project.new_temporary(app) as project:
            project.configuration.name = Demo.plugin_id()
            project.configuration.title = {
                "en-US": "A Betty demonstration",
                "nl-NL": "Een demonstratie van Betty",
            }
            project.configuration.extensions.append(ExtensionConfiguration(Demo))
            project.configuration.extensions.append(
                ExtensionConfiguration(
                    CottonCandy,
                    extension_configuration=CottonCandyConfiguration(
                        featured_entities=[
                            EntityReference(Place, "betty-demo-amsterdam"),
                            EntityReference(Person, "betty-demo-liberta-lankester"),
                            EntityReference(Place, "betty-demo-netherlands"),
                        ],
                    ),
                )
            )
            # Include all of the translations Betty ships with.
            project.configuration.locales.replace(
                LocaleConfiguration(
                    "en-US",
                    alias="en",
                ),
                LocaleConfiguration(
                    "nl-NL",
                    alias="nl",
                ),
                LocaleConfiguration(
                    "fr-FR",
                    alias="fr",
                ),
                LocaleConfiguration(
                    "uk",
                    alias="uk",
                ),
                LocaleConfiguration(
                    "de-DE",
                    alias="de",
                ),
            )
            async with project:
                yield project