plesty.lib.report.blocks ======================== .. py:module:: plesty.lib.report.blocks .. autoapi-nested-parse:: What an analysis says about its result: text, tables and figures. A :class:`Report` is an ordered list of blocks, and there are three kinds: * :class:`Text` — a paragraph with inline markup: ``**bold**``, ``*italic*``, ```code``` and ``$math$`` in LaTeX notation. * :class:`Table` — header rows and body rows of :class:`Cell` and :class:`Value`, with ``rowspan``/``colspan`` as in HTML, notes, and a caption. * :class:`Figure` — the same drawables a live view draws (:mod:`plesty.lib.monitor.plots`), so a figure in a report and a view on screen are one vocabulary. A block holds structure, never markup. :meth:`Report.to_html` and :meth:`Report.to_latex` render the same report for a browser and for a paper; :meth:`Table.raw` is the escape hatch for a table that structure cannot say. **Spans are written the HTML way.** A spanning cell is written once, where it starts; the positions it covers are left out of the rows below it and to its right. Every row, counting what spans into it from above, must fill the table's width exactly, and spans may not overlap — both are checked when the table is built, naming the row, so a miscounted table fails where it is written rather than rendering lopsided: .. code-block:: python Table( header=[ [Cell("QD", rowspan=2), Cell("Splitting", colspan=2)], [Cell(r"$\Delta E$ (µeV)"), Cell(r"$\Delta\lambda$ (pm)")], ], rows=[ [Cell("QD 3", rowspan=2), Value(11.5, err=0.3), Value(5.52, err=0.14)], [Value(12.1, err=0.4), Value(5.81, err=0.19)], ], ) A report is saved as JSON (:meth:`Report.save`) beside the outputs it describes, so a reader renders it without importing the analyzer that wrote it — the reader's plesty-lib is routinely older than the writer's, which is what :data:`SCHEMA_VERSION` is for. Attributes ---------- .. autoapisummary:: plesty.lib.report.blocks.SCHEMA_VERSION plesty.lib.report.blocks.REPORT_KEY plesty.lib.report.blocks.REPORT_SUFFIX plesty.lib.report.blocks.ALIGNS plesty.lib.report.blocks.MARKUP_FORMATS plesty.lib.report.blocks.DRAWABLES plesty.lib.report.blocks.CellLike plesty.lib.report.blocks.CELLS plesty.lib.report.blocks.Block plesty.lib.report.blocks.BLOCKS Exceptions ---------- .. autoapisummary:: plesty.lib.report.blocks.ReportError Classes ------- .. autoapisummary:: plesty.lib.report.blocks._Spanning plesty.lib.report.blocks.Cell plesty.lib.report.blocks.Value plesty.lib.report.blocks.Slot plesty.lib.report.blocks.Table plesty.lib.report.blocks.Text plesty.lib.report.blocks.Figure plesty.lib.report.blocks.Report Functions --------- .. autoapisummary:: plesty.lib.report.blocks._number plesty.lib.report.blocks._engineering plesty.lib.report.blocks._decimals plesty.lib.report.blocks._fixed plesty.lib.report.blocks._cell plesty.lib.report.blocks._cell_from_dict plesty.lib.report.blocks._layout plesty.lib.report.blocks._json_value plesty.lib.report.blocks._drawable_to_dict plesty.lib.report.blocks._drawable_from_dict Module Contents --------------- .. py:data:: SCHEMA_VERSION :value: 1 .. py:data:: REPORT_KEY :value: '__plesty_report__' .. py:data:: REPORT_SUFFIX :value: '.report.json' .. py:data:: ALIGNS :type: tuple[str, Ellipsis] :value: ('l', 'c', 'r') .. py:data:: MARKUP_FORMATS :type: tuple[str, Ellipsis] :value: ('html', 'latex') .. py:data:: DRAWABLES :type: dict[str, type[plesty.lib.monitor.plots.Drawable]] .. py:exception:: ReportError Bases: :py:obj:`ValueError` Raised when a report, or a block in it, cannot be built or read. Initialize self. See help(type(self)) for accurate signature. .. py:class:: _Spanning What every table cell has: its span, its alignment, and a note mark. :ivar rowspan: Rows the cell covers, downwards from its own. :ivar colspan: Columns the cell covers, rightwards from its own. :ivar align: One of :data:`ALIGNS`; ``None`` leaves it to the renderer — left for text, centred for values. :ivar note: Mark of a table note this cell refers to, e.g. ``"†"``. .. py:attribute:: kind :type: ClassVar[str] :value: '' .. py:attribute:: rowspan :type: int :value: 1 .. py:attribute:: colspan :type: int :value: 1 .. py:attribute:: align :type: Optional[str] :value: None .. py:attribute:: note :type: Optional[str] :value: None .. py:method:: __post_init__() -> None Check the span and the alignment. :raises ReportError: If a span is not a positive integer or the alignment is not one of :data:`ALIGNS`. .. py:method:: to_dict() -> dict[str, Any] Return the cell as JSON-ready values, leaving out every default. .. py:class:: Cell Bases: :py:obj:`_Spanning` A cell of text, with inline markup (see :class:`Text`). :ivar content: What the cell says. .. py:attribute:: kind :type: ClassVar[str] :value: 'cell' .. py:attribute:: content :type: str :value: '' .. py:class:: Value Bases: :py:obj:`_Spanning` A cell holding a number, and optionally its one-sigma uncertainty. The number is written once and each renderer formats it: ``11.5 ± 0.3`` in HTML, ``$11.5 \pm 0.3$`` in LaTeX. With an uncertainty, the precision follows it — two significant digits of the uncertainty when it starts with 1, 2 or 3, one otherwise — so a value never claims more digits than it has. :ivar value: The number; ``None`` or ``nan`` renders as a dash. :ivar err: Its one-sigma uncertainty. :ivar digits: Significant digits of a value without an uncertainty. .. py:attribute:: kind :type: ClassVar[str] :value: 'value' .. py:attribute:: value :type: Optional[float] :value: None .. py:attribute:: err :type: Optional[float] :value: None .. py:attribute:: digits :type: int :value: 4 .. py:method:: __post_init__() -> None Store plain Python numbers, so the cell saves as JSON as it is. .. py:method:: formatted() -> Optional[tuple[str, Optional[str], int]] Return the value and uncertainty as digits, and a shared power of ten. :returns: ``(value, err, exponent)`` — ``err`` is ``None`` without an uncertainty, and ``exponent`` is the power of ten both are scaled by (``0`` for most numbers). ``None`` when there is no value. .. py:data:: CellLike .. py:data:: CELLS :type: dict[str, type[_Spanning]] .. py:function:: _number(value: Any) -> Optional[float] Return a number as a Python ``int`` or ``float``; ``nan`` becomes ``None``. .. py:function:: _engineering(magnitude: float) -> int Return the power of ten to factor out of a number, ``0`` unless extreme. .. py:function:: _decimals(err: float) -> int Return the decimal places an uncertainty is quoted to. .. py:function:: _fixed(value: float, decimals: int) -> str Return *value* rounded to *decimals* places, which may be negative. .. py:function:: _cell(value: Any) -> _Spanning Coerce what a table was given for one position into a cell. .. py:class:: Slot What occupies one position of a table's grid. :ivar state: ``"cell"`` where a cell starts; ``"below"`` in the first column of a cell spanning down from an earlier row; ``"covered"`` anywhere else a span reaches. :ivar cell: The cell that starts at, or spans over, this position. .. py:attribute:: state :type: str .. py:attribute:: cell :type: _Spanning .. py:class:: Table Header rows and body rows of cells, with notes and a caption. :ivar header: Header rows, each a list of cells. Spans stay within the header; they never reach into the body. :ivar rows: Body rows, each a list of cells. :ivar title: What the table shows, set above it. :ivar caption: A sentence below it. :ivar notes: Mark → text. A mark some cell refers to with ``note=`` is a footnote, set as a superscript (``"†"``); any other mark is a term being defined, such as a column label. :ivar columns: The table's width. Taken from the first row when left out. :ivar markup: Format → finished markup, for a :meth:`raw` table only. .. py:attribute:: kind :type: ClassVar[str] :value: 'table' .. py:attribute:: header :type: list[list[Any]] :value: [] .. py:attribute:: rows :type: list[list[Any]] :value: [] .. py:attribute:: title :type: str :value: '' .. py:attribute:: caption :type: str :value: '' .. py:attribute:: notes :type: dict[str, str] .. py:attribute:: columns :type: Optional[int] :value: None .. py:attribute:: markup :type: dict[str, str] .. py:method:: __post_init__() -> None Coerce every cell, take the width, and lay out the grid. :raises ReportError: If a row does not fill the width, spans overlap, a rowspan runs past its section, a note is referred to but never given, or a raw table also has cells. .. py:method:: footnotes() -> set[str] Return the note marks some cell refers to — the footnotes among the notes. .. py:method:: raw(html: Optional[str] = None, latex: Optional[str] = None, *, title: str = '', caption: str = '') -> Table :classmethod: Return a table given as finished markup, in one format or both. The escape hatch, for a table the cell structure cannot express. Each renderer uses the format it writes; a renderer whose format is missing says so instead of guessing. HTML from here is sanitised before it is shown, since a report is written by a third-party package. :param html: The table as HTML. :param latex: The table as LaTeX, a ``tabular`` or anything that goes inside a ``table`` environment. :param title: What the table shows. :param caption: A sentence below it. :returns: The table. :raises ReportError: If neither format is given. .. py:method:: layout(section: str) -> list[list[Slot]] Return a section's grid: one :class:`Slot` per row and column. :param section: ``"header"`` or ``"body"``. :returns: The rows of the section, each exactly :attr:`columns` wide. .. py:method:: to_dict() -> dict[str, Any] Return the table as JSON-ready values. .. py:method:: from_dict(document: Mapping[str, Any]) -> Table :classmethod: Rebuild a table from :meth:`to_dict`. .. py:function:: _cell_from_dict(document: Any) -> _Spanning Rebuild a cell saved by :meth:`_Spanning.to_dict`. .. py:function:: _layout(rows: Sequence[Sequence[_Spanning]], width: int, section: str) -> list[list[Slot]] Place a section's cells on its grid, checking every row fills it. :raises ReportError: Naming the section and the row (from 1) that is wrong. .. py:class:: Text Prose, with inline markup. Paragraphs are separated by a blank line. Inside one: ``**bold**``, ``*italic*``, ```code``` and ``$math$`` in LaTeX notation — rendered as MathML in HTML and passed through unchanged to LaTeX. ``\$`` is a literal dollar sign. :ivar content: The text. .. py:attribute:: kind :type: ClassVar[str] :value: 'text' .. py:attribute:: content :type: str :value: '' .. py:method:: to_dict() -> dict[str, Any] Return the text as JSON-ready values. .. py:method:: from_dict(document: Mapping[str, Any]) -> Text :classmethod: Rebuild a text block from :meth:`to_dict`. .. py:class:: Figure A plot, drawn from the drawables of :mod:`plesty.lib.monitor.plots`. ``Line`` and ``Scatter`` share one pair of axes; an ``Image`` fills them; ``Scalar`` and ``Text`` marks are set in the corner, as on screen. :ivar drawables: What to draw, in the order it is layered. :ivar title: What the figure shows. :ivar caption: A sentence below it. .. py:attribute:: kind :type: ClassVar[str] :value: 'figure' .. py:attribute:: drawables :type: list[plesty.lib.monitor.plots.Drawable] :value: [] .. py:attribute:: title :type: str :value: '' .. py:attribute:: caption :type: str :value: '' .. py:method:: __post_init__() -> None Check every item is a drawable. :raises ReportError: If one is not. .. py:method:: to_dict() -> dict[str, Any] Return the figure as JSON-ready values; arrays become lists. .. py:method:: from_dict(document: Mapping[str, Any]) -> Figure :classmethod: Rebuild a figure from :meth:`to_dict`. .. py:function:: _json_value(value: Any) -> Any Return a drawable field as JSON: arrays as lists, ``nan`` as ``None``. .. py:function:: _drawable_to_dict(item: plesty.lib.monitor.plots.Drawable) -> dict[str, Any] Return a drawable as JSON-ready values, named by its kind. .. py:function:: _drawable_from_dict(document: Mapping[str, Any]) -> plesty.lib.monitor.plots.Drawable Rebuild a drawable saved by :func:`_drawable_to_dict`. .. py:data:: Block .. py:data:: BLOCKS :type: dict[str, Any] .. py:class:: Report What one analysis says about its result, in blocks, and who said it. :ivar blocks: Text, tables and figures, in reading order. :ivar title: What the analysis is, as a heading. :ivar provenance: The analyzer's stamp — its class, module, version and parameters (:meth:`~plesty.lib.analyzer.Analyzer.provenance`). .. py:attribute:: blocks :type: list[Any] :value: [] .. py:attribute:: title :type: str :value: '' .. py:attribute:: provenance :type: dict[str, Any] .. py:method:: __post_init__() -> None Turn each string into a :class:`Text`, then check every block. :raises ReportError: If a block is not a text, table or figure. .. py:method:: to_html(level: int = 2) -> str Render as an HTML fragment; see :func:`plesty.lib.report.html.to_html`. .. py:method:: to_latex() -> str Render as LaTeX; see :func:`plesty.lib.report.latex.to_latex`. .. py:method:: to_dict() -> dict[str, Any] Return the saved document: the blocks under :data:`REPORT_KEY`. .. py:method:: from_dict(document: Mapping[str, Any]) -> Report :classmethod: Rebuild a report from :meth:`to_dict`. :raises ReportError: If the document is not a report, was written by a newer plesty-lib than this one, or holds a block kind this one does not know. .. py:method:: save(path: str | pathlib.Path) -> pathlib.Path Write the report as JSON, atomically. :param path: Destination; by convention the outputs' stem plus :data:`REPORT_SUFFIX`. :returns: The written path. .. py:method:: load(path: str | pathlib.Path) -> Report :classmethod: Read a report written by :meth:`save`. :raises ReportError: As :meth:`from_dict`, or when the file is not JSON.