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]):
48class BlastingPage(PageWithDoubleRecipe[BlastingRecipe], type="patchouli:blasting"):
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 CampfireCookingPage(hexdoc.patchouli.page.PageWithTitle, typing.Generic[~_T_Recipe]):
52class CampfireCookingPage(
53    PageWithDoubleRecipe[CampfireCookingRecipe], type="patchouli:campfire_cooking"
54):
55    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]):
58class CraftingPage(
59    PageWithDoubleRecipeAccumulator[CraftingRecipe], type="patchouli:crafting"
60):
61    @classmethod
62    @override
63    def accumulator_type(cls):
64        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):
61    @classmethod
62    @override
63    def accumulator_type(cls):
64        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):
73class EmptyPage(Page, type="patchouli:empty", template_type="patchouli:page"):
74    draw_filler: bool = True
draw_filler: bool
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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):
 77class EntityPage(PageWithText, type="patchouli:entity"):
 78    _entity_name: LocalizedStr = PrivateAttr()
 79    _texture: PNGTexture = PrivateAttr()
 80
 81    entity: Entity
 82    scale: float = 1
 83    offset: float = 0
 84    rotate: bool = True
 85    default_rotation: float = -45
 86    name_field: LocalizedStr | None = Field(default=None, serialization_alias="name")
 87
 88    @property
 89    def entity_name(self):
 90        return self._entity_name
 91
 92    @property
 93    def name(self):
 94        if self.name_field is None or not self.name_field.value:
 95            return self._entity_name
 96        return self.name_field
 97
 98    @property
 99    def texture(self):
100        return self._texture
101
102    @model_validator(mode="after")
103    def _get_texture(self, info: ValidationInfo) -> Self:
104        # can't be on Entity's validator because it's frozen and
105        # causes circular references with the PNGTexture
106        assert info.context is not None
107        i18n = I18n.of(info)
108        self._entity_name = i18n.localize_entity(self.entity.id)
109        self._texture = PNGTexture.load_id(
110            id="textures/entities" / self.entity.id + ".png", context=info.context
111        )
112        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
88    @property
89    def entity_name(self):
90        return self._entity_name
name
92    @property
93    def name(self):
94        if self.name_field is None or not self.name_field.value:
95            return self._entity_name
96        return self.name_field
texture
 98    @property
 99    def texture(self):
