Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
41133f2
Make tabs_ready public
ayjayt Aug 21, 2025
d4a5bd4
Add some types to kaleido.py
ayjayt Aug 22, 2025
0cbd7c0
Merge branch 'master' into andrew/test_coverage
ayjayt Aug 22, 2025
196aca7
Merge branch 'master' into andrew/sometypes
ayjayt Aug 22, 2025
4b1ea3c
Merge branch 'andrew/sometypes' into andrew/test_coverage
ayjayt Aug 22, 2025
583b8c8
Upgrade choreographer for typing.
ayjayt Aug 22, 2025
d36dbab
Add types to __init__
ayjayt Aug 22, 2025
9838ac4
Fix some bad typing in __init__.py
ayjayt Aug 22, 2025
7d4d297
Improve page logic.
ayjayt Aug 22, 2025
5719383
Merge branch 'andrew/sometypes' into andrew/test_coverage
ayjayt Aug 22, 2025
cbd925b
Add claude run on init and public api
ayjayt Aug 22, 2025
d99bb88
Move pytest async detection to auto
ayjayt Aug 23, 2025
6d3cf13
Iterate with Claude on test_public_api
ayjayt Aug 23, 2025
e4ba49c
Make corrections to public api tests.
ayjayt Aug 23, 2025
1898bd9
Fix up test_init
ayjayt Aug 23, 2025
4147850
Add comments.
ayjayt Aug 23, 2025
7a808b4
Reorganize a bit.
ayjayt Aug 23, 2025
abcc184
Make error_log/profiler 100% optional in __init__
ayjayt Aug 23, 2025
24d0dab
Iterate with claude on public_api test.
ayjayt Aug 25, 2025
f5cc483
Parameterize public api tests for fig/to_dict()
ayjayt Aug 25, 2025
9489b71
Change to using args fixtures.
ayjayt Aug 25, 2025
824230c
Remove regex for html parsing.
ayjayt Aug 25, 2025
d152afa
Fix non-existent function
ayjayt Aug 25, 2025
ec62908
Fix whitespace errors.
ayjayt Aug 25, 2025
930f338
Add types for page_generaetor.
ayjayt Aug 25, 2025
3ddb876
Merge branch 'andrew/sometypes' into andrew/test_coverage
ayjayt Aug 25, 2025
f9175fb
Add hypothesis tests generated by Claude.
ayjayt Aug 25, 2025
2b20a3e
Fix file path ensuring error.
ayjayt Aug 26, 2025
a71a202
Add hypothesis.
ayjayt Aug 26, 2025
63332ca
Allow _ensure_path to deal with str/encoding combos
ayjayt Aug 26, 2025
d495e34
Tweak manually hypothesis tests in PageGenerator
ayjayt Aug 26, 2025
e92fca8
Rename test file
ayjayt Aug 26, 2025
6711e61
Fix bad mathjax conditional
ayjayt Aug 26, 2025
76ad595
Add more types to Kaleido
ayjayt Aug 26, 2025
cfdacc1
Add check for None value.
ayjayt Aug 26, 2025
9fd331c
Fix kaleido typing errors.
ayjayt Aug 26, 2025
e3d5a87
Iterate on kaleido.py tests claude
ayjayt Aug 26, 2025
c8afc88
Mark test skipped for after refactor.
ayjayt Aug 26, 2025
30eecca
Add further kaleido tests.
ayjayt Aug 26, 2025
8ca2ae7
Split context/noncontext tests into two
ayjayt Aug 27, 2025
84629f3
Close browser before cancelling kaleido tasks.
ayjayt Aug 27, 2025
74c8765
Reorganize so __init__ creates no tmp dir
ayjayt Aug 27, 2025
d2dd530
Fix test_kaleido tests.
ayjayt Aug 27, 2025
cc97c41
Remove forcefail
ayjayt Aug 27, 2025
0adba8f
Shore up current file detecting for testing.
ayjayt Aug 28, 2025
a115723
Use tmp_path not __file__ for valid file.
ayjayt Aug 28, 2025
6f281f8
Supress unhelpful hypo health checks.
ayjayt Aug 28, 2025
be01fdb
Add faster assert to prove file existence
ayjayt Aug 28, 2025
0bca6f7
Add additional url parsing tool.
ayjayt Aug 28, 2025
a76fbad
Make special fixture for non existent file URI:
ayjayt Aug 28, 2025
719a7fd
Fix bad path definition
ayjayt Aug 28, 2025
02a1f80
Change function to str, so don't call
ayjayt Aug 28, 2025
cfb4eff
Be more explicit in path parsing.
ayjayt Aug 28, 2025
f9e7f64
Add missing function.
ayjayt Aug 28, 2025
69294ed
Add logging.
ayjayt Aug 28, 2025
5407965
Add yet more logging.
ayjayt Aug 28, 2025
33ead6d
Fix bad logic.
ayjayt Aug 28, 2025
449282b
Tone down unreasonable 20 processor test.
ayjayt Aug 28, 2025
62fcefe
Remove hypo deadlines for slow CI runners.
ayjayt Aug 28, 2025
501fb80
Organize a bit conftest.py
ayjayt Aug 28, 2025
b8f7185
Clear up mathjax logic.
ayjayt Aug 29, 2025
970f8b8
Test Path() as well as str() in filenotfound tests.
ayjayt Aug 29, 2025
7c70737
Fix to properly validate Path() types
ayjayt Aug 29, 2025
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
2 changes: 1 addition & 1 deletion src/py/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repos:
- id: add-trailing-comma
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.8.2
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff
Expand Down
62 changes: 35 additions & 27 deletions src/py/kaleido/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,24 @@

