Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/guides/publishing/opengraph.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ __marimo__/assets/<notebook_stem>/opengraph.png
To generate these thumbnails, use:

```bash
marimo tools thumbnails generate notebook.py
marimo tools thumbnails generate folder/
marimo export thumbnail notebook.py
marimo export thumbnail folder/
```

For execution and sandbox options (and for Playwright installation instructions), see [Thumbnails](thumbnails.md).
Expand Down
23 changes: 12 additions & 11 deletions docs/guides/publishing/thumbnails.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Generate screenshot-based thumbnail images for notebooks, used by [OpenGraph previews](opengraph.md) and cards in [gallery mode](../apps.md#gallery-mode).

```bash
marimo export thumbnail generate notebook.py
marimo export thumbnail notebook.py
```

## Output location

Expand Down Expand Up @@ -31,13 +32,13 @@ This is the default OpenGraph thumbnail path used by [OpenGraph previews](opengr
You can generate a thumbnail for a single notebook:

```bash
marimo tools thumbnails generate notebook.py
marimo export thumbnail notebook.py
```

Or generate thumbnails for all notebooks in a directory:

```bash
marimo tools thumbnails generate folder/
marimo export thumbnail folder/
```

When you pass a directory, marimo scans it for marimo notebooks and skips non-notebook files (for example `README.md`).
Expand All @@ -49,19 +50,19 @@ By default, thumbnails are generated without executing the notebook (fast; no ou
=== "No execution (default)"

```bash
marimo tools thumbnails generate notebook.py
marimo export thumbnail notebook.py
```

=== "Execute notebook"

```bash
marimo tools thumbnails generate notebook.py --execute
marimo export thumbnail --execute notebook.py
```

=== "Execute in a sandbox"

```bash
marimo tools thumbnails generate notebook.py --execute --sandbox
marimo export thumbnail --execute --sandbox notebook.py
```

!!! note "Requires uv"
Expand All @@ -81,13 +82,13 @@ By default, thumbnails are generated without executing the notebook (fast; no ou
If a thumbnail already exists, marimo will skip it by default. To replace existing thumbnails:

```bash
marimo tools thumbnails generate notebook.py --overwrite
marimo export thumbnail --overwrite notebook.py
```

To write a thumbnail to a specific filename, use `--output` (single notebook only):

```bash
marimo tools thumbnails generate notebook.py --output thumbnail.png
marimo export thumbnail --output thumbnail.png notebook.py
```

## Tuning quality
Expand All @@ -97,21 +98,21 @@ For OpenGraph previews, the default viewport size is 1200x630. marimo also uses
To increase output resolution, increase `--scale` (max 4):

```bash
marimo tools thumbnails generate notebook.py --scale 3
marimo export thumbnail --scale 3 notebook.py
```

If thumbnails appear blank or partially rendered, increase `--timeout-ms` to wait longer before the screenshot:

```bash
marimo tools thumbnails generate notebook.py --timeout-ms 3000
marimo export thumbnail --timeout-ms 3000 notebook.py
```

## Passing arguments to notebooks

To pass CLI args through to the notebook, separate them with `--`:

```bash
marimo tools thumbnails generate notebook.py -- --foo 123
marimo export thumbnail notebook.py -- --foo 123
```

For more on passing values to notebooks, see [Command Line Arguments](../../api/cli_args.md).
16 changes: 2 additions & 14 deletions marimo/_cli/export/thumbnail.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,6 @@
_READINESS_WAIT_TIMEOUT_MS = 30_000


def _split_paths_and_args(
name: str, args: tuple[str, ...]
) -> tuple[list[str], tuple[str, ...]]:
paths = [name]
for index, arg in enumerate(args):
if arg == "--":
return paths, args[index + 1 :]
paths.append(arg)
return paths, ()


def _collect_notebooks(paths: Iterable[Path]) -> list[MarimoPath]:
notebooks: dict[str, MarimoPath] = {}

Expand Down Expand Up @@ -531,8 +520,7 @@ def thumbnail(
) from None
raise

paths, notebook_args = _split_paths_and_args(str(name), args)
notebooks = _collect_notebooks([Path(p) for p in paths])
notebooks = _collect_notebooks([Path(name)])
if not notebooks:
raise click.ClickException("No marimo notebooks found.")
if output is not None and len(notebooks) > 1:
Expand Down Expand Up @@ -562,7 +550,7 @@ def thumbnail(
overwrite=overwrite,
include_code=include_code,
execute=execute,
notebook_args=notebook_args,
notebook_args=args,
continue_on_error=continue_on_error,
sandbox=sandbox_mode is not None,
)
Expand Down
92 changes: 91 additions & 1 deletion tests/_cli/test_cli_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import time
from os import path
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import click
import pytest

from marimo._dependencies.dependencies import DependencyManager
Expand Down Expand Up @@ -860,3 +861,92 @@ def test_export_pdf_missing_dependencies(
stderr = p.stderr.decode()
assert "nbconvert" in stderr
assert "pip install" in stderr


@pytest.mark.skipif(
not DependencyManager.playwright.has(),
reason="This test requires playwright.",
)
class TestExportThumbnail:
def test_export_thumbnail(self, temp_marimo_file: str) -> None:
p = _run_export("thumbnail", temp_marimo_file)
_assert_success(p)

def test_export_thumbnail_with_args(self, temp_marimo_file: str) -> None:
p = _run_export("thumbnail", temp_marimo_file, "--", "--foo", "123")
_assert_success(p)


class TestClickArgsParsing:
"""
Tests below are technically testing Click more than Marimo. This was created as part of an investigation into
the Click option to allow interspersed arguments or not, and the presence of "--" in the `args` parameter.

In review, we discussed leaving some of the tests behind to continue illustrating. They can also act as a sort of
sandbox to try alternative approaches.
"""

@staticmethod
def _params(
command, expected_name, expected_args, expected_opt
) -> tuple[Any, ...]:
return pytest.param(
command,
expected_name,
expected_args,
expected_opt,
id=f"`test_command {' '.join(command)}`",
)

@pytest.mark.parametrize(
(
"command",
"expected_name",
"expected_args",
"expected_opt",
),
[
_params(["nb.py"], "nb.py", (), False),
_params(["--opt", "nb.py"], "nb.py", (), True),
_params(["nb.py", "--opt"], "nb.py", (), True),
_params(
["nb.py", "--", "--foo", "123"],
"nb.py",
("--foo", "123"),
False,
),
_params(
["--opt", "nb.py", "--", "--foo", "123"],
"nb.py",
("--foo", "123"),
True,
),
_params(
["nb.py", "--opt", "--", "--foo", "123"],
"nb.py",
("--foo", "123"),
True,
),
],
)
def test_click_args_parsing(
self,
command: list[str],
expected_name: str,
expected_args: tuple[str, ...],
expected_opt: bool,
) -> None:
@click.command("test_command")
@click.argument("name")
@click.option("--opt/--no-opt", default=False)
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
def test_command(
name: str, opt: bool, args: tuple[str, ...]
) -> tuple[str, tuple[str, ...], bool]:
return name, args, opt

assert test_command.main(command, standalone_mode=False) == (
expected_name,
expected_args,
expected_opt,
)