Skip to content

API reference

Built from the docstrings in src/molviz/, so this page and the code cannot drift apart. Write the docstring; this page follows.

The examples are tests

pixi run check runs the Examples blocks in src/molviz/, so an example that no longer matches its code fails the tests.

Write one where it makes the object easier to use, and leave it out where it would not. An example nobody keeps up to date is worse than none.

Keep an example cheap, offline and deterministic. It has to give the same answer on any machine, with no network. A line that cannot do that needs # doctest: +SKIP at the end of that line.

Two things about that marker are easy to get backwards:

  • It covers only the line it sits on. It does not carry to the line below. In a block that mixes lines that run with lines that cannot, each line that cannot run needs its own.
  • A trailing comment written as plain prose looks just like a marker and is not one. Only the # doctest: form is read as one.

molviz

molviz.viewer.viewer_script

viewer_script() -> str

Return the viewer's JavaScript, for a page to inline in a <script> element.

Running it defines one global, molviz. The same release returns the same text.

Raises:

Type Description
FileNotFoundError

If the viewer was never built: pixi run build-js writes it.

Source code in src/molviz/viewer.py
 6
 7
 8
 9
10
11
12
13
14
15
16
def viewer_script() -> str:
    """Return the viewer's JavaScript, for a page to inline in a ``<script>`` element.

    Running it defines one global, ``molviz``. The same release returns the same text.

    Raises
    ------
    FileNotFoundError
        If the viewer was never built: ``pixi run build-js`` writes it.
    """
    return files("molviz").joinpath("viewer.js").read_text(encoding="utf-8")

molviz.check

check(record: dict[str, Any]) -> None

Check record against the format, with the viewer's own check.

Raises:

Type Description
ValueError

If the record breaks the format. The message names every problem by its path.

TypeError

If a value has no JSON form, such as a set.

Examples:

>>> check({"name": "p", "circular": False, "bases": "ACGX"})
Traceback (most recent call last):
...
ValueError: bases: has "X" at index 3, which is not an IUPAC nucleotide letter
Source code in src/molviz/drawing.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def check(record: dict[str, Any]) -> None:
    """Check ``record`` against the format, with the viewer's own check.

    Raises
    ------
    ValueError
        If the record breaks the format. The message names every problem by its path.
    TypeError
        If a value has no JSON form, such as a set.

    Examples
    --------
    >>> check({"name": "p", "circular": False, "bases": "ACGX"})
    Traceback (most recent call last):
    ...
    ValueError: bases: has "X" at index 3, which is not an IUPAC nucleotide letter
    """
    call("record", "check", record)

molviz.check_gel

check_gel(gel: dict[str, Any]) -> None

Check gel against the gel format, with the viewer's own check.

Raises:

Type Description
ValueError

If the gel breaks the format. The message names every problem by its path.

TypeError

If a value has no JSON form, such as a set.

Examples:

>>> check_gel({"ladder": {"name": "100 bp", "bands": [100, 0]}, "lanes": []})
Traceback (most recent call last):
...
ValueError: ladder.bands[1]: must be 1 or more
Source code in src/molviz/gel.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def check_gel(gel: dict[str, Any]) -> None:
    """Check ``gel`` against the gel format, with the viewer's own check.

    Raises
    ------
    ValueError
        If the gel breaks the format. The message names every problem by its path.
    TypeError
        If a value has no JSON form, such as a set.

    Examples
    --------
    >>> check_gel({"ladder": {"name": "100 bp", "bands": [100, 0]}, "lanes": []})
    Traceback (most recent call last):
    ...
    ValueError: ladder.bands[1]: must be 1 or more
    """
    call("gel", "check", gel)

molviz.check_plate

check_plate(plate: dict[str, Any]) -> None

Check plate against the plate format, with the viewer's own check.

Raises:

Type Description
ValueError

If the plate breaks the format. The message names every problem by its path.

TypeError

If a value has no JSON form, such as a set.

Examples:

