diff --git a/.github/workflows/publish_testpypi.yml b/.github/workflows/publish_testpypi.yml index 2e9492a1..c99fb64a 100644 --- a/.github/workflows/publish_testpypi.yml +++ b/.github/workflows/publish_testpypi.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python_v: ['3.8', '3.9', '3.10', '3.12', '3.13', '3.14'] + python_v: ['3.9', '3.10', '3.12', '3.13', '3.14'] # chrome_v: ['-1'] defaults: run: diff --git a/CHANGELOG.md b/CHANGELOG.md index cd4fc87c..afd1c338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ ## Unreleased +### Changed +- Update Choreographer to v1.4.0 and use its large figure chunking, remove the Kaleido chunking code [[#478](https://github.com/plotly/Kaleido/pull/478)] + +### Removed +- Drop support for Python 3.8 [[#478](https://github.com/plotly/Kaleido/pull/478)] +- Remove the `orjson` dependency [[#478](https://github.com/plotly/Kaleido/pull/478)] + +### Fixed +- Restore serialization of figure specs through the `plotly` JSON encoder [[#478](https://github.com/plotly/Kaleido/pull/478)] + ## v1.4.0 ### Fixed diff --git a/src/py/.python-version b/src/py/.python-version index cc1923a4..bd28b9c5 100644 --- a/src/py/.python-version +++ b/src/py/.python-version @@ -1 +1 @@ -3.8 +3.9 diff --git a/src/py/docs/examples_script.py b/src/py/docs/examples_script.py index c0f5a8f3..3219af3d 100644 --- a/src/py/docs/examples_script.py +++ b/src/py/docs/examples_script.py @@ -1,5 +1,5 @@ # /// script -# requires-python = ">=3.8" +# requires-python = ">=3.9" # dependencies = [ # "pandas", # "plotly[express]", diff --git a/src/py/kaleido/_kaleido_tab/_tab.py b/src/py/kaleido/_kaleido_tab/_tab.py index 800676ea..be1a7685 100644 --- a/src/py/kaleido/_kaleido_tab/_tab.py +++ b/src/py/kaleido/_kaleido_tab/_tab.py @@ -1,11 +1,9 @@ from __future__ import annotations import base64 -from decimal import Decimal from typing import TYPE_CHECKING import logistro -import orjson from . import _devtools_utils as _dtools from . import _js_logger @@ -21,22 +19,10 @@ _TEXT_FORMATS = ("svg", "json") # eps -_CHUNK_SIZE = 10 * 1024 * 1024 # 10 MB _logger = logistro.getLogger(__name__) -def _orjson_default(obj): - """Fallback for types orjson can't handle natively (e.g. NumPy string arrays).""" - if isinstance(obj, Decimal): - return float(obj) - if hasattr(obj, "isoformat"): # datetime-like, e.g. pandas Timestamp (#458) - return obj.isoformat() - if hasattr(obj, "tolist"): - return obj.tolist() - raise TypeError(f"Type is not JSON serializable: {type(obj).__name__}") - - def _subscribe_new(tab: choreo.Tab, event: str) -> asyncio.Future: """Create subscription to tab clearing old ones first: helper function.""" new_future = tab.subscribe_once(event) @@ -152,38 +138,23 @@ async def _calc_fig( render_prof, stepper, ) -> bytes: - render_prof.profile_log.tick("serializing spec") - spec_str = orjson.dumps( - spec, - default=_orjson_default, - option=orjson.OPT_SERIALIZE_NUMPY, - ).decode() - render_prof.profile_log.tick("spec serialized") - + # Choreographer serializes the spec with the registered PlotlyJSONEncoder + # and splits messages that exceed the Chrome devtools buffer + kaleido_js_fn = ( + r"function(spec, ...args)" + r"{" + r"return kaleido_scopes.plotly(spec, ...args).then(JSON.stringify);" + r"}" + ) render_prof.profile_log.tick("sending javascript") - if len(spec_str) <= _CHUNK_SIZE: - kaleido_js_fn = ( - r"function(specStr, ...args)" - r"{" - r"return kaleido_scopes" - r".plotly(JSON.parse(specStr), ...args)" - r".then(JSON.stringify);" - r"}" - ) - result = await _dtools.exec_js_fn( - self.tab, - self._current_js_id, - kaleido_js_fn, - spec_str, - topojson, - stepper, - ) - else: - result = await self._calc_fig_chunked( - spec_str, - topojson=topojson, - stepper=stepper, - ) + result = await _dtools.exec_js_fn( + self.tab, + self._current_js_id, + kaleido_js_fn, + spec, + topojson, + stepper, + ) _raise_error(result) render_prof.profile_log.tick("javascript sent") @@ -205,45 +176,3 @@ async def _calc_fig( render_prof.data_out_size = len(res) render_prof.js_log = self.js_logger.log return res - - async def _calc_fig_chunked( - self, - spec_str: str, - *, - topojson: str | None, - stepper, - ): - _raise_error( - await _dtools.exec_js_fn( - self.tab, - self._current_js_id, - r"function() { window.__kaleido_chunks = []; }", - ) - ) - - for i in range(0, len(spec_str), _CHUNK_SIZE): - chunk = spec_str[i : i + _CHUNK_SIZE] - _raise_error( - await _dtools.exec_js_fn( - self.tab, - self._current_js_id, - r"function(c) { window.__kaleido_chunks.push(c); }", - chunk, - ) - ) - - kaleido_js_fn = ( - r"function(...args)" - r"{" - r"var spec = JSON.parse(window.__kaleido_chunks.join(''));" - r"delete window.__kaleido_chunks;" - r"return kaleido_scopes.plotly(spec, ...args).then(JSON.stringify);" - r"}" - ) - return await _dtools.exec_js_fn( - self.tab, - self._current_js_id, - kaleido_js_fn, - topojson, - stepper, - ) diff --git a/src/py/kaleido/_page_generator.py b/src/py/kaleido/_page_generator.py index 34a0176b..be3b2903 100644 --- a/src/py/kaleido/_page_generator.py +++ b/src/py/kaleido/_page_generator.py @@ -8,11 +8,11 @@ from ._utils import path_tools if TYPE_CHECKING: - from typing import Tuple, Union + from typing import Union from typing_extensions import TypeAlias - UrlAndCharset: TypeAlias = Tuple[Union[str, Path], str] + UrlAndCharset: TypeAlias = tuple[Union[str, Path], str] """A tuple to explicitly set charset= in the