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]):
46class BlastingPage(PageWithDoubleRecipe[BlastingRecipe], type="patchouli:blasting"):
47    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]):
50class CampfireCookingPage(
51    PageWithDoubleRecipe[CampfireCookingRecipe], type="patchouli:campfire_cooking"
52):
53    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.abstract_pages.PageWithDoubleRecipe, hexdoc.patchouli.page.abstract_pages.PageWithRecipeAccumulator, abc.ABC, typing.Generic[~_T_Recipe]):
56class CraftingPage(
57    PageWithDoubleRecipeAccumulator[CraftingRecipe], type="patchouli:crafting"
58):
59    @classmethod
60    @override
61    def accumulator_type(cls):
62        return CraftingAccumulatorPage

Base class for a Page with optional title and text.

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

@classmethod
@override
def accumulator_type(cls):
59    @classmethod
60    @override
61    def accumulator_type(cls):
62        return CraftingAccumulatorPage

Returns the RecipeAccumulator class for this page type.

The template type of the returned class must match that of this class.

class EmptyPage(hexdoc.patchouli.page.Page):
71class EmptyPage(Page, type="patchouli:empty", template_type="patchouli:page"):
72    draw_filler: bool = True
draw_filler: bool
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
 75class EntityPage(PageWithText, type="patchouli:entity"):
 76    _entity_name: LocalizedStr = PrivateAttr()
 77    _texture: PNGTexture = PrivateAttr()
 78
 79    entity: Entity
 80    scale: float = 1
 81    offset: float = 0
 82    rotate: bool = True
 83    default_rotation: float = -45
 84    name_field: LocalizedStr | None = Field(default=None, serialization_alias="name")
 85
 86    @property
 87    def entity_name(self):
 88        return self._entity_name
 89
 90    @property
 91    def name(self):
 92        if self.name_field is None or not self.name_field.value:
 93            return self._entity_name
 94        return self.name_field
 95
 96    @property
 97    def texture(self):
 98        return self._texture
 99
