Edit on GitHub

hexdoc.patchouli.page

 1__all__ = [
 2    "BlastingPage",
 3    "CampfireCookingPage",
 4    "CraftingPage",
 5    "EmptyPage",
 6    "EntityPage",
 7    "ImagePage",
 8    "LinkPage",
 9    "Multiblock",
10    "MultiblockPage",
11    "Page",
12    "PageWithText",
13    "PageWithTitle",
14    "QuestPage",
15    "RelationsPage",
16    "SmeltingPage",
17    "SmithingPage",
18    "SmokingPage",
19    "SpotlightPage",
20    "StonecuttingPage",
21    "TextPage",
22]
23
24from .abstract_pages import Page, PageWithText, PageWithTitle
25from .pages import (
26    BlastingPage,
27    CampfireCookingPage,
28    CraftingPage,
29    EmptyPage,
30    EntityPage,
31    ImagePage,
32    LinkPage,
33    Multiblock,
34    MultiblockPage,
35    QuestPage,
36    RelationsPage,
37    SmeltingPage,
38    SmithingPage,
39    SmokingPage,
40    SpotlightPage,
41    StonecuttingPage,
42    TextPage,
43)
class BlastingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
38class BlastingPage(PageWithDoubleRecipe[BlastingRecipe], type="patchouli:blasting"):
39    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class CampfireCookingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
42class CampfireCookingPage(
43    PageWithDoubleRecipe[CampfireCookingRecipe], type="patchouli:campfire_cooking"
44):
45    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class CraftingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
48class CraftingPage(PageWithDoubleRecipe[CraftingRecipe], type="patchouli:crafting"):
49    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class EmptyPage(hexdoc.patchouli.page.Page):
52class EmptyPage(Page, type="patchouli:empty", template_type="patchouli:page"):
53    draw_filler: bool = True
draw_filler: bool
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class EntityPage(hexdoc.patchouli.page.PageWithText):
56class EntityPage(PageWithText, type="patchouli:entity"):
57    _entity_name: LocalizedStr = PrivateAttr()
58    _texture: PNGTexture = PrivateAttr()
59
60    entity: Entity
61    scale: float = 1
62    offset: float = 0
63    rotate: bool = True
64    default_rotation: float = -45
65    name_field: LocalizedStr | None = Field(default=None, serialization_alias="name")
66
67    @property
68    def entity_name(self):
69        return self._entity_name
70
71    @property
72    def name(self):
73        if self.name_field is None or not self.name_field.value:
74            return self._entity_name
75        return self.name_field
76
77    @property
78    def texture(self):
79        return self._texture
80
81    @model_validator(mode="after")
82    def _get_texture(self, info: ValidationInfo) -> Self:
83        # can't be on Entity's validator because it's frozen and
84        # causes circular references with the PNGTexture
85        assert info.context is not None
86        i18n = I18n.of(info)
87        self._entity_name = i18n.localize_entity(self.entity.id)
88        self._texture = PNGTexture.load_id(
89            id="textures/entities" / self.entity.id + ".png", context=info.context
90        )
91        return self

Base class for a Page with optional text.

If text is required, do not subclass this type.

scale: float
offset: float
rotate: bool
default_rotation: float
name_field: hexdoc.minecraft.LocalizedStr | None
entity_name
67    @property
68    def entity_name(self):
69        return self._entity_name
name
71    @property
72    def name(self):
73        if self.name_field is None or not self.name_field.value:
74            return self._entity_name
75        return self.name_field
texture
77    @property
78    def texture(self):
79        return self._texture
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class ImagePage(hexdoc.patchouli.page.PageWithTitle):
 94class ImagePage(PageWithTitle, type="patchouli:image"):
 95    images: list[Texture]
 96    border: bool = False
 97
 98    @property
 99    def images_with_alt(self):
100        for image in self.images:
101            if self.title:
102                yield image, self.title
103            else:
104                yield image, str(image)

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

border: bool
images_with_alt
 98    @property
 99    def images_with_alt(self):