>>> check_plate({"rows": 8, "columns": 12, "wells": {"I1": "water"}})
Traceback (most recent call last):
...
ValueError: wells.I1: not on the plate, which runs A1 to H12
Source code in src/molviz/plate.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def check_plate(plate: dict[str, Any]) -> None:
    """Check ``plate`` against the plate format, with the viewer's own check.

    Raises
    ------
    ValueError
        If the plate breaks the format. The message names every problem by its path.
    TypeError
        If a value has no JSON form, such as a set.

    Examples
    --------
    >>> check_plate({"rows": 8, "columns": 12, "wells": {"I1": "water"}})
    Traceback (most recent call last):
    ...
    ValueError: wells.I1: not on the plate, which runs A1 to H12
    """
    call("plate", "check", plate)

molviz.draw

draw(record: dict[str, Any], **options: Any) -> Drawing

Draw record with options, checking both with the viewer.

The options are the viewer's, passed to it as they are, and each one left out takes its default. The API reference lists them: https://liuhlab.github.io/liulab-molviz/api/#options.

Raises:

Type Description
ValueError

If the record breaks the format, or else if an option is unknown or wrong. The message names every problem by its path, an option's under options.

TypeError

If a value has no JSON form, such as a set.

Examples:

>>> draw({"name": "p", "circular": False, "bases": "ACGT"}, bogus=1)
Traceback (most recent call last):
...
ValueError: options.bogus: unknown key
Source code in src/molviz/drawing.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def draw(record: dict[str, Any], **options: Any) -> Drawing:
    """Draw ``record`` with ``options``, checking both with the viewer.

    The options are the viewer's, passed to it as they are, and each one left out takes its
    default. The API reference lists them: <https://liuhlab.github.io/liulab-molviz/api/#options>.

    Raises
    ------
    ValueError
        If the record breaks the format, or else if an option is unknown or wrong. The message
        names every problem by its path, an option's under ``options``.
    TypeError
        If a value has no JSON form, such as a set.

    Examples
    --------
    >>> draw({"name": "p", "circular": False, "bases": "ACGT"}, bogus=1)
    Traceback (most recent call last):
    ...
    ValueError: options.bogus: unknown key
    """
    drawn = call("record", "svg", record, options)
    return Drawing("record", record, drawn, record["name"], options)

molviz.draw_gel

draw_gel(gel: dict[str, Any]) -> Drawing

Draw gel, checking it with the viewer.

Ladder sizes that would crowd are left out and not counted, so the drawing's hidden stays empty and its notice blank. A page written from it takes the gel's title, or "Gel" when it has none.

Raises:

Type Description
ValueError

If the gel breaks the format. The message names every problem by its path.

TypeError

If a value has no JSON form, such as a set.

Examples:

>>> gel = {
...     "title": "PCR products",
...     "ladder": {"name": "NEB 100 bp DNA Ladder (N3231)", "bands": [100, 200, 300]},
...     "lanes": [{"name": "GFP", "bands": [741]}, {"name": "empty vector", "bands": []}],
... }
>>> draw_gel(gel).write("gel.pdf")
Source code in src/molviz/gel.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def draw_gel(gel: dict[str, Any]) -> Drawing:
    """Draw ``gel``, checking it with the viewer.

    Ladder sizes that would crowd are left out and not counted, so the drawing's ``hidden``
    stays empty and its ``notice`` blank. A page written from it takes the gel's title, or
    "Gel" when it has none.

    Raises
    ------
    ValueError
        If the gel breaks the format. The message names every problem by its path.
    TypeError
        If a value has no JSON form, such as a set.

    Examples
    --------
    >>> gel = {
    ...     "title": "PCR products",
    ...     "ladder": {"name": "NEB 100 bp DNA Ladder (N3231)", "bands": [100, 200, 300]},
    ...     "lanes": [{"name": "GFP", "bands": [741]}, {"name": "empty vector", "bands": []}],
    ... }
    >>> draw_gel(gel).write("gel.pdf")  # doctest: +SKIP
    """
    drawn = call("gel", "svg", gel)
    return Drawing("gel", gel, drawn, gel.get("title") or "Gel")