100    @model_validator(mode="after")
101    def _get_texture(self, info: ValidationInfo) -> Self:
102        # can't be on Entity's validator because it's frozen and
103        # causes circular references with the PNGTexture
104        assert info.context is not None
105        i18n = I18n.of(info)
106        self._entity_name = i18n.localize_entity(self.entity.id)
107        self._texture = PNGTexture.load_id(
108            id="textures/entities" / self.entity.id + ".png", context=info.context
109        )
110        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
86    @property
87    def entity_name(self):
88        return self._entity_name
name
90    @property
91    def name(self):
92        if self.name_field is None or not self.name_field.value:
93            return self._entity_name
94        return self.name_field
texture
96    @property
97    def texture(self):
98        return self._texture
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
113class ImagePage(PageWithTitle, type="patchouli:image"):
114    images: list[Texture]
115    border: bool = False
116
117    @property
118    def images_with_alt(self):
119        for image in self.images:
120            if self.title:
121                yield image, self.title
122            else:
123                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
117    @property
118    def images_with_alt(self):
119        for image in self.images:
120            if self.title:
121                yield image, self.title
122            else:
123                yield image, str(image)
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
126class LinkPage(TextPage, type="patchouli:link"):
127    url: str
128    link_text: LocalizedStr
url: str
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
131class Multiblock(HexdocModel):
132    """https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/multiblocks/"""
133
134    mapping: dict[str, ItemWithTexture | TagWithTexture]
135    pattern: list[list[str]]
136    symmetrical: bool = False
137    offset: tuple[int, int, int] | None = None
138
139    def bill_of_materials(self):
140        character_counts = defaultdict[str, int](int)
141
142        for layer in self.pattern:
143            for row in layer:
144                for character in row:
145                    match character:
146                        case str() if character in self.mapping:
147                            character_counts[character] += 1
148                        case " " | "0":  # air
149                            pass
150                        case _:
151                            raise ValueError(
152                                f"Character not found in multiblock mapping: `{character}`"
153                            )
154
155        materials = [
156            (self.mapping[character], count)
157            for character, count in character_counts.items()
158        ]
159
160        # sort by descending count, break ties by ascending name
161        materials.sort(key=lambda v: v[0].name.value)
162        materials.sort(key=lambda v: v[1], reverse=True)
163
164        return materials
165
166    @field_validator("mapping", mode="after")
167    @classmethod
168    def _add_default_mapping(
169        cls,
170        mapping: dict[str, ItemWithTexture | TagWithTexture],
171        info: ValidationInfo,
172    ):
173        i18n = I18n.of(info)
174        return {
175            "_": ItemWithTexture(
176                id=ItemStack("hexdoc", "any"),
177                name=i18n.localize("hexdoc.any_block"),
178                texture=PNGTexture.load_id(
179                    ResourceLocation("hexdoc", "textures/gui/any_block.png"),
180                    context=info,
181                ),
182            ),
183        } | mapping
pattern: list[list[str]]
symmetrical: bool
offset: tuple[int, int, int] | None
def bill_of_materials(self):
139    def bill_of_materials(self):
140        character_counts = defaultdict[str, int](int)
141
142        for layer in self.pattern:
143            for row in layer:
144                for character in row:
145                    match character:
146                        case str() if character in self.mapping:
147                            character_counts[character] += 1
148                        case " " | "0":  # air
149                            pass
150                        case _:
151                            raise ValueError(
152                                f"Character not found in multiblock mapping: `{character}`"
153                            )
154
155        materials = [
156            (self.mapping[character], count)
157            for character, count in character_counts.items()
158        ]
159
160        # sort by descending count, break ties by ascending name
161        materials.sort(key=lambda v: v[0].name.value)
162        materials.sort(key=lambda v: v[1], reverse=True)
163
164        return materials
class MultiblockPage(hexdoc.patchouli.page.PageWithText):
186class MultiblockPage(PageWithText, type="patchouli:multiblock"):
187    name: LocalizedStr
188    multiblock_id: ResourceLocation | None = None
189    multiblock: Multiblock | None = None
190    enable_visualize: bool = True
191
192    @model_validator(mode="after")
193    def _check_multiblock(self) -> Self:
194        if self.multiblock_id is None and self.multiblock is None:
195            raise ValueError(f"One of multiblock_id or multiblock must be set\n{self}")
196        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:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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, hexdoc.patchouli.utils.Flagged):
35class Page(TypeTaggedTemplate, AdvancementSpoilered, Flagged, type=None):
36    """Base class for Patchouli page types.
37
38    See: https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/page-types
39    """
40
41    advancement: ResourceLocation | None = None
42    anchor: str | None = None
43
44    def __init_subclass__(
45        cls,
46        *,
47        type: str | InheritType | None = Inherit,
48        template_type: str | None = None,
49        **kwargs: Unpack[ConfigDict],
50    ) -> None:
51        super().__init_subclass__(type=type, template_type=template_type, **kwargs)
52
53    @classproperty
54    @classmethod
55    def type(cls) -> ResourceLocation | None:
56        assert cls._type is not NoValue
57        return cls._type
58
59    @model_validator(mode="wrap")
60    @classmethod
61    def _pre_root(cls, value: str | Any, handler: ModelWrapValidatorHandler[Self]):
62        match value:
63            case str(text):
64                # treat a plain string as a text page
65                value = {"type": "patchouli:text", "text": text}
66            case {"type": str(raw_type)} if ":" not in raw_type:
67                # default to the patchouli namespace if not specified
68                # see: https://github.com/VazkiiMods/Patchouli/blob/b87e91a5a08d/Xplat/src/main/java/vazkii/patchouli/client/book/ClientBookRegistry.java#L110
69                value["type"] = f"patchouli:{raw_type}"
70            case _:
71                pass
72        return handler(value)
73
74    @classproperty
75    @classmethod
76    def template(cls) -> str:
77        return cls.template_id.template_path("pages")
78
79    def book_link_key(self, entry_key: str):
80        """Key to look up this page in `BookContext.book_links`, or `None` if this page
81        has no anchor."""
82        if self.anchor is not None:
83            return f"{entry_key}#{self.anchor}"
84
85    def fragment(self, entry_fragment: str):
86        """URL fragment for this page in `BookContext.book_links`, or `None` if this
87        page has no anchor."""
88        if self.anchor is not None:
89            return f"{entry_fragment}@{self.anchor}"
90
91    def redirect_path(self, entry_path: str):
92        """Path to this page when generating redirect pages, or `None` if this page has
93        no anchor."""
94        if self.anchor is not None:
95            return f"{entry_path}/{self.anchor}"
96
97    def _get_advancement(self):
98        # implements AdvancementSpoilered
99        return self.advancement
advancement: hexdoc.core.ResourceLocation | 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):
85    def fragment(self, entry_fragment: str):
86        """URL fragment for this page in `BookContext.book_links`, or `None` if this
87        page has no anchor."""
88        if self.anchor is not None:
89            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):
91    def redirect_path(self, entry_path: str):
92        """Path to this page when generating redirect pages, or `None` if this page has
93        no anchor."""
94        if self.anchor is not None:
95            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:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
102class PageWithText(Page, type=None):
103    """Base class for a `Page` with optional text.
104
105    If text is required, do not subclass this type.
106    """
107
108    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:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
111class PageWithTitle(PageWithText, type=None):
112    """Base class for a `Page` with optional title and text.
113
114    If title and/or text is required, do not subclass this type.
115    """
116
117    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:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
199class QuestPage(PageWithText, type="patchouli:quest"):
200    trigger: ResourceLocation | None = None
201    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:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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):
204class RelationsPage(PageWithText, type="patchouli:relations"):
205    entries: list[ResourceLocation]
206    title: LocalizedStr = LocalizedStr.with_value("Related Chapters")
207
208    @pass_context
209    def get_entries(self, context: Context) -> list[ResourceLocation]:
210        for entry in self.entries:
211            if entry not in context["book"].all_entries:
212                raise ValueError(f"Broken entry reference in relations: {entry}")
213        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]:
208    @pass_context
209    def get_entries(self, context: Context) -> list[ResourceLocation]:
210        for entry in self.entries:
211            if entry not in context["book"].all_entries:
212                raise ValueError(f"Broken entry reference in relations: {entry}")
213        return self.entries
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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]):
216class SmeltingPage(PageWithDoubleRecipe[SmeltingRecipe], type="patchouli:smelting"):
217    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]):
220class SmithingPage(PageWithDoubleRecipe[SmithingRecipe], type="patchouli:smithing"):
221    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]):
224class SmokingPage(PageWithDoubleRecipe[SmokingRecipe], type="patchouli:smoking"):
225    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):
234class SpotlightPage(PageWithText, type="patchouli:spotlight"):
235    title_field: LocalizedStr | None = Field(default=None, alias="title")
236    item: ItemWithTexture
237    link_recipe: bool = False
238
239    @property
240    def title(self) -> LocalizedStr | None:
241        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
239    @property
240    def title(self) -> LocalizedStr | None:
241        return self.title_field or self.item.name
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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]):
228class StonecuttingPage(
229    PageWithDoubleRecipe[StonecuttingRecipe], type="patchouli:stonecutting"
230):
231    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):
41class TextPage(Page, type="patchouli:text"):
42    title: LocalizedStr | None = None
43    text: FormatTree
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
358def init_private_attributes(self: BaseModel, context: Any, /) -> None:
359    """This function is meant to behave like a BaseModel method to initialise private attributes.
360
361    It takes context as an argument since that's what pydantic-core passes when calling it.
362
363    Args:
364        self: The BaseModel instance.
365        context: The context.
366    """
367    if getattr(self, '__pydantic_private__', None) is None:
368        pydantic_private = {}
369        for name, private_attr in self.__private_attributes__.items():
370            default = private_attr.get_default()
371            if default is not PydanticUndefined:
372                pydantic_private[name] = default
373        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.