100        for image in self.images:
101            if self.title:
102                yield image, self.title
103            else:
104                yield image, str(image)
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class LinkPage(hexdoc.patchouli.page.TextPage):
107class LinkPage(TextPage, type="patchouli:link"):
108    url: str
109    link_text: LocalizedStr
url: str
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class Multiblock(hexdoc.model.base.HexdocModel):
112class Multiblock(HexdocModel):
113    """https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/multiblocks/"""
114
115    mapping: dict[str, ItemWithTexture | TagWithTexture]
116    pattern: list[list[str]]
117    symmetrical: bool = False
118    offset: tuple[int, int, int] | None = None
119
120    def bill_of_materials(self):
121        character_counts = defaultdict[str, int](int)
122
123        for layer in self.pattern:
124            for row in layer:
125                for character in row:
126                    match character:
127                        case str() if character in self.mapping:
128                            character_counts[character] += 1
129                        case " " | "0":  # air
130                            pass
131                        case _:
132                            raise ValueError(
133                                f"Character not found in multiblock mapping: `{character}`"
134                            )
135
136        materials = [
137            (self.mapping[character], count)
138            for character, count in character_counts.items()
139        ]
140
141        # sort by descending count, break ties by ascending name
142        materials.sort(key=lambda v: v[0].name.value)
143        materials.sort(key=lambda v: v[1], reverse=True)
144
145        return materials
146
147    @field_validator("mapping", mode="after")
148    @classmethod
149    def _add_default_mapping(
150        cls,
151        mapping: dict[str, ItemWithTexture | TagWithTexture],
152        info: ValidationInfo,
153    ):
154        i18n = I18n.of(info)
155        return {
156            "_": ItemWithTexture(
157                id=ItemStack("hexdoc", "any"),
158                name=i18n.localize("hexdoc.any_block"),
159                texture=PNGTexture.load_id(
160                    ResourceLocation("hexdoc", "textures/gui/any_block.png"),
161                    context=info,
162                ),
163            ),
164        } | mapping
pattern: list[list[str]]
symmetrical: bool
offset: tuple[int, int, int] | None
def bill_of_materials(self):
120    def bill_of_materials(self):
121        character_counts = defaultdict[str, int](int)
122
123        for layer in self.pattern:
124            for row in layer:
125                for character in row:
126                    match character:
127                        case str() if character in self.mapping:
128                            character_counts[character] += 1
129                        case " " | "0":  # air
130                            pass
131                        case _:
132                            raise ValueError(
133                                f"Character not found in multiblock mapping: `{character}`"
134                            )
135
136        materials = [
137            (self.mapping[character], count)
138            for character, count in character_counts.items()
139        ]
140
141        # sort by descending count, break ties by ascending name
142        materials.sort(key=lambda v: v[0].name.value)
143        materials.sort(key=lambda v: v[1], reverse=True)
144
145        return materials
class MultiblockPage(hexdoc.patchouli.page.PageWithText):
167class MultiblockPage(PageWithText, type="patchouli:multiblock"):
168    name: LocalizedStr
169    multiblock_id: ResourceLocation | None = None
170    multiblock: Multiblock | None = None
171    enable_visualize: bool = True
172
173    @model_validator(mode="after")
174    def _check_multiblock(self) -> Self:
175        if self.multiblock_id is None and self.multiblock is None:
176            raise ValueError(f"One of multiblock_id or multiblock must be set\n{self}")
177        return self

Base class for a Page with optional text.

If text is required, do not subclass this type.

multiblock_id: hexdoc.core.ResourceLocation | None
multiblock: Multiblock | None
enable_visualize: bool
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class Page(hexdoc.model.tagged_union.TypeTaggedTemplate, hexdoc.patchouli.utils.AdvancementSpoilered):
19class Page(TypeTaggedTemplate, AdvancementSpoilered, type=None):
20    """Base class for Patchouli page types.
21
22    See: https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/page-types
23    """
24
25    advancement: ResourceLocation | None = None
26    flag: str | None = None
27    anchor: str | None = None
28
29    def __init_subclass__(
30        cls,
31        *,
32        type: str | InheritType | None = Inherit,
33        template_type: str | None = None,
34        **kwargs: Unpack[ConfigDict],
35    ) -> None:
36        super().__init_subclass__(type=type, template_type=template_type, **kwargs)
37
38    @classproperty
39    @classmethod
40    def type(cls) -> ResourceLocation | None:
41        assert cls._type is not NoValue
42        return cls._type
43
44    @model_validator(mode="wrap")
45    @classmethod
46    def _pre_root(cls, value: str | Any, handler: ModelWrapValidatorHandler[Self]):
47        match value:
48            case str(text):
49                # treat a plain string as a text page
50                value = {"type": "patchouli:text", "text": text}
51            case {"type": str(raw_type)} if ":" not in raw_type:
52                # default to the patchouli namespace if not specified
53                # see: https://github.com/VazkiiMods/Patchouli/blob/b87e91a5a08d/Xplat/src/main/java/vazkii/patchouli/client/book/ClientBookRegistry.java#L110
54                value["type"] = f"patchouli:{raw_type}"
55            case _:
56                pass
57        return handler(value)
58
59    @classproperty
60    @classmethod
61    def template(cls) -> str:
62        return cls.template_id.template_path("pages")
63
64    def book_link_key(self, entry_key: str):
65        """Key to look up this page in `BookContext.book_links`, or `None` if this page
66        has no anchor."""
67        if self.anchor is not None:
68            return f"{entry_key}#{self.anchor}"
69
70    def fragment(self, entry_fragment: str):
71        """URL fragment for this page in `BookContext.book_links`, or `None` if this
72        page has no anchor."""
73        if self.anchor is not None:
74            return f"{entry_fragment}@{self.anchor}"
75
76    def redirect_path(self, entry_path: str):
77        """Path to this page when generating redirect pages, or `None` if this page has
78        no anchor."""
79        if self.anchor is not None:
80            return f"{entry_path}/{self.anchor}"
81
82    def _get_advancement(self):
83        # implements AdvancementSpoilered
84        return self.advancement
advancement: hexdoc.core.ResourceLocation | None
flag: str | None
anchor: str | None
def type(unknown):