100        return self._texture
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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):
115class ImagePage(PageWithTitle, type="patchouli:image"):
116    images: list[Texture]
117    border: bool = False
118
119    @property
120    def images_with_alt(self):
121        for image in self.images:
122            if self.title:
123                yield image, self.title
124            else:
125                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
119    @property
120    def images_with_alt(self):
121        for image in self.images:
122            if self.title:
123                yield image, self.title
124            else:
125                yield image, str(image)
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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):
128class LinkPage(TextPage, type="patchouli:link"):
129    url: str
130    link_text: LocalizedStr
url: str
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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):
133class Multiblock(HexdocModel):
134    """https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/multiblocks/"""
135
136    mapping: dict[str, ItemWithTexture | TagWithTexture]
137    pattern: list[list[str]]
138    symmetrical: bool = False
139    offset: tuple[int, int, int] | None = None
140
141    def bill_of_materials(self):
142        character_counts = defaultdict[str, int](int)
143
144        for layer in self.pattern:
145            for row in layer:
146                for character in row:
147                    match character:
148                        case str() if character in self.mapping:
149                            character_counts[character] += 1
150                        case " " | "0":  # air
151                            pass
152                        case _:
153                            raise ValueError(
154                                f"Character not found in multiblock mapping: `{character}`"
155                            )
156
157        materials = [
158            (self.mapping[character], count)
159            for character, count in character_counts.items()
160        ]
161
162        # sort by descending count, break ties by ascending name
163        materials.sort(key=lambda v: v[0].name.value)
164        materials.sort(key=lambda v: v[1], reverse=True)
165
166        return materials
167
168    @field_validator("mapping", mode="after")
169    @classmethod
170    def _add_default_mapping(
171        cls,
172        mapping: dict[str, ItemWithTexture | TagWithTexture],
173        info: ValidationInfo,
174    ):
175        i18n = I18n.of(info)
176        return {
177            "_": ItemWithTexture(
178                id=ItemStack("hexdoc", "any"),
179                name=i18n.localize("hexdoc.any_block"),
180                texture=PNGTexture.load_id(
181                    ResourceLocation("hexdoc", "textures/gui/any_block.png"),
182                    context=info,
183                ),
184            ),
185        } | mapping
pattern: list[list[str]]
symmetrical: bool
offset: tuple[int, int, int] | None
def bill_of_materials(self):
141    def bill_of_materials(self):
142        character_counts = defaultdict[str, int](int)
143
144        for layer in self.pattern:
145            for row in layer:
146                for character in row:
147                    match character:
148                        case str() if character in self.mapping:
149                            character_counts[character] += 1
150                        case " " | "0":  # air
151                            pass
152                        case _:
153                            raise ValueError(
154                                f"Character not found in multiblock mapping: `{character}`"
155                            )
156
157        materials = [
158            (self.mapping[character], count)
159            for character, count in character_counts.items()
160        ]
161
162        # sort by descending count, break ties by ascending name
163        materials.sort(key=lambda v: v[0].name.value)
164        materials.sort(key=lambda v: v[1], reverse=True)
165
166        return materials
class MultiblockPage(hexdoc.patchouli.page.PageWithText):
188class MultiblockPage(PageWithText, type="patchouli:multiblock"):
189    name: LocalizedStr
190    multiblock_id: ResourceLocation | None = None
191    multiblock: Multiblock | None = None
192    enable_visualize: bool = True
193
194    _texture: ImageTexture | None = PrivateAttr(None)
195
196    @property
197    def texture(self):
198        return self._texture
199
200    @property
201    def _texture_id(self):
202        if self.multiblock_id:
203            return self.multiblock_id.with_path(
204                f"textures/multiblock/hexdoc/{self.multiblock_id.path}.png"
205            )
206
207    @model_validator(mode="after")
208    def _check_multiblock(self, info: ValidationInfo) -> Self:
209        if self.multiblock_id is None and self.multiblock is None:
210            raise ValueError(f"One of multiblock_id or multiblock must be set\n{self}")
211
212        if texture_id := self._texture_id:
213            self._texture = validate_texture(
214                texture_id,
215                context=info,
216                model_type=ImageTexture,
217            )
218
219        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
texture
196    @property
197    def texture(self):
198        return self._texture
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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):
222class QuestPage(PageWithText, type="patchouli:quest"):
223    trigger: ResourceLocation | None = None
224    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:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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):
227class RelationsPage(PageWithText, type="patchouli:relations"):
228    entries: list[ResourceLocation]
229    title: LocalizedStr = LocalizedStr.with_value("Related Chapters")
230
231    @pass_context
232    def get_entries(self, context: Context) -> list[ResourceLocation]:
233        for entry in self.entries:
234            if entry not in context["book"].all_entries:
235                raise ValueError(f"Broken entry reference in relations: {entry}")
236        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]:
231    @pass_context
232    def get_entries(self, context: Context) -> list[ResourceLocation]:
233        for entry in self.entries:
234            if entry not in context["book"].all_entries:
235                raise ValueError(f"Broken entry reference in relations: {entry}")
236        return self.entries
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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]):
239class SmeltingPage(PageWithDoubleRecipe[SmeltingRecipe], type="patchouli:smelting"):
240    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]):
243class SmithingPage(PageWithDoubleRecipe[SmithingRecipe], type="patchouli:smithing"):
244    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]):
247class SmokingPage(PageWithDoubleRecipe[SmokingRecipe], type="patchouli:smoking"):
248    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):
257class SpotlightPage(PageWithText, type="patchouli:spotlight"):
258    title_field: LocalizedStr | None = Field(default=None, alias="title")
259    item: ItemWithTexture
260    link_recipe: bool = False
261
262    @property
263    def title(self) -> LocalizedStr | None:
264        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
262    @property
263    def title(self) -> LocalizedStr | None:
264        return self.title_field or self.item.name
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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]):
251class StonecuttingPage(
252    PageWithDoubleRecipe[StonecuttingRecipe], type="patchouli:stonecutting"
253):
254    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):
43class TextPage(Page, type="patchouli:text"):
44    title: LocalizedStr | None = None
45    text: FormatTree
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
365def init_private_attributes(self: BaseModel, context: Any, /) -> None:
366    """This function is meant to behave like a BaseModel method to initialize private attributes.
367
368    It takes context as an argument since that's what pydantic-core passes when calling it.
369
370    Args:
371        self: The BaseModel instance.
372        context: The context.
373    """
374    if getattr(self, '__pydantic_private__', None) is None:
375        pydantic_private = {}
376        for name, private_attr in self.__private_attributes__.items():
377            # Avoid needlessly creating a new dict for the validated data:
378            if private_attr.default_factory_takes_validated_data:
379                default = private_attr.get_default(
380                    call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
381                )
382            else:
383                default = private_attr.get_default(call_default_factory=True)
384            if default is not PydanticUndefined:
385                pydantic_private[name] = default
386        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialize 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.