diff --git a/LICENSE b/LICENSE index 563130e..5fc5705 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Smithed +Copyright (c) 2026 Smithed Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/basic_scripts/custom/data/custom/weld/scripts/main.bolt b/examples/basic_scripts/custom/data/custom/weld/scripts/main.bolt new file mode 100644 index 0000000..09babd9 --- /dev/null +++ b/examples/basic_scripts/custom/data/custom/weld/scripts/main.bolt @@ -0,0 +1,3 @@ +function list:functions: + for func_name in sorted(ctx.data.functions): + say func_name diff --git a/examples/basic_scripts/custom/pack.mcmeta b/examples/basic_scripts/custom/pack.mcmeta new file mode 100644 index 0000000..134cc87 --- /dev/null +++ b/examples/basic_scripts/custom/pack.mcmeta @@ -0,0 +1,3 @@ +{ + "id": "custom" +} \ No newline at end of file diff --git a/examples/basic_scripts/custom/pack.png b/examples/basic_scripts/custom/pack.png new file mode 100644 index 0000000..f4a61a0 Binary files /dev/null and b/examples/basic_scripts/custom/pack.png differ diff --git a/examples/basic_scripts/fancy_bats/data/fancy_bats/functions/load.mcfunction b/examples/basic_scripts/fancy_bats/data/fancy_bats/functions/load.mcfunction new file mode 100644 index 0000000..806591a --- /dev/null +++ b/examples/basic_scripts/fancy_bats/data/fancy_bats/functions/load.mcfunction @@ -0,0 +1 @@ +say "these bats are wildin'" diff --git a/examples/basic_scripts/fancy_bats/pack.mcmeta b/examples/basic_scripts/fancy_bats/pack.mcmeta new file mode 100644 index 0000000..5847e19 --- /dev/null +++ b/examples/basic_scripts/fancy_bats/pack.mcmeta @@ -0,0 +1,3 @@ +{ + "id": "fancy_bats" +} \ No newline at end of file diff --git a/examples/basic_scripts/fancy_bats/pack.png b/examples/basic_scripts/fancy_bats/pack.png new file mode 100644 index 0000000..f4a61a0 Binary files /dev/null and b/examples/basic_scripts/fancy_bats/pack.png differ diff --git a/examples/basic_scripts/tasty_tacos/data/tasty_tacos/functions/load.mcfunction b/examples/basic_scripts/tasty_tacos/data/tasty_tacos/functions/load.mcfunction new file mode 100644 index 0000000..19f231d --- /dev/null +++ b/examples/basic_scripts/tasty_tacos/data/tasty_tacos/functions/load.mcfunction @@ -0,0 +1 @@ +say "the tastiest of tacos" diff --git a/examples/basic_scripts/tasty_tacos/data/tasty_tacos/functions/tacos.mcfunction b/examples/basic_scripts/tasty_tacos/data/tasty_tacos/functions/tacos.mcfunction new file mode 100644 index 0000000..c6a19e4 --- /dev/null +++ b/examples/basic_scripts/tasty_tacos/data/tasty_tacos/functions/tacos.mcfunction @@ -0,0 +1 @@ +give @a taco 64 diff --git a/examples/basic_scripts/tasty_tacos/pack.mcmeta b/examples/basic_scripts/tasty_tacos/pack.mcmeta new file mode 100644 index 0000000..27984a2 --- /dev/null +++ b/examples/basic_scripts/tasty_tacos/pack.mcmeta @@ -0,0 +1,3 @@ +{ + "id": "tasty_tacos" +} \ No newline at end of file diff --git a/examples/basic_scripts/tasty_tacos/pack.png b/examples/basic_scripts/tasty_tacos/pack.png new file mode 100644 index 0000000..ff0e7c9 Binary files /dev/null and b/examples/basic_scripts/tasty_tacos/pack.png differ diff --git a/pyproject.toml b/pyproject.toml index be37936..9c255b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,8 @@ dependencies = [ "rich>=13.6.0", "pydantic>=2.5.2", "lectern>=0.34.0", + "bolt>=0.38.5", + "bolt-expressions>=0.17.0", ] description = "Smithed's Python client with CLI, Weld and more" license = "MIT" diff --git a/src/smithed/weld/__init__.py b/src/smithed/weld/__init__.py index 17a2fa2..829a4c2 100644 --- a/src/smithed/weld/__init__.py +++ b/src/smithed/weld/__init__.py @@ -1,10 +1,16 @@ -from .toolchain import cli, run_weld, weld, weld_handler +from .toolchain import cli, run_weld, weld_loader, weld_metadata, weld_handler, weld +from .scripts import WeldScript, WeldPyScript, ResourceDefinition __version__ = "0.20.0" __all__ = [ "cli", + "ResourceDefinition", "run_weld", - "weld", "weld_handler", + "weld_loader", + "weld_metadata", + "weld", + "WeldPyScript", + "WeldScript", ] diff --git a/src/smithed/weld/scripts/__init__.py b/src/smithed/weld/scripts/__init__.py new file mode 100644 index 0000000..d3cc505 --- /dev/null +++ b/src/smithed/weld/scripts/__init__.py @@ -0,0 +1,11 @@ +from .plugins import process_scripting, clear_resources, load_resources +from .resources import WeldScript, WeldPyScript, ResourceDefinition + +__all__ = [ + "process_scripting", + "clear_resources", + "load_resources", + "ResourceDefinition", + "WeldPyScript", + "WeldScript", +] diff --git a/src/smithed/weld/scripts/errors.py b/src/smithed/weld/scripts/errors.py new file mode 100644 index 0000000..cf892bc --- /dev/null +++ b/src/smithed/weld/scripts/errors.py @@ -0,0 +1,10 @@ +class WeldScriptError(Exception): + namespace: str + + def __init__(self, *args, namespace: str, cause: Exception): + super().__init__(*args) + self.namespace = namespace + self.__cause__ = cause + + def __str__(self) -> str: + return f"{self.namespace}: {self.args[0]}\n{self.__cause__}" diff --git a/src/smithed/weld/scripts/plugins.py b/src/smithed/weld/scripts/plugins.py new file mode 100644 index 0000000..9c7ba14 --- /dev/null +++ b/src/smithed/weld/scripts/plugins.py @@ -0,0 +1,136 @@ +import logging +import traceback as tb + +from beet import Context, DataPack, Draft, Pack, ResourcePack +import beet +import bolt_expressions +from bolt import Runtime +from bolt.contrib.sandbox import Sandbox, public_attrs +from mecha import CompilationError, Mecha + +from .resources import RESOURCES, ResourceDefinition, WeldScript + +logger = logging.getLogger("weld") + +ALLOWED_TYPES = (DataPack, ResourcePack) +WELD_SCRIPTS_OVERLAY_NAME = "weld_generated" + + +def process_scripting(ctx: Context): + """Load scripting stuff""" + + if not any( + pack[WeldScript] + for root in ctx.packs + for pack in (root, *root.overlays.values()) + ): + return + + mc = ctx.inject(Mecha) + runtime = ctx.inject(Runtime) + + # TODO: move to new file, and write up comments for everything allowed. make more specific + sandbox = ctx.inject(Sandbox) + sandbox.allow_basics() + sandbox.allow_pipeline_context() + + sandbox.allowed_type_attrs[Draft] |= {field for field in public_attrs(Draft)} + + sandbox.allowed_obj_attrs[beet] |= {field for field in public_attrs(beet)} + sandbox.allowed_obj_attrs[bolt_expressions] |= { + field for field in public_attrs(bolt_expressions) + } + + for _type in ALLOWED_TYPES: + sandbox.allowed_type_attrs[_type] |= {field for field in public_attrs(_type)} + + sandbox.allowed_imports |= {"beet", "bolt_expressions"} + sandbox.allowed_builtins |= {"breakpoint"} + sandbox.activate() + + # load all ctx content into a draft so that it doesn't merge + with ctx.generate.draft() as draft: + # we shallow copy everything into a draft so we can shadow ctx + for draft_pack, ctx_pack in zip((draft.assets, draft.data), ctx.packs): + for resource, file in ctx_pack.all(): + draft_pack[resource] = file.copy() + + # pretend draft is ctx so that people can't change ctx stuff + # the draft has shallow copies of all content + runtime.globals["ctx"] = draft + for pack in ctx.packs: + pack.overlays.setdefault( + WELD_SCRIPTS_OVERLAY_NAME, + supported_formats=pack.supported_formats or pack.pack_format, + min_format=pack.min_format, + max_format=pack.max_format, + ) + overlay = ctx.generate.overlays[WELD_SCRIPTS_OVERLAY_NAME] + + # processing packs for compliation + for pack in [ + ctx.data, + *ctx.data.overlays.values(), + ctx.assets, + *ctx.assets.overlays.values(), + ]: + for resource_location, script in list(pack[WeldScript].items()): + logger.info('Executing Weld Script: "%s"', resource_location) + + # compile with recovery to ensure we can compile other modules + try: + with overlay.push(): + mc.compile(script) + + except CompilationError as err: + # TODO: better error reporting for weld script writers + logger.error(err.__cause__) + + except Exception as err: + # if something errors (like mecha), log error AND traceback + logger.error(err) + logger.error("\n".join(tb.format_tb(err.__traceback__))) + + # delete files that have not changed in our draft (if `._content` exists, it's changed) + for pack in [draft.assets, draft.data]: + for resource, file in list(pack.all()): + if hasattr(file, "_content") and not file._content: + del pack[type(file)][resource] + + # merge all draft content into our global overlay. the draft needs to + # be cleared so that it doesn't merge into the actual pack. + for overlay_pack, draft_pack in zip( + (overlay.assets, overlay.data), (draft.assets, draft.data) + ): + overlay_pack.merge(draft_pack) # type: ignore + draft_pack.clear() + draft_pack.extra.clear() + + for pack in ctx.packs: + if not pack.overlays[WELD_SCRIPTS_OVERLAY_NAME]: + del pack.overlays[WELD_SCRIPTS_OVERLAY_NAME] + + +def clear_resources(ctx: Context): + """Clear weld script resources""" + + for pack in [ + ctx.data, + *ctx.data.overlays.values(), + ctx.assets, + *ctx.assets.overlays.values(), + ]: + for resource in [WeldScript, ResourceDefinition]: + pack[resource].clear() + + +def load_resources(ctx: Context | Pack): + """Load all of the custom resource types for weld scripts""" + + match ctx: + case Context(): + for pack in ctx.packs: + pack.extend_namespace += RESOURCES + + case DataPack() | ResourcePack() as pack: + pack.extend_namespace += RESOURCES diff --git a/src/smithed/weld/scripts/resources.py b/src/smithed/weld/scripts/resources.py new file mode 100644 index 0000000..046d6d7 --- /dev/null +++ b/src/smithed/weld/scripts/resources.py @@ -0,0 +1,42 @@ +from typing import ClassVar + +from beet import DataPack, Drop, NamespaceFileScope, TextFile + + +class WeldScript(TextFile): + scope: ClassVar[NamespaceFileScope] = { + 0: ("weld", "scripts"), + 45: ("weld", "script"), + } + extension: ClassVar[str] = ".bolt" + + +class WeldPyScript(TextFile): + scope: ClassVar[NamespaceFileScope] = { + 0: ("weld", "scripts"), + 45: ("weld", "script"), + } + extension: ClassVar[str] = ".py" + + def bind(self, pack: DataPack, path: str): + """Rebind file as a proper `Module` and drop current file.""" + + super().bind(pack, path) + + if self.source_path: + pack[path] = WeldScript(source_path=self.source_path) + else: + pack[path] = WeldScript(self.text) + + raise Drop() + + +class ResourceDefinition(TextFile): + scope: ClassVar[NamespaceFileScope] = { + 0: ("weld", "resource_definitions"), + 45: ("weld", "resource_definition"), + } + extension: ClassVar[str] = ".py" + + +RESOURCES = [WeldScript, WeldPyScript, ResourceDefinition] diff --git a/src/smithed/weld/toolchain/__init__.py b/src/smithed/weld/toolchain/__init__.py index dbf5de3..656df17 100644 --- a/src/smithed/weld/toolchain/__init__.py +++ b/src/smithed/weld/toolchain/__init__.py @@ -1,5 +1,5 @@ from .cli import cli from .helpers import run_weld -from .plugins import weld, weld_handler +from .plugins import weld_loader, weld_metadata, weld_handler, weld -__all__ = ["cli", "run_weld", "weld", "weld_handler"] +__all__ = ["cli", "run_weld", "weld_loader", "weld_metadata", "weld_handler", "weld"] diff --git a/src/smithed/weld/toolchain/cli.py b/src/smithed/weld/toolchain/cli.py index aee91f6..22006b3 100644 --- a/src/smithed/weld/toolchain/cli.py +++ b/src/smithed/weld/toolchain/cli.py @@ -80,8 +80,8 @@ def weld( success = False - logger.debug("Using cache: '%s'", path_str(cache)) - logger.info("Packs: %s", ", ".join(f"'{pack}'" for pack in packs)) + logger.debug('Using cache: "%s"', path_str(cache)) + logger.info("Packs: %s", ", ".join(f'"{pack}"' for pack in packs)) t0 = perf_counter() with ( diff --git a/src/smithed/weld/toolchain/helpers.py b/src/smithed/weld/toolchain/helpers.py index 154bc85..d7fffc1 100644 --- a/src/smithed/weld/toolchain/helpers.py +++ b/src/smithed/weld/toolchain/helpers.py @@ -28,7 +28,10 @@ def run_weld( cache: bool | ProjectCache = True, as_fabric_mod: bool = False, ): - """Runs the beet toolchain alongside the weld machinery programmatically.""" + """Runs the beet toolchain alongside the weld machinery programmatically. + + Matches the beet helper, `run_beet`. + """ with run_beet(config, directory=directory, cache=cache) as ctx: ctx.require(weld_handler) diff --git a/src/smithed/weld/toolchain/plugins.py b/src/smithed/weld/toolchain/plugins.py index 0717bad..088e1fb 100644 --- a/src/smithed/weld/toolchain/plugins.py +++ b/src/smithed/weld/toolchain/plugins.py @@ -13,7 +13,7 @@ ) from jinja2 import Template -from smithed.weld import merging +from smithed.weld import merging, scripts from .process import PackProcessor @@ -31,14 +31,35 @@ ).read_bytes() -class WeldOptions(PluginOptions, arbitrary_types_allowed=True): +class WeldLoaderOptions(PluginOptions, arbitrary_types_allowed=True): + """Options to configure how weld loads packs before merge time. + + packs - which packs (paths or ZipFile) to load + """ + + # TODO: change `str` to `PathLike`? packs: list[str] | list[ZipFile] +class WeldOptions(PluginOptions, arbitrary_types_allowed=True): + """Options to configure how weld operates at merge time. + + scripts - determines whether weld scripts run after merging. + """ + + scripts: bool = True + + def add_fabric_mod_json(ctx: Context, pack_names: list[str] | None = None): + """Generates and adds a fabric mod json for producing value fabric jars. + + Note: Fabric jar is compromised of a renamed `.zip` -> `.jar` and a `fabric.mod.json`. + """ + if pack_names is None: processor = ctx.inject(PackProcessor) - pack_names = list(set(pack.name for pack in processor.packs)) + # deduplicate names while preserving pack loading order for Fabric metadata. + pack_names = list(dict.fromkeys(pack.name for pack in processor.packs)) content = FABRIC_MOD_TEMPLATE.render( pack_hash=hash("".join(pack_names)), @@ -49,17 +70,27 @@ def add_fabric_mod_json(ctx: Context, pack_names: list[str] | None = None): ctx.data.extra["fabric.mod.json"] = JsonFile(content) -@configurable(validator=WeldOptions) -def weld_loader(ctx: Context, opts: WeldOptions): +@configurable(validator=WeldLoaderOptions) +def weld_loader(ctx: Context, opts: WeldLoaderOptions): + """Loads packs into weld via the `PackProcessor` class.""" + processor = ctx.inject(PackProcessor) processor.load_packs(opts.packs) def weld_handler(ctx: Context): + """Loads the merging pre-processing pipeline.""" + ctx.require(merging.beet_default) + ctx.require(scripts.load_resources) def weld_metadata(ctx: Context): + """Attaches custom metadata to welded packs. + + TODO: make configurable + """ + processor = ctx.inject(PackProcessor) if ctx.data: @@ -79,5 +110,11 @@ def weld_metadata(ctx: Context): ] -def weld(ctx: Context): +@configurable(validator=WeldOptions) +def weld(ctx: Context, opt: WeldOptions): + """Beet plugin that runs the main weld processes (merging + scripting)""" + ctx.require(merging.process) + + if opt.scripts: + ctx.require(scripts.process_scripting) diff --git a/src/smithed/weld/toolchain/process.py b/src/smithed/weld/toolchain/process.py index dc0c537..2399496 100644 --- a/src/smithed/weld/toolchain/process.py +++ b/src/smithed/weld/toolchain/process.py @@ -43,6 +43,7 @@ successor, ) from ..errors import InvalidMcmeta, InvalidPack +from smithed.weld import scripts from ...type import JsonDict logger = logging.getLogger("weld") @@ -162,6 +163,7 @@ def _create_empty_pack[T: (DataPack, ResourcePack)](pack_type: type[T]) -> T: else: raise ValueError("`pack_type` must be `DataPack` or `ResourcePack`") + scripts.load_resources(pack) return pack @@ -207,6 +209,9 @@ class PackProcessor: file_id_cache: dict[JsonFileBase[JsonDict], Pack] = field(default_factory=dict) packs: list[PackWithName[Pack]] = field(default_factory=list) + def __post_init__(self): + self.ctx.meta["packs"] = {} + def __getitem__(self, key: JsonFileBase[JsonDict]): return self.file_id_cache[key] @@ -346,6 +351,7 @@ def cache_pack[T: (DataPack, ResourcePack)]( if register: self.packs.append(pack) + self.ctx.meta["packs"][pack.name] = pack.pack def load_packs(self, files: list[str] | list[ZipFile]): """Load a series of packs into `ctx`. Triggers merge policies. diff --git a/tests/snapshots/examples__build_basic_scripts__0.pack.md b/tests/snapshots/examples__build_basic_scripts__0.pack.md new file mode 100644 index 0000000..72afdbd --- /dev/null +++ b/tests/snapshots/examples__build_basic_scripts__0.pack.md @@ -0,0 +1,109 @@ +# Lectern snapshot + +## Data pack + +`@data_pack pack.mcmeta` + +```json +{ + "pack": { + "description": "", + "min_format": [ + 121, + 0 + ], + "max_format": [ + 121, + 0 + ] + }, + "id": "tasty_tacos", + "overlays": { + "entries": [ + { + "directory": "weld_generated", + "min_format": [ + 121, + 0 + ], + "max_format": [ + 121, + 0 + ] + } + ] + } +} +``` + +`@data_pack pack.png` + +![data_pack.png](data:image/png;base64,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) + +### custom + +`@weld_script custom:main` + +``` +function list:functions: + for func_name in sorted(ctx.data.functions): + say func_name +``` + +### fancy_bats + +`@function fancy_bats:load` + +```mcfunction +say "these bats are wildin'" +``` + +### tasty_tacos + +`@function tasty_tacos:load` + +```mcfunction +say "the tastiest of tacos" +``` + +`@function tasty_tacos:tacos` + +```mcfunction +give @a taco 64 +``` + +## Overlay `weld_generated` + +`@overlay weld_generated` + +### list + +`@function list:functions` + +```mcfunction +say fancy_bats:load +say tasty_tacos:load +say tasty_tacos:tacos +``` + +`@endoverlay` + +## Resource pack + +`@resource_pack pack.mcmeta` + +```json +{ + "pack": { + "min_format": [ + 75, + 0 + ], + "max_format": [ + 75, + 0 + ], + "description": "" + } +} +``` diff --git a/tests/test_examples.py b/tests/test_examples.py index f65d83b..02ee731 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -5,11 +5,26 @@ from beet.library.test_utils import ignore_name import pytest from lectern import Document +from lectern.pytest_plugin import FmtPackMarkdown from pytest_insta import SnapshotFixture from smithed.type import JsonDict -from smithed.weld import run_weld +from smithed.weld import run_weld, scripts + + +class FmtWeldPackMarkdown(FmtPackMarkdown): + def load(self, path: Path) -> Document: + document = Document() + scripts.load_resources(document.assets) + scripts.load_resources(document.data) + document.load(path) + for pack in (document.assets, document.data): + ignore_name(pack) + for overlay in pack.overlays.values(): + ignore_name(overlay) + return document + EXAMPLES = [f for f in os.listdir("examples") if not f.startswith(".")] diff --git a/uv.lock b/uv.lock index d3c0b64..13f6a29 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 5 +revision = 3 requires-python = ">=3.14" [[package]] @@ -131,6 +131,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] +[[package]] +name = "bolt" +version = "0.51.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beet" }, + { name = "mecha" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/ff/4fcca0be75b32a790bc751a0113ede064d033c517167c0de2ae228bf8389/bolt-0.51.0.tar.gz", hash = "sha256:1abb78bf058cb7439cacc60cde8d3b9af49cadc147990b3525c16f4bc2463461", size = 52523, upload-time = "2026-09-10T18:33:06.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/5e/83d8dddc4603ac43b71569247ba53d18149198b8a70ebc9f6d2e1390d486/bolt-0.51.0-py3-none-any.whl", hash = "sha256:ef3fb1f40c01c4a5d216b8440f98a083368f53b12989d795e1a684ff9f416ad1", size = 59765, upload-time = "2026-09-10T18:33:05.858Z" }, +] + +[[package]] +name = "bolt-control-flow" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beet" }, + { name = "bolt" }, + { name = "mecha" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/d7/2c0d8073a6e7fd73e5fc519799d966ab2827f402569c45e510bd17962074/bolt_control_flow-0.4.0.tar.gz", hash = "sha256:c4975ae03bad072fff979e08b2283b746e92c84c7e053a721bd466de23483667", size = 7385, upload-time = "2026-01-18T03:23:30.771Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/47/3345c978b3fe00db95c83d08c3d458eaf7530b1683bc650c60670437e08c/bolt_control_flow-0.4.0-py3-none-any.whl", hash = "sha256:7ce1d9a36cc67409077ac8aa7debcaaa16279ef97524443e18974cf2ed6cc4f6", size = 9699, upload-time = "2026-01-18T03:23:29.341Z" }, +] + +[[package]] +name = "bolt-expressions" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beet" }, + { name = "bolt" }, + { name = "bolt-control-flow" }, + { name = "frozendict" }, + { name = "mecha" }, + { name = "nbtlib" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/2a/8c2aed527acbdebacc2b23a36aa6c26a103dc6645b5320f9bc864131eeeb/bolt_expressions-0.19.2.tar.gz", hash = "sha256:b1e3ef28af5b13aa7a88c240e6b72f1cc7e736790b466f2d856977e25f9fa237", size = 39382, upload-time = "2026-01-18T04:49:58.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/5c/65a87588d2a1f26065d4077447fdd1c929aa3b185bcbbb5f6628fbbea7ec/bolt_expressions-0.19.2-py3-none-any.whl", hash = "sha256:e303efe505e2d7678e179ae5d663bc00fa7fc6a04bf57f3ed5dd3676b34d6996", size = 46432, upload-time = "2026-01-18T04:49:56.889Z" }, +] + [[package]] name = "boto3" version = "1.42.71" @@ -530,6 +575,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] +[[package]] +name = "frozendict" +version = "2.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/b2/2a3d1374b7780999d3184e171e25439a8358c47b481f68be883c14086b4c/frozendict-2.4.7.tar.gz", hash = "sha256:e478fb2a1391a56c8a6e10cc97c4a9002b410ecd1ac28c18d780661762e271bd", size = 317082, upload-time = "2025-11-11T22:40:14.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/74/f94141b38a51a553efef7f510fc213894161ae49b88bffd037f8d2a7cb2f/frozendict-2.4.7-py3-none-any.whl", hash = "sha256:972af65924ea25cf5b4d9326d549e69a9a4918d8a76a9d3a7cd174d98b237550", size = 16264, upload-time = "2025-11-11T22:40:12.836Z" }, +] + [[package]] name = "gitdb" version = "4.0.12" @@ -1718,6 +1772,8 @@ source = { editable = "." } dependencies = [ { name = "backports-strenum" }, { name = "beet" }, + { name = "bolt" }, + { name = "bolt-expressions" }, { name = "lectern" }, { name = "mecha" }, { name = "pydantic" }, @@ -1750,6 +1806,8 @@ github-workflows = [ requires-dist = [ { name = "backports-strenum", specifier = ">=1.2.8" }, { name = "beet", specifier = ">=0.119.0" }, + { name = "bolt", specifier = ">=0.38.5" }, + { name = "bolt-expressions", specifier = ">=0.17.0" }, { name = "lectern", specifier = ">=0.34.0" }, { name = "mecha", specifier = ">=0.106.0" }, { name = "pydantic", specifier = ">=2.5.2" },