Remove most of the HexContext arguments

This commit is contained in:
object-Object 2023-08-26 21:13:31 -04:00
parent c9f4941bad
commit 4e205871c8
8 changed files with 110 additions and 75 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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 (

View file

@ -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

View file

@ -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

View file

@ -345,3 +345,7 @@ class ModResourceLoader:
class LoaderContext(ValidationContext):
loader: ModResourceLoader
@property
def props(self):
return self.loader.props