molviz.draw_plate

draw_plate(plate: dict[str, Any]) -> Drawing

Draw plate, checking it with the viewer.

The drawing's hidden is empty and its notice blank. A page written from it takes the plate's title, or "Plate" when it has none.

Raises:

Type Description
ValueError

If the plate breaks the format. The message names every problem by its path.

TypeError

If a value has no JSON form, such as a set.

Examples:

>>> plate = {"title": "picked 1", "rows": 8, "columns": 12, "wells": {"A1": "water"}}
>>> draw_plate(plate).write("plate.pdf")
Source code in src/molviz/plate.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def draw_plate(plate: dict[str, Any]) -> Drawing:
    """Draw ``plate``, checking it with the viewer.

    The drawing's ``hidden`` is empty and its ``notice`` blank. A page written from it takes
    the plate's title, or "Plate" when it has none.

    Raises
    ------
    ValueError
        If the plate breaks the format. The message names every problem by its path.
    TypeError
        If a value has no JSON form, such as a set.

    Examples
    --------
    >>> plate = {"title": "picked 1", "rows": 8, "columns": 12, "wells": {"A1": "water"}}
    >>> draw_plate(plate).write("plate.pdf")  # doctest: +SKIP
    """
    drawn = call("plate", "svg", plate)
    return Drawing("plate", plate, drawn, plate.get("title") or "Plate")

molviz.Drawing

An input drawn, as each of molviz's draw calls returns it. A caller never makes one.

Attributes:

Name Type Description
hidden list[dict[str, Any]]

The labels left out because they would overlap: of what a file draws at full size, or of what a page opens on. Each is the viewer's Hidden, defined in its drawing.ts: the label's kind, its name and its spans.

notice str

The sentence that counts the hidden labels, worded by the viewer. Empty when nothing hides.

Source code in src/molviz/drawing.py
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
class Drawing:
    """An input drawn, as each of molviz's draw calls returns it. A caller never makes one.

    Attributes
    ----------
    hidden : list[dict[str, Any]]
        The labels left out because they would overlap: of what a file draws at full size, or
        of what a page opens on. Each is the viewer's ``Hidden``, defined in its
        ``drawing.ts``: the label's kind, its name and its spans.
    notice : str
        The sentence that counts the hidden labels, worded by the viewer. Empty when nothing
        hides.
    """

    def __init__(
        self,
        name: str,
        data: dict[str, Any],
        drawn: dict[str, Any],
        title: str,
        options: dict[str, Any] | None = None,
    ) -> None:
        # `drawn` is the viewer's drawing of `data`, the input called `name`. A page titled
        # `title` hands that input's draw call `data`, then any `options`.
        self._name = name
        # As text, so a change the caller makes to an input afterwards never reaches the page.
        self._data = _json(data)
        self._options = None if options is None else _json(options)
        self._title = title
        self._svg: str = drawn["svg"]
        self._pages: list[str] = drawn["pages"]
        self.hidden: list[dict[str, Any]] = drawn["hidden"]
        self.notice: str = drawn["notice"]

    def write(self, path: str | PathLike[str], dpi: float = 300) -> None:
        """Write the drawing to ``path``, in the format its ending names, in any case.

        The endings are ``.html``, a page that draws it; ``.svg``; ``.png``, at ``dpi``
        pixels per inch; and ``.pdf``. One unit of the drawing is one point, so a PDF page is
        the drawing's own size and a PNG is that size in print, cut to whole pixels.

        Raises
        ------
        ValueError
            If the ending is none of those.
        NotImplementedError
            For a PDF of more than one page.
        """
        path = Path(path)
        match path.suffix.lower():
            case ".html":
                page = _page(self._title, self._name, self._data, self._options)
                path.write_bytes(page.encode())
            case ".svg":
                path.write_bytes(self._svg.encode())
            case ".png":
                import vl_convert

                # vl-convert ships no type stub, so pyright cannot see its functions.
                png = vl_convert.svg_to_png(self._svg, ppi=dpi)  # pyright: ignore[reportAttributeAccessIssue]
                path.write_bytes(png)
            case ".pdf":
                if len(self._pages) > 1:
                    raise NotImplementedError(
                        f"cannot write {path!s}: a PDF of more than one page is not written yet"
                    )
                import vl_convert

                pdf = vl_convert.svg_to_pdf(self._pages[0])  # pyright: ignore[reportAttributeAccessIssue]
                path.write_bytes(pdf)
            case _:
                raise ValueError(
                    f"cannot write {path!s}: its ending must be .html, .svg, .png or .pdf"
                )

