Source code for rs_files_templates.models.base

"""Base classes for file-template input models."""

from __future__ import annotations

import json
from abc import ABC
from copy import deepcopy
from pathlib import Path
from typing import Any, ClassVar, Self

from pydantic import BaseModel, ConfigDict, create_model
from rsm_schema import RSMMetadata


[docs] class FileTemplateModel(BaseModel, ABC): """Base class for data consumed by one generated file. Models are deliberately small: each concrete model selects only the published RSM fields needed by its generated file. """ model_config = ConfigDict(extra="forbid", validate_assignment=True) template_name: ClassVar[str] output_name: ClassVar[str] schema_url: ClassVar[str | None] = None media_type: ClassVar[str] = "text"
[docs] @classmethod def from_json(cls, value: str | bytes | bytearray) -> Self: """Build a model from JSON text. Parameters ---------- value JSON document containing model fields. Returns ------- FileTemplateModel Validated concrete model instance. """ return cls.model_validate_json(value)
[docs] @classmethod def from_json_file(cls, path: str | Path) -> Self: """Build a model from a UTF-8 JSON file.""" return cls.from_json(Path(path).read_text(encoding="utf-8"))
[docs] def to_json_file(self, path: str | Path, *, indent: int = 2) -> None: """Write this model to a JSON file.""" payload = self.model_dump(mode="json", exclude_none=True) Path(path).write_text(json.dumps(payload, indent=indent) + "\n", encoding="utf-8")
[docs] def template_data(self) -> dict[str, Any]: """Return JSON-compatible data for Jinja rendering.""" # Preserve optional keys as ``None`` so readable templates can use # straightforward conditionals under Jinja's ``StrictUndefined``. return self.model_dump(mode="json", exclude_none=False)
[docs] def archive_entries(self) -> dict[str, str]: """Return ``{filename: rendered content}`` for a ``zip``-typed model. Only overridden by models whose ``media_type`` is ``"zip"``, which bundle several rendered files into one downloadable archive instead of rendering a single Jinja template. """ raise NotImplementedError
[docs] def render( self, output_dir: str | Path, *, validate_schema: bool = False, ) -> Path: """Render this model to its default output file. Parameters ---------- output_dir Directory receiving the generated file. validate_schema Validate schema-backed output after rendering. Returns ------- pathlib.Path Generated file path. """ from rs_files_templates.renderer import render_file return render_file(self, output_dir, validate_schema=validate_schema).path
def rsm_template_base(name: str, *field_names: str) -> type[FileTemplateModel]: """Create a file-model base from selected published RSM fields. Copying the source ``FieldInfo`` values preserves the schema package's annotations, defaults, constraints, aliases, and nested generated models. """ unknown = set(field_names) - set(RSMMetadata.model_fields) if unknown: raise ValueError(f"Unknown RSM fields for {name}: {', '.join(sorted(unknown))}") definitions = { field_name: ( deepcopy(RSMMetadata.model_fields[field_name].annotation), deepcopy(RSMMetadata.model_fields[field_name]), ) for field_name in field_names } return create_model(name, __base__=FileTemplateModel, **definitions)