Skip to content

Commit

Permalink
feat: add ctx.validate()
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Jun 28, 2021
1 parent e08d004 commit e1210cf
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 16 deletions.
15 changes: 3 additions & 12 deletions beet/toolchain/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from beet.core.utils import FileSystemPath, JsonDict, TextComponent

from .pipeline import FormattedPipelineException
from .utils import format_validation_error


class InvalidProjectConfig(FormattedPipelineException):
Expand Down Expand Up @@ -203,16 +204,6 @@ def config_error_handler(path: FileSystemPath = "<unknown>"):
except FileNotFoundError as exc:
raise InvalidProjectConfig(f"{path}: File not found.") from exc
except ValidationError as exc:
errors = [
(
"config" + "".join(json.dumps([item]) for item in error["loc"]),
error["msg"].capitalize(),
)
for error in exc.errors()
]
width = max(len(loc) for loc, _ in errors) + 1
message = f"{path}: Validation error.\n\n" + "\n".join(
"{loc:<{width}} => {msg}.".format(loc=loc, width=width, msg=msg)
for loc, msg in errors
)
message = f"{path}: Validation error.\n\n"
message += format_validation_error("config", exc)
raise InvalidProjectConfig(message) from exc
46 changes: 42 additions & 4 deletions beet/toolchain/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"ProjectCache",
"Context",
"ContextContainer",
"InvalidContextOptions",
]


Expand All @@ -15,25 +16,50 @@
from pathlib import Path
from typing import Any, Callable, List, Optional, Set, Tuple, TypeVar, overload

from pydantic import ValidationError

from beet.core.cache import MultiCache
from beet.core.container import Container
from beet.core.utils import FileSystemPath, JsonDict, TextComponent, extra_field
from beet.library.data_pack import DataPack
from beet.library.resource_pack import ResourcePack

from .generator import Generator
from .pipeline import GenericPipeline, GenericPlugin, GenericPluginSpec
from .pipeline import (
FormattedPipelineException,
GenericPipeline,
GenericPlugin,
GenericPluginSpec,
PipelineFallthroughException,
)
from .template import TemplateManager
from .tree import generate_tree
from .utils import import_from_string
from .utils import format_validation_error, import_from_string
from .worker import WorkerPoolHandle

InjectedType = TypeVar("InjectedType")
T = TypeVar("T")

Plugin = GenericPlugin["Context"]
PluginSpec = GenericPluginSpec["Context"]


class InvalidContextOptions(FormattedPipelineException):
"""Raised when validating context metadata."""

key: str
explanation: Optional[str]

def __init__(self, key: str, explanation: Optional[str] = None):
super().__init__(key, explanation)
self.key = key
self.explanation = explanation
self.message = f"Invalid context options {key!r}."
self.format_cause = True

if explanation:
self.message += f"\n\n{explanation}"


@dataclass
class Pipeline(GenericPipeline["Context"]):
ctx: "Context"
Expand Down Expand Up @@ -116,7 +142,7 @@ def __post_init__(self, whitelist: Optional[List[str]]):
self.template.expose("parse_json", lambda string: json.loads(string))

@overload
def inject(self, cls: Callable[["Context"], InjectedType]) -> InjectedType:
def inject(self, cls: Callable[["Context"], T]) -> T:
...

@overload
Expand Down Expand Up @@ -174,6 +200,18 @@ def override(self, **meta: Any):
del self.meta[key]
self.meta.update(to_restore)

def validate(self, key: str, validator: Callable[..., T]) -> T:
"""Validate context metadata."""
try:
return validator(**self.meta.get(key, {}))
except ValidationError as exc:
explanation = format_validation_error(key, exc)
raise InvalidContextOptions(key, explanation) from None
except PipelineFallthroughException:
raise
except Exception as exc:
raise InvalidContextOptions(key) from exc

@property
def packs(self) -> Tuple[ResourcePack, DataPack]:
return self.assets, self.data
Expand Down
17 changes: 17 additions & 0 deletions beet/toolchain/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"stable_hash",
"format_obj",
"format_exc",
"format_validation_error",
"import_from_string",
"locate_minecraft",
"ensure_builtins",
Expand All @@ -21,6 +22,7 @@

from base58 import b58encode
from fnvhash import fnv1a_32, fnv1a_64
from pydantic import ValidationError

HASH_ALPHABET = b"123456789abcdefghijkmnopqrstuvwxyz"

Expand Down Expand Up @@ -58,6 +60,21 @@ def format_obj(obj: Any) -> str:
return repr(f"{module}.{name}") if module and name else repr(obj)


def format_validation_error(prefix: str, exc: ValidationError) -> str:
errors = [
(
prefix + "".join(json.dumps([item]) for item in error["loc"]),
error["msg"].capitalize(),
)
for error in exc.errors()
]
width = max(len(loc) for loc, _ in errors) + 1
return "\n".join(
"{loc:<{width}} => {msg}.".format(loc=loc, width=width, msg=msg)
for loc, msg in errors
)


def import_from_string(
dotted_path: str,
default_member: Optional[str] = None,
Expand Down
9 changes: 9 additions & 0 deletions examples/code_validate/beet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"pipeline": ["demo"],
"meta": {
"demo": {
"message": "hello",
"repeat": 7
}
}
}
14 changes: 14 additions & 0 deletions examples/code_validate/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pydantic import BaseModel

from beet import Context, Function


class DemoOptions(BaseModel):
message: str
repeat: int


def beet_default(ctx: Context):
config = ctx.validate("demo", DemoOptions)

ctx.data["demo:foo"] = Function([f"say {config.message}"] * config.repeat)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
say hello
say hello
say hello
say hello
say hello
say hello
say hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 7,
"description": ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 7,
"description": ""
}
}

0 comments on commit e1210cf

Please sign in to comment.