Remove most of the HexContext arguments
This commit is contained in:
parent
c9f4941bad
commit
4e205871c8
8 changed files with 110 additions and 75 deletions
|
@ -1,6 +1,5 @@
|
|||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Self
|
||||
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
|
@ -28,7 +27,7 @@ class HexContext(BookContext):
|
|||
patterns: dict[ResourceLocation, PatternInfo] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _post_root_load_patterns(self) -> Self:
|
||||
def _load_patterns(self):
|
||||
# load the tag that specifies which patterns are random per world
|
||||
per_world = Tag.load(
|
||||
registry="action",
|
||||
|
@ -44,11 +43,12 @@ class HexContext(BookContext):
|
|||
self._add_pattern(pattern, signatures)
|
||||
|
||||
# export patterns so addons can use them
|
||||
pattern_metadata = PatternMetadata(
|
||||
patterns=self.patterns,
|
||||
)
|
||||
self.loader.export(
|
||||
path=PatternMetadata.path(self.props.modid),
|
||||
data=PatternMetadata(
|
||||
patterns=self.patterns,
|
||||
).model_dump_json(warnings=False),
|
||||
data=pattern_metadata.model_dump_json(warnings=False),
|
||||
)
|
||||
|
||||
# add external patterns AFTER exporting so we don't reexport them
|
||||
|
|
|
@ -5,17 +5,16 @@ import sys
|
|||
from argparse import ArgumentParser
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Self, Sequence
|
||||
from typing import Self, Sequence
|
||||
|
||||
from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader, StrictUndefined
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
from hexdoc.hexcasting.hex_book import HexContext
|
||||
from hexdoc.minecraft.i18n import I18n
|
||||
from hexdoc.patchouli.book import Book
|
||||
from hexdoc.utils import Properties
|
||||
from hexdoc.utils.cd import cd
|
||||
from hexdoc.utils.deserialize import cast_or_raise
|
||||
from hexdoc.utils.model import init_context
|
||||
from hexdoc.utils.resource_loader import ModResourceLoader
|
||||
|
||||
from .jinja_extensions import IncludeRawExtension, hexdoc_block, hexdoc_wrap
|
||||
|
@ -89,53 +88,44 @@ def main(args: Args | None = None) -> None:
|
|||
props = Properties.load(args.properties_file)
|
||||
with ModResourceLoader.load_all(props) as loader:
|
||||
_, book_data = Book.load_book_json(loader, props.book)
|
||||
book = Book.load_all(
|
||||
book_data,
|
||||
HexContext(
|
||||
props=props,
|
||||
loader=loader,
|
||||
i18n=I18n(
|
||||
props=props,
|
||||
loader=loader,
|
||||
enabled=cast_or_raise(book_data["i18n"], bool),
|
||||
),
|
||||
macros=cast_or_raise(book_data["macros"], dict[Any, Any]),
|
||||
),
|
||||
)
|
||||
|
||||
# set up Jinja environment
|
||||
env = SandboxedEnvironment(
|
||||
# search order: template_dirs, template_packages, built-in hexdoc templates
|
||||
loader=ChoiceLoader(
|
||||
[FileSystemLoader(props.template_dirs)]
|
||||
+ [
|
||||
PackageLoader(name, str(path))
|
||||
for name, path in props.template_packages
|
||||
]
|
||||
),
|
||||
undefined=StrictUndefined,
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True,
|
||||
autoescape=True,
|
||||
extensions=[
|
||||
IncludeRawExtension,
|
||||
],
|
||||
)
|
||||
env.filters |= { # type: ignore
|
||||
"hexdoc_block": hexdoc_block,
|
||||
"hexdoc_wrap": hexdoc_wrap,
|
||||
}
|
||||
with init_context(book_data):
|
||||
context = HexContext(loader=loader)
|
||||
|
||||
# load and render template
|
||||
template = env.get_template(props.template)
|
||||
docs = strip_empty_lines(
|
||||
template.render(
|
||||
**props.template_args,
|
||||
book=book,
|
||||
props=props,
|
||||
mod_metadata=loader.mod_metadata,
|
||||
)
|
||||
book = Book.model_validate(book_data, context=context)
|
||||
|
||||
# set up Jinja environment
|
||||
env = SandboxedEnvironment(
|
||||
# search order: template_dirs, template_packages
|
||||
loader=ChoiceLoader(
|
||||
[FileSystemLoader(props.template_dirs)]
|
||||
+ [
|
||||
PackageLoader(name, str(path))
|
||||
for name, path in props.template_packages
|
||||
]
|
||||
),
|
||||
undefined=StrictUndefined,
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True,
|
||||
autoescape=True,
|
||||
extensions=[
|
||||
IncludeRawExtension,
|
||||
],
|
||||
)
|
||||
env.filters |= { # type: ignore
|
||||
"hexdoc_block": hexdoc_block,
|
||||
"hexdoc_wrap": hexdoc_wrap,
|
||||
}
|
||||
|
||||
# load and render template
|
||||
template = env.get_template(props.template)
|
||||
docs = strip_empty_lines(
|
||||
template.render(
|
||||
**props.template_args,
|
||||
book=book,
|
||||
props=props,
|
||||
)
|
||||
)
|
||||
|
||||
# if there's an output file specified, write to it
|
||||
# otherwise just print the generated docs
|
||||
|
|
|
@ -5,7 +5,7 @@ from dataclasses import InitVar
|
|||
from functools import total_ordering
|
||||
from typing import Any, Callable, Self
|
||||
|
||||
from pydantic import ValidationInfo, model_validator
|
||||
from pydantic import Field, ValidationInfo, model_validator
|
||||
from pydantic.dataclasses import dataclass
|
||||
from pydantic.functional_validators import ModelWrapValidatorHandler
|
||||
|
||||
|
@ -22,7 +22,7 @@ from hexdoc.utils.deserialize import (
|
|||
decode_and_flatten_json_dict,
|
||||
isinstance_or_raise,
|
||||
)
|
||||
from hexdoc.utils.model import ValidationContext
|
||||
from hexdoc.utils.resource_loader import LoaderContext
|
||||
|
||||
|
||||
@total_ordering
|
||||
|
@ -201,5 +201,16 @@ class I18n:
|
|||
return self.localize(f"key.{key}")
|
||||
|
||||
|
||||
class I18nContext(ValidationContext):
|
||||
i18n: I18n
|
||||
class I18nContext(LoaderContext):
|
||||
i18n: I18n = Field(default=None)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _init_i18n(self, info: ValidationInfo):
|
||||
if self.i18n is None: # pyright: ignore[reportUnnecessaryComparison]
|
||||
context = cast_or_raise(info.context, dict)
|
||||
self.i18n = I18n(
|
||||
props=self.props,
|
||||
loader=self.loader,
|
||||
enabled=cast_or_raise(context["i18n"], bool),
|
||||
)
|
||||
return self
|
||||
|
|
|
@ -8,14 +8,14 @@ from contextlib import nullcontext
|
|||
from enum import Enum, auto
|
||||
from typing import Any, Literal, Self
|
||||
|
||||
from pydantic import ValidationInfo, field_validator, model_validator
|
||||
from pydantic import Field, ValidationInfo, model_validator
|
||||
from pydantic.dataclasses import dataclass
|
||||
from pydantic.functional_validators import ModelWrapValidatorHandler
|
||||
|
||||
from hexdoc.minecraft import LocalizedStr
|
||||
from hexdoc.minecraft.i18n import I18nContext
|
||||
from hexdoc.patchouli.text.html import HTMLElement, HTMLStream
|
||||
from hexdoc.utils import DEFAULT_CONFIG, HexDocModel, PropsContext
|
||||
from hexdoc.utils import DEFAULT_CONFIG, HexDocModel
|
||||
from hexdoc.utils.deserialize import cast_or_raise
|
||||
from hexdoc.utils.properties import Properties
|
||||
from hexdoc.utils.resource import ResourceLocation
|
||||
|
@ -69,16 +69,17 @@ _COLORS = {
|
|||
|
||||
class FormattingContext(
|
||||
I18nContext,
|
||||
PropsContext,
|
||||
LoaderContext,
|
||||
arbitrary_types_allowed=True,
|
||||
):
|
||||
macros: dict[str, str]
|
||||
macros: dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
@field_validator("macros")
|
||||
@classmethod
|
||||
def _add_default_macros(cls, macros: dict[str, str]) -> dict[str, str]:
|
||||
return DEFAULT_MACROS | macros
|
||||
@model_validator(mode="after")
|
||||
def _add_macros(self, info: ValidationInfo) -> Self:
|
||||
# precedence: ctx arguments, book macros, default macros
|
||||
context = cast_or_raise(info.context, dict)
|
||||
self.macros = DEFAULT_MACROS | context["macros"] | self.macros
|
||||
return self
|
||||
|
||||
|
||||
class BookLink(HexDocModel):
|
||||
|
|
|
@ -8,7 +8,6 @@ __all__ = [
|
|||
"NoValueType",
|
||||
"TagValue",
|
||||
"Properties",
|
||||
"PropsContext",
|
||||
"Entity",
|
||||
"ItemStack",
|
||||
"ResLoc",
|
||||
|
@ -19,7 +18,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
from .model import DEFAULT_CONFIG, HexDocModel, ValidationContext
|
||||
from .properties import Properties, PropsContext
|
||||
from .properties import Properties
|
||||
from .resource import Entity, ItemStack, ResLoc, ResourceLocation
|
||||
from .resource_loader import LoaderContext, ModResourceLoader
|
||||
from .tagged_union import (
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Self, dataclass_transform
|
||||
from contextlib import contextmanager
|
||||
from contextvars import ContextVar
|
||||
from typing import TYPE_CHECKING, Any, Iterator, Self, dataclass_transform
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, model_validator
|
||||
from pydantic.config import ConfigDict
|
||||
|
@ -9,16 +11,48 @@ DEFAULT_CONFIG = ConfigDict(
|
|||
extra="forbid",
|
||||
)
|
||||
|
||||
_init_context_var = ContextVar[Any]("_init_context_var", default=None)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def init_context(value: Any) -> Iterator[None]:
|
||||
"""https://docs.pydantic.dev/latest/usage/validators/#using-validation-context-with-basemodel-initialization"""
|
||||
token = _init_context_var.set(value)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_init_context_var.reset(token)
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class ValidationContext(BaseModel):
|
||||
"""Base class for Pydantic validation context for `HexDocModel`."""
|
||||
class HexDocBaseModel(BaseModel):
|
||||
"""Base class for all Pydantic models in hexdoc. You should probably use
|
||||
`HexDocModel` or `ValidationContext` instead.
|
||||
|
||||
Sets the default model config, and overrides __init__ to allow using the
|
||||
`init_context` context manager to set validation context for constructors.
|
||||
"""
|
||||
|
||||
model_config = DEFAULT_CONFIG
|
||||
|
||||
def __init__(__pydantic_self__, **data: Any) -> None: # type: ignore
|
||||
__tracebackhide__ = True
|
||||
__pydantic_self__.__pydantic_validator__.validate_python(
|
||||
data,
|
||||
self_instance=__pydantic_self__,
|
||||
context=_init_context_var.get(),
|
||||
)
|
||||
|
||||
__init__.__pydantic_base_init__ = True # type: ignore
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class HexDocModel(BaseModel):
|
||||
class ValidationContext(HexDocBaseModel):
|
||||
"""Base class for Pydantic validation context for `HexDocModel`."""
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class HexDocModel(HexDocBaseModel):
|
||||
"""Base class for most Pydantic models in hexdoc.
|
||||
|
||||
Includes type overrides to require using subclasses of `ValidationContext` for
|
||||
|
|
|
@ -6,7 +6,7 @@ from typing import Annotated, Any, Self
|
|||
|
||||
from pydantic import AfterValidator, HttpUrl
|
||||
|
||||
from .model import HexDocModel, StripHiddenModel, ValidationContext
|
||||
from .model import HexDocModel, StripHiddenModel
|
||||
from .resource import ResourceDir, ResourceLocation
|
||||
from .toml_placeholders import load_toml_with_placeholders
|
||||
|
||||
|
@ -79,7 +79,3 @@ class Properties(StripHiddenModel):
|
|||
def get_asset_url(self, id: ResourceLocation) -> str:
|
||||
base_url = self.base_asset_urls[id.namespace]
|
||||
return f"{base_url}/{id.file_path_stub('assets').as_posix()}"
|
||||
|
||||
|
||||
class PropsContext(ValidationContext):
|
||||
props: Properties
|
||||
|
|
|
@ -345,3 +345,7 @@ class ModResourceLoader:
|
|||
|
||||
class LoaderContext(ValidationContext):
|
||||
loader: ModResourceLoader
|
||||
|
||||
@property
|
||||
def props(self):
|
||||
return self.loader.props
|
||||
|
|
Loading…
Reference in a new issue