Equivalent of classmethod(property(...)).

Use @classproperty. Do not instantiate this class directly.

def template(unknown):

Equivalent of classmethod(property(...)).

Use @classproperty. Do not instantiate this class directly.

def fragment(self, entry_fragment: str):
70    def fragment(self, entry_fragment: str):
71        """URL fragment for this page in `BookContext.book_links`, or `None` if this
72        page has no anchor."""
73        if self.anchor is not None:
74            return f"{entry_fragment}@{self.anchor}"

URL fragment for this page in BookContext.book_links, or None if this page has no anchor.

def redirect_path(self, entry_path: str):
76    def redirect_path(self, entry_path: str):
77        """Path to this page when generating redirect pages, or `None` if this page has
78        no anchor."""
79        if self.anchor is not None:
80            return f"{entry_path}/{self.anchor}"

Path to this page when generating redirect pages, or None if this page has no anchor.

def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class PageWithText(hexdoc.patchouli.page.Page):
87class PageWithText(Page, type=None):
88    """Base class for a `Page` with optional text.
89
90    If text is required, do not subclass this type.
91    """
92
93    text: FormatTree | None = None

Base class for a Page with optional text.

If text is required, do not subclass this type.

def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class PageWithTitle(hexdoc.patchouli.page.PageWithText):
 96class PageWithTitle(PageWithText, type=None):
 97    """Base class for a `Page` with optional title and text.
 98
 99    If title and/or text is required, do not subclass this type.
100    """
101
102    title: LocalizedStr | None = None

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class QuestPage(hexdoc.patchouli.page.PageWithText):
180class QuestPage(PageWithText, type="patchouli:quest"):
181    trigger: ResourceLocation | None = None
182    title: LocalizedStr = LocalizedStr.with_value("Objective")

Base class for a Page with optional text.

If text is required, do not subclass this type.

def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class RelationsPage(hexdoc.patchouli.page.PageWithText):
185class RelationsPage(PageWithText, type="patchouli:relations"):
186    entries: list[ResourceLocation]
187    title: LocalizedStr = LocalizedStr.with_value("Related Chapters")
188
189    @pass_context
190    def get_entries(self, context: Context) -> list[ResourceLocation]:
191        for entry in self.entries:
192            if entry not in context["book"].all_entries:
193                raise ValueError(f"Broken entry reference in relations: {entry}")
194        return self.entries

Base class for a Page with optional text.

If text is required, do not subclass this type.

@pass_context
def get_entries( self, context: jinja2.runtime.Context) -> list[hexdoc.core.ResourceLocation]:
189    @pass_context
190    def get_entries(self, context: Context) -> list[ResourceLocation]:
191        for entry in self.entries:
192            if entry not in context["book"].all_entries:
193                raise ValueError(f"Broken entry reference in relations: {entry}")
194        return self.entries
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class SmeltingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
197class SmeltingPage(PageWithDoubleRecipe[SmeltingRecipe], type="patchouli:smelting"):
198    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class SmithingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
201class SmithingPage(PageWithDoubleRecipe[SmithingRecipe], type="patchouli:smithing"):
202    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class SmokingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
205class SmokingPage(PageWithDoubleRecipe[SmokingRecipe], type="patchouli:smoking"):
206    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class SpotlightPage(hexdoc.patchouli.page.PageWithText):
215class SpotlightPage(PageWithText, type="patchouli:spotlight"):
216    title_field: LocalizedStr | None = Field(default=None, alias="title")
217    item: ItemWithTexture
218    link_recipe: bool = False
219
220    @property
221    def title(self) -> LocalizedStr | None:
222        return self.title_field or self.item.name

Base class for a Page with optional text.

If text is required, do not subclass this type.

title_field: hexdoc.minecraft.LocalizedStr | None
title: hexdoc.minecraft.LocalizedStr | None
220    @property
221    def title(self) -> LocalizedStr | None:
222        return self.title_field or self.item.name
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class StonecuttingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
209class StonecuttingPage(
210    PageWithDoubleRecipe[StonecuttingRecipe], type="patchouli:stonecutting"
211):
212    pass

Base class for a Page with optional title and text.

If title and/or text is required, do not subclass this type.

class TextPage(hexdoc.patchouli.page.Page):
33class TextPage(Page, type="patchouli:text"):
34    title: LocalizedStr | None = None
35    text: FormatTree
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.