from __future__ import annotations

from typing import TYPE_CHECKING

from choreographer.cli import get_chrome, get_chrome_sync

from . import _sync_server
from ._page_generator import PageGenerator
from .kaleido import Kaleido

if TYPE_CHECKING:
from collections.abc import AsyncIterable, Iterable
from pathlib import Path
from typing import Any, TypeVar, Union

from ._fig_tools import Figurish, LayoutOpts

T = TypeVar("T")
AnyIterable = Union[AsyncIterable[T], Iterable[T]]

__all__ = [
"Kaleido",
"PageGenerator",
Expand All @@ -30,7 +42,7 @@
_global_server = _sync_server.GlobalKaleidoServer()


def start_sync_server(*args, silence_warnings=False, **kwargs):
def start_sync_server(*args: Any, silence_warnings: bool = False, **kwargs: Any):
"""
Start a kaleido server which will process all sync generation requests.

Expand All @@ -50,7 +62,7 @@ def start_sync_server(*args, silence_warnings=False, **kwargs):
_global_server.open(*args, silence_warnings=silence_warnings, **kwargs)


def stop_sync_server(*, silence_warnings=False):
def stop_sync_server(*, silence_warnings: bool = False):
"""
Stop the kaleido server. It can be restarted. Warns if not started.

Expand All @@ -63,12 +75,12 @@ def stop_sync_server(*, silence_warnings=False):


async def calc_fig(
fig,
path=None,
opts=None,
fig: Figurish,
path: str | None | Path = None,
opts: LayoutOpts | None = None,
*,
topojson=None,
kopts=None,
topojson: str | None = None,
kopts: dict[str, Any] | None = None,
):
"""
Return binary for plotly figure.
Expand All @@ -85,7 +97,7 @@ async def calc_fig(

"""
kopts = kopts or {}
kopts["n"] = 1
kopts["n"] = 1 # should we force this?
async with Kaleido(**kopts) as k:
return await k.calc_fig(
fig,
Expand All @@ -95,15 +107,14 @@ async def calc_fig(
)


async def write_fig( # noqa: PLR0913 (too many args, complexity)
fig,
path=None,
opts=None,
async def write_fig(
fig: Figurish,
path: str | None | Path = None,
opts: LayoutOpts | None = None,
*,
topojson=None,
error_log=None,
profiler=None,
kopts=None,
topojson: str | None = None,
kopts: dict[str, Any] | None = None,
**kwargs,
):
"""
Write a plotly figure(s) to a file.
Expand All @@ -123,17 +134,15 @@ async def write_fig( # noqa: PLR0913 (too many args, complexity)
path=path,
opts=opts,
topojson=topojson,
error_log=error_log,
profiler=profiler,
**kwargs,
)


async def write_fig_from_object(
generator,
generator: AnyIterable, # this could be more specific with []
*,
error_log=None,
profiler=None,
kopts=None,
kopts: dict[str, Any] | None = None,
**kwargs,
):
"""
Write a plotly figure(s) to a file.
Expand All @@ -150,28 +159,27 @@ async def write_fig_from_object(
async with Kaleido(**(kopts or {})) as k:
await k.write_fig_from_object(
generator,
error_log=error_log,
profiler=profiler,
**kwargs,
)


def calc_fig_sync(*args, **kwargs):
def calc_fig_sync(*args: Any, **kwargs: Any):
"""Call `calc_fig` but blocking."""
if _global_server.is_running():
return _global_server.call_function("calc_fig", *args, **kwargs)
else:
return _sync_server.oneshot_async_run(calc_fig, args=args, kwargs=kwargs)


def write_fig_sync(*args, **kwargs):
def write_fig_sync(*args: Any, **kwargs: Any):
"""Call `write_fig` but blocking."""
if _global_server.is_running():
_global_server.call_function("write_fig", *args, **kwargs)
else:
_sync_server.oneshot_async_run(write_fig, args=args, kwargs=kwargs)


def write_fig_from_object_sync(*args, **kwargs):
def write_fig_from_object_sync(*args: Any, **kwargs: Any):
"""Call `write_fig_from_object` but blocking."""
if _global_server.is_running():
_global_server.call_function("write_fig_from_object", *args, **kwargs)
Expand Down
37 changes: 27 additions & 10 deletions src/py/kaleido/_page_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pathlib import Path
from urllib.parse import urlparse
from urllib.request import url2pathname

import logistro

Expand All @@ -16,12 +17,21 @@
KJS_PATH = Path(__file__).resolve().parent / "vendor" / "kaleido_scopes.js"


def _ensure_path(path: Path | str):
def _ensure_path(path: Path | str | tuple[str | Path, str]) -> None:
if isinstance(path, tuple):
path = path[0]
_logger.debug(f"Ensuring path {path!s}")
if urlparse(str(path)).scheme.startswith("http"): # is url
parsed = urlparse(str(path))
_logger.debug(f"Parsed file path: {parsed}")
if parsed.scheme.startswith("http"): # is url
return
if not Path(path).exists():
raise FileNotFoundError(f"{path!s} does not exist.")
elif parsed.scheme.startswith("file"):
if (_p := Path(url2pathname(parsed.path))).exists():
return
_logger.error(f"File parsed to: {_p}")
elif Path(path).exists():
return
raise FileNotFoundError(f"{path!s} does not exist.")


class PageGenerator:
Expand Down Expand Up @@ -54,7 +64,14 @@ class PageGenerator:
"""
"""The footer is the HTML that always goes on the bottom. Rarely needs changing."""

def __init__(self, *, plotly=None, mathjax=None, others=None, force_cdn=False):
def __init__( # noqa: C901
self,
*,
plotly: None | Path | str | tuple[Path | str, str] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what situation could these arguments be tuples? Is that documented anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its probably not documented, but actually, I think plotly.js was getting misinterpreted by the browser as utf-16, so i had to set it as utf-8 manually. So the tuple is (link, encoding).

I think its a hidden feature at the moment, I haven't done a revision of the documentation, I was thinking of making it a NamedTuple.

mathjax: None | Path | str | bool | tuple[Path | str, str] = None,
others: None | list[Path | str | tuple[Path | str, str]] = None,
force_cdn: bool = False,
):
"""
Create a PageGenerator.

Expand All @@ -71,9 +88,9 @@ def __init__(self, *, plotly=None, mathjax=None, others=None, force_cdn=False):
"""
self._scripts = []
if mathjax is not False:
if not mathjax:
if mathjax is None or mathjax is True:
mathjax = DEFAULT_MATHJAX
else:
elif mathjax:
_ensure_path(mathjax)
self._scripts.append(mathjax)
if force_cdn:
Expand Down Expand Up @@ -101,7 +118,7 @@ def __init__(self, *, plotly=None, mathjax=None, others=None, force_cdn=False):
except ImportError:
_logger.info("Plotly not installed. Using CDN.")
plotly = (DEFAULT_PLOTLY, "utf-8")
elif isinstance(plotly, str):
elif isinstance(plotly, (str, Path)):
_ensure_path(plotly)
plotly = (plotly, "utf-8")
_logger.debug(f"Plotly script: {plotly}")
Expand All @@ -123,8 +140,8 @@ def generate_index(self, path=None):
script_tag = '\n <script src="%s"></script>'
script_tag_charset = '\n <script src="%s" charset="%s"></script>'
for script in self._scripts:
if isinstance(script, str):
page += script_tag % script
if isinstance(script, (str, Path)):
page += script_tag % str(script)
else:
page += script_tag_charset % script
page += self.footer
Expand Down
Loading
Loading