write

write(path: str | PathLike[str], dpi: float = 300) -> None

Write the drawing to path, in the format its ending names, in any case.

The endings are .html, a page that draws it; .svg; .png, at dpi pixels per inch; and .pdf. One unit of the drawing is one point, so a PDF page is the drawing's own size and a PNG is that size in print, cut to whole pixels.

Raises:

Type Description
ValueError

If the ending is none of those.

NotImplementedError

For a PDF of more than one page.

Source code in src/molviz/drawing.py
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def write(self, path: str | PathLike[str], dpi: float = 300) -> None:
    """Write the drawing to ``path``, in the format its ending names, in any case.

    The endings are ``.html``, a page that draws it; ``.svg``; ``.png``, at ``dpi``
    pixels per inch; and ``.pdf``. One unit of the drawing is one point, so a PDF page is
    the drawing's own size and a PNG is that size in print, cut to whole pixels.

    Raises
    ------
    ValueError
        If the ending is none of those.
    NotImplementedError
        For a PDF of more than one page.
    """
    path = Path(path)
    match path.suffix.lower():
        case ".html":
            page = _page(self._title, self._name, self._data, self._options)
            path.write_bytes(page.encode())
        case ".svg":
            path.write_bytes(self._svg.encode())
        case ".png":
            import vl_convert

            # vl-convert ships no type stub, so pyright cannot see its functions.
            png = vl_convert.svg_to_png(self._svg, ppi=dpi)  # pyright: ignore[reportAttributeAccessIssue]
            path.write_bytes(png)
        case ".pdf":
            if len(self._pages) > 1:
                raise NotImplementedError(
                    f"cannot write {path!s}: a PDF of more than one page is not written yet"
                )
            import vl_convert

            pdf = vl_convert.svg_to_pdf(self._pages[0])  # pyright: ignore[reportAttributeAccessIssue]
            path.write_bytes(pdf)
        case _:
            raise ValueError(
                f"cannot write {path!s}: its ending must be .html, .svg, .png or .pdf"
            )

Options

draw takes these options by name. Each says what the drawing starts with. An option it does not know is an error, and one left out takes its default.

Option What it does Default
span Draws only the bases from start to end, as in {"start": 10, "end": 500}. start counts from 0, and the base at end is not drawn. The line is numbered as the record is. On a circular record, end may pass the record's length to run across the origin. the whole record
map The map to draw. For now it can only be "line". "line"
types_off The feature types to leave out. A type the record does not have is ignored. ["source"]

The command line

The whole module, because typer makes every verb a plain function with a docstring, and molviz.cli:app — the object [project.scripts] registers — is built from them.

molviz.cli

The command line.

Typer, because every lab repo that ships a command line uses it: one typer.Typer named app, and no_args_is_help=True so a bare invocation prints help instead of nothing. A larger surface grows by mounting sub-apps with app.add_typer.

main

main() -> None

Draw DNA sequences and plasmids.

Source code in src/molviz/cli.py
19
20
21
@app.callback()
def main() -> None:
    """Draw DNA sequences and plasmids."""

version

version() -> None

Print the installed package version.

Source code in src/molviz/cli.py
24
25
26
27
@app.command()
def version() -> None:
    """Print the installed package version."""
    typer.echo(